스프링부트 JWT 는 자주 사용하는 인증 기술 중 하나이다. 우리는 토큰인증방식으로 서버와의 통신을 허용할지 결정한다. 여기에서 반드시 필요로 하는 기술이 바로 JWT 이다. 보통 Session / Cookie 등 인증토큰 방식은 다양하게 있어왔다. 하지만 제각각의 장/단점이 있었다. 하지만 이 부분에 대한 설정 작업은 복잡성이 상대적으로 높았지만, JWT 는 인증 단계 뿐만 아니라 보안적으로도 상대적으로 가장 안정되었다는 평가를 받고 있기에 많은 사람들이 이 기술들을 애용하고 있다.
📌 Spring Boot에서 JWT 상세 설명
Spring Boot에서 JWT(JSON Web Token)는 인증 및 권한 부여(Authorization) 목적으로 많이 사용돼.
기존의 세션 기반 인증 방식과 비교했을 때, 무상태(Stateless)로 인증을 처리할 수 있다는 점이 가장 큰 장점이야.
1️⃣ JWT 개념
JWT는 JSON 형태의 데이터를 안전하게 전달하기 위한 토큰 기반 인증 방식이야.
서버가 클라이언트에게 JWT를 발급하고, 이후 요청에서 해당 토큰을 검증하여 사용자 인증 및 권한을 확인할 수 있어.
2️⃣ JWT 구조
JWT는 3가지 부분으로 구성돼 있고, .(점)으로 구분돼.
Header.Payload.Signature
예제:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzdWIiOiJ1c2VySWQiLCJyb2xlIjoiQURNSU4iLCJpYXQiOjE2OTgwNzE2MDAsImV4cCI6MTY5ODA3NTIwMH0.
C9P5PQbGpL9aXyJ2DkryIXfFC7H5qCNtLmjMaGZ0n9o
각 부분을 분석해 보면:
① Header (헤더)
JWT의 타입과 서명 알고리즘 정보를 포함
{
"alg": "HS256",
"typ": "JWT"
}
- alg: 사용할 알고리즘 (HMAC SHA256, RSA 등)
- typ: 토큰 타입 (JWT)
Base64로 인코딩하면:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
② Payload (페이로드)
- **사용자 정보(Claim)**를 포함하는 부분
- 일반적으로 인증과 권한 정보를 포함
{
"sub": "userId",
"role": "ADMIN",
"iat": 1698071600,
"exp": 1698075200
}
- sub (Subject): 사용자 ID 또는 고유 식별자
- role: 사용자 권한 (ADMIN, USER 등)
- iat (Issued At): 토큰 생성 시간
- exp (Expiration Time): 만료 시간
Base64로 인코딩하면:
eyJzdWIiOiJ1c2VySWQiLCJyb2xlIjoiQURNSU4iLCJpYXQiOjE2OTgwNzE2MDAsImV4cCI6MTY5ODA3NTIwMH0
③ Signature (서명)
- 토큰의 무결성을 검증하기 위해 사용
- 서버에서 비밀 키(secret key)를 사용해 서명
- JWT 변조 여부를 확인할 때 사용됨
HMACSHA256(
base64UrlEncode(header) + "." + base64UrlEncode(payload),
secret_key
)
Base64로 인코딩하면:
C9P5PQbGpL9aXyJ2DkryIXfFC7H5qCNtLmjMaGZ0n9o
그렇다면 Springboot 에서는 어떻게 JWT 를 적용할 수 있을까?
② JWT 생성 및 검증 클래스 작성
import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import java.util.Date;
import java.util.Base64;
import javax.crypto.SecretKey;
public class JwtUtil {
private static final String SECRET_KEY = "YourSecretKeyForJwtToken123456"; // 256-bit 이상의 키 권장
private static final long EXPIRATION_TIME = 1000 * 60 * 60; // 1시간
// 🔹 JWT 생성
public static String generateToken(String username, String role) {
SecretKey key = Keys.hmacShaKeyFor(Base64.getDecoder().decode(SECRET_KEY));
return Jwts.builder()
.setSubject(username) // sub
.claim("role", role) // 사용자 권한 정보 추가
.setIssuedAt(new Date()) // iat (발급 시간)
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME)) // exp (만료 시간)
.signWith(key, SignatureAlgorithm.HS256) // HS256 알고리즘 사용
.compact();
}
// 🔹 JWT 검증
public static Claims verifyToken(String token) {
SecretKey key = Keys.hmacShaKeyFor(Base64.getDecoder().decode(SECRET_KEY));
return Jwts.parserBuilder()
.setSigningKey(key)
.build()
.parseClaimsJws(token)
.getBody();
}
}
③ JWT 테스트
public class JwtTest {
public static void main(String[] args) {
String token = JwtUtil.generateToken("user123", "ADMIN");
System.out.println("Generated Token: " + token);
Claims claims = JwtUtil.verifyToken(token);
System.out.println("User: " + claims.getSubject());
System.out.println("Role: " + claims.get("role"));
}
}
➡ 토큰 생성 및 검증 성공!
④ Spring Security에서 JWT 인증 적용
🔹 JWT 필터 생성
import io.jsonwebtoken.Claims;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class JwtAuthenticationFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String token = request.getHeader("Authorization");
if (token != null && token.startsWith("Bearer ")) {
token = token.substring(7); // "Bearer " 제거
try {
Claims claims = JwtUtil.verifyToken(token);
String username = claims.getSubject();
String role = (String) claims.get("role");
UserDetails userDetails = User.withUsername(username).roles(role).build();
SecurityContextHolder.getContext().setAuthentication(
new JwtAuthenticationToken(userDetails, token, userDetails.getAuthorities())
);
} catch (Exception e) {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Invalid Token");
return;
}
}
filterChain.doFilter(request, response);
}
}
🔹 Security 설정
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) // 세션 사용 안 함
.and()
.authorizeHttpRequests(auth -> auth
.antMatchers("/public/**").permitAll()
.antMatchers("/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
)
.addFilterBefore(new JwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}
🔹 정리
✅ JWT 특징
- 세션을 사용하지 않고 Stateless(무상태) 인증 가능
- 토큰을 사용하여 API를 보호할 수 있음
- 사용자 정보(Claim)를 담을 수 있어 추가적인 DB 조회 없이 인증 가능
✅ Spring Boot에서 JWT 사용 흐름
- 로그인 시 서버에서 JWT 토큰 발급
- 클라이언트가 API 요청 시 JWT 포함 (Authorization: Bearer <token>)
- 서버에서 JWT 검증 후 사용자 인증
- 인증된 사용자는 API 사용 가능
우리는 이렇게 JWT 에 대해서 알아보았고 이제 제대로 적용해서 보안에 치중한 네트워크 통신을 진행해보자 !!!!
![](https://t1.daumcdn.net/keditor/emoticon/friends1/large/007.gif)
별책부록
✅ 1. JWT 서버를 따로 두는 이유
📌 (1) 마이크로서비스 아키텍처 (MSA) 지원
- 대규모 애플리케이션에서는 **여러 개의 독립적인 서비스(마이크로서비스)**가 서로 통신해야 해.
- JWT 인증을 중앙 서버에서 처리하면 각 서비스가 별도로 인증 로직을 구현할 필요 없이 JWT 검증만 수행하면 됨.
- 예를 들어, 아래처럼 JWT 서버를 별도로 두면 서비스 간 인증이 간단해져.
🔹 예제 (JWT 인증 서버 따로 둠)
[인증 서버 (Auth Server)] → JWT 발급 ⬇️ [서비스 A] (게시판) [서비스 B] (결제) [서비스 C] (채팅)
- 서비스 A, B, C는 JWT 검증만 수행하고, JWT 자체는 직접 발급하지 않아도 됨.
- 이렇게 하면 각 서비스에서 별도의 로그인 관리가 필요 없어지고, 인증 부하도 줄어듦.
📌 (2) 보안성 강화
- JWT 자체는 서버가 발급하고 클라이언트에서 검증하는 구조라 DB 조회 없이 빠르게 처리할 수 있어.
- 하지만, Refresh Token을 다루는 경우에는 보안이 중요하므로 별도의 인증 서버에서 관리하는 것이 일반적이야.
- Access Token은 탈취되면 위험하기 때문에, 주기적으로 갱신할 필요가 있어.
- 이를 위해 Refresh Token을 관리하는 서버를 따로 두고, 이 서버가 만료된 Access Token을 재발급해 주는 방식이 많이 사용돼.
🔹 예제 (JWT 서버가 Refresh Token 관리)
1️⃣ 클라이언트 → 아이디/비번 전송 → JWT 인증 서버
2️⃣ JWT 인증 서버 → Access Token + Refresh Token 발급
3️⃣ 서비스 서버 → Access Token 검증 후 서비스 제공
4️⃣ Access Token 만료됨 → Refresh Token을 사용하여 JWT 인증 서버에서 새로운 Access Token 발급
📌 (3) JWT 발급 및 검증 부하 분산
- 대규모 서비스에서는 인증 요청이 많아지면 JWT 발급/검증이 서버 부하를 유발할 수 있어.
- 특히, Access Token을 주기적으로 갱신해야 하는 경우, 인증 서버를 따로 두면 메인 서비스의 성능 저하를 방지할 수 있어.
- 이를 위해, JWT 인증 전용 서버를 두고, 토큰 검증을 처리하는 서버를 분리하는 방식이 사용됨.
🔹 예제 (JWT 서버 부하 분산 구조)
[JWT 인증 서버] (JWT 발급, Refresh Token 관리) ⬇️ [API Gateway] (JWT 검증) ⬇️ [서비스 A, B, C]
- JWT 인증 서버는 Refresh Token을 관리하고, 새로운 Access Token을 발급함.
- API Gateway는 JWT를 검증하여 부하를 줄임.
✅ 2. JWT 서버를 따로 둘 때의 단점
1️⃣ 구현이 복잡해짐
- JWT 인증 서버를 따로 운영하면 서비스 간 인증 흐름을 잘 설계해야 함.
- 기존의 단일 서버 인증보다 구현 난이도가 높아짐.
2️⃣ 인증 서버 장애 시 모든 서비스에 영향
- JWT 검증만으로는 문제가 없지만, Refresh Token을 갱신하는 인증 서버가 다운되면 새로운 Access Token을 받을 수 없음.
- 따라서 인증 서버를 이중화(Load Balancing) 하는 것이 중요함.
3️⃣ 트래픽이 많아지면 비용 증가
- JWT 서버를 따로 두면 서버 유지 비용이 추가됨.
- 특히, 대량의 트래픽이 발생하는 경우 **고성능 인증 서버와 캐시 시스템(Redis 등)**을 사용해야 함.
✅ 3. JWT 서버를 따로 두는 대표적인 사례
🔹 (1) OAuth 2.0 기반 인증 서버
- JWT 인증 서버를 따로 두고, OAuth 2.0 방식을 활용해 토큰 발급 및 검증을 수행
- 예: Google, Facebook, Kakao 로그인
🔹 (2) 기업 내부 API Gateway + JWT 인증 서버
- 여러 개의 내부 서비스(API)를 운영하는 기업에서는 API Gateway에서 JWT를 검증하고,
인증 서버에서 JWT를 발급하는 구조를 사용함. - 예: 쿠팡, 네이버, 카카오 같은 대형 서비스
✅ 4. 결론: JWT 서버를 따로 두는 게 좋을까?
✅ 마이크로서비스(MSA) 아키텍처를 사용하면 → JWT 인증 서버를 따로 두는 것이 좋음
✅ 보안이 중요한 서비스(금융, 기업 시스템 등) → Refresh Token을 따로 관리하는 것이 좋음
✅ 규모가 작은 단일 서비스라면 → 굳이 따로 둘 필요 없이 서버에서 바로 JWT 발급 가능
🚀 즉, 서비스 규모와 보안 요구 사항에 따라 JWT 서버를 분리할지 결정하면 돼!
'Springboot' 카테고리의 다른 글
Springboot nginx wegsacket ssl 연결 통신 후 [WebSocket Error]java.net.ProtocolException: Expected HTTP 101 response but was '400 ' 에러해결법 (2) | 2024.10.14 |
---|---|
vscode_ SpringBoot 프로젝트 만들기 (0) | 2024.08.20 |
Springboot -SSE ( 로컬에서는 실시간 o / https nginx 서버에서는 실시간 x) (0) | 2024.06.23 |
springboot https 적용기 (0) | 2024.06.05 |
Springboot_ Redis SSE 작업 (0) | 2024.06.02 |