
JWT와 Token을 사용한 로그인 방식은 서버에 세션을 저장하지 않고 클라이언트측에서 인증 정보를 저장하는 방식이다.
- 주로 스케일링, 분산 시스템에서 많이 사용된다.
- 서버에 Session을 저장하지 않는다 → 여러 서버 간에 인증 상태를 공유하는 데 유리하다.
JWT (JSON Web Token)
헤더(Header), 페이로드(Payload), 서명(Signature)으로 구성된 자체 포함된 인증 토큰이다.
헤더(Header)
JWT 타입과 서명에 사용된 알고리즘을 지정한다.
{
"alg": "HS256", // 알고리즘 (HMAC SHA-256)
"typ": "JWT"
}
페이로드(Payload)
- 사용자 정보, 권한, 토큰의 만료 시간 등 클레임이 포함된다.
- 등록된 클레임: iss(발행자), exp(만료 시간), sub(주체), aud(대상)
- 사용자 정의 클레임: 사용자 정보 등 자유롭게 추가 가능
{
"sub": "1234567890",
"name": "John Doe",
"iat": 1516239022
}
서명(Signature)
- 헤더와 페이로드를 비밀 키를 사용하여 서명한다.
- 해당 서명을 통해 토큰이 변조되지 않았음을 보장한다.
// 서명 알고리즘 > HMACSHA256을 사용해 서명
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
헤더, 페이로드, 서명을 Base64Url로 인코딩하여 결합하면 JWT 토큰이 생성된다.
Header.Payload.Signature
/*예시*/
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
- 헤더: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
- 페이로드: eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ
- 서명: SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
JWT 기반 로그인
1) 사용자 로그인 시
- 사용자 인증: 클라이언트는 로그인 폼에 ID와 Password 를 입력 → 서버로 전송한다.
- 서버 검증: 서버는 사용자 정보를 DB에서 조회 → Password가 일치하면 JWT 토큰을 생성한다.
- JWT 토큰 발급: 서버는 생성한 JWT를 클라이언트에게 반환한다.
- JWT 토큰은 만료 시간을 포함할 수 있다.
- 만료된 토큰은 더 이상 유효하지 않게 되기 때문에 사용할 수 없어 보안을 강화할 수 있다.
.setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60)) // 1시간 후 만료
- 클라이언트 저장: 클라이언트는 받은 JWT를 로컬 저장소 혹은 쿠키(Cookie)에 저장한다.
Cookie cookie = new Cookie("JWT", jwtToken);
cookie.setHttpOnly(true); // JS에서 접근 불가능
cookie.setSecure(true); // HTTPS에서만 전송
cookie.setPath("/");
response.addCookie(cookie);
2) 사용자 인증 요청 시
- 클라이언트가 API 요청 보낼 때 JWT를 Authorization 헤더에 포함시켜 보낸다.
Authorization: Bearer <JWT>
- 요청을 받은 서버는 JWT를 검증한다 → JWT가 유효한지, 만료되었는지 확인 진행
- 토큰이 유효할 경우 → 요청 처리
- 토큰이 유효하지 않을 경우 → 인증 오류 반환
Spring Security - JWT 구현
1. JWT 토큰 생성
public class JwtTokenUtil {
private String secretKey = "mySecretKey"; // 비밀 키 (예시)
// JWT 생성
public String generateToken(String username) {
return Jwts.builder()
.setSubject(username) // 토큰의 주체(사용자)
.setIssuedAt(new Date()) // 발행 시간
.setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60)) // 만료 시간 1시간
.signWith(SignatureAlgorithm.HS256, secretKey) // 서명 (HMAC SHA-256)
.compact();
}
}
2. JWT 필터 작성 (Authorization 헤더 검증)
Spring Security에서 JWT를 검증하는 필터 작성
→ 모든 요청에 대한 Authorization 헤더를 검사할 수 있다.
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final String secretKey = "mySecretKey"; // 비밀 키 (예시)
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String token = request.getHeader("Authorization");
if (token != null && token.startsWith("Bearer ")) {
try {
token = token.substring(7); // 'Bearer ' 제외한 부분
Claims claims = Jwts.parser()
.setSigningKey(secretKey)
.parseClaimsJws(token)
.getBody();
String username = claims.getSubject(); // 사용자 정보
// 인증 성공 시 SecurityContext에 저장
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(username, null, new ArrayList<>());
SecurityContextHolder.getContext().setAuthentication(authentication); // Spring Security에서 인증된 사용자를 상태로 저장하는 과정
} catch (Exception e) {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Invalid Token");
return;
}
}
filterChain.doFilter(request, response);
}
}
- 3. Security 설정에 필터 추가
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private JwtAuthenticationFilter jwtAuthenticationFilter;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
}
}
JWT 장점 ( = 사용 이유)
1) Stateless / 상태 비저장 (가장 큰 특징!)
서버는 세션을 저장하지 않고, 클라이언트가 보내는 토큰에 모든 인증 정보를 담는다
- Stateless의 특징은 각 서버가 독립적으로 요청을 처리할 수 있게 만들어, 서버 간 세션 동기화 문제를 해결해준다
이로 인해 서버의 메모리 사용을 줄일 수 있다.
2) 분산 환경에서의 유리함
여러 서버가 있을 경우
- 세션의 경우 공유하려면 세션 동기화가 필요하다.
- JWT의 경우 자체적으로 정보를 포함하기 때문에 별도의 세션 동기화가 필요하지 않다.
- 즉, 여러 서버가 각각 JWT를 검증하기만 하면 되므로 서버 간 독립적인 인증 처리가 가능하다.
3) 확장성
세션 기반 인증 방식은 세선 정보 공유가 필요하기 때문에 서버가 많아질 수록 동작 과정이 복잡해진다
그에 반해 JWT는 모든 서버가 독립적으로 동작할 수 있기 때문에 확장성이 뛰어나다.
세션 기반 인증 방식과 차이점
세션 기반 인증: 클라이언트의 인증 상태를 서버 측에서 관리해야 한다.
- 클라이언트가 로그인 > 서버는 세션을 생성 + 생성한 세션을 서버 메모리 or DB에 저장
- 모든 서버가 세션 데이터를 공유해야하기 때문에 세션 동기화 필요 > 세션 정보를 공통으로 저장할 수 있는 공유 저장소(DB,Radis 등)가 필요하다.
JWT: 인증 정보를 클라이언트 측에서 저장한다.
- 클라이언트가 서버에 요청을 보낼 때 마다 JWT 토큰을 포함해서 요청을 보내고, 서버는 JWT를 검증한다.
- JWT는 자체적으로 필요한 모든 정보를 포함 + 서명된 토큰을 사용 > 서버는 해당 토큰을 검증한다.
- 서버는 세션 정보를 관리하지 않기 때문에 -> 각 서버는 독립적으로 동작한다 할 수 있다.
정리 - 세션 vs JWT
세션
- 서버가 세션을 관리하므로, 여러 서버가 세션 공유를 위해 세션 동기화가 필요하다.
- 이를 위해 **공유된 세션 저장소 (예: Redis)**가 필요하다.
JWT
- JWT는 토큰 자체에 모든 정보 (예: 사용자 인증 정보, 권한 등)를 포함하고 있어서
→ 서버는 세션 정보를 별도로 관리할 필요가 없다. - 요청이 들어오면 서버는 JWT 토큰을 검증만 하면 되고, 어떤 서버에서든 요청을 처리할 수 있다.
이처럼 JWT는 세션을 관리하지 않기 때문에 서버 간의 세션 동기화가 필요가 없다.
이 말은 즉,
- JWT는 서버가 상태를 저장하지 않는 상태 비저장 특성으로 서버간 의존성이 줄어들어서
- 향후 필요 시 서버를 추가하는 등의 서버 확장이 용이하다.
그리고 각 서버가 독립적으로 토큰을 통해 인증 처리가 가능하기 때문에
MSA(마이크로서비스 아키텍쳐)에서 매우 유용하다.
- 마이크로 서비스가 독립적으로 동작하며, 서로 다른 서버가 인증을 처리할 수 있다.
- 즉, 한 서버에서 인증을 받은 후 JWT를 다른 서비스에 전파 > 서비스 간 인증 상태를 공유할 수 있다.
- 예) 네이버 로그인 시 네이버 플레이, 치지직, 네이버 스포츠 등 다른 서버이지만 로그인이 유지되어 사용할 수 있다.
4. 보안
- JWT는 서명을 통해 변조를 장비할 수 있다.
- 만료 시간 설정을 통해 자동으로 만려되도록 할 수 있다.
- 비밀 키를 안전하게만 관리할 수 있다면 매우 안전한 인증 방법이 JWT이다.
JWT 단점
토큰 탈취 시 위험하다
- 당연한 얘기겠지만, 토큰이 탈취될 경우 누구나 탈취한 토큰을 사용해서 인증된 사용자의 권한을 이용할 수 있다.
- 이러한 탈취를 막기위해서는 HTTPS를 사용하는 것이 필수적이다.
토큰 만료
- 토큰 만료가 되기전에 갱신할 방법을 고민할 필요가 있다.
- 일반적으로는 토큰 만료를 막기 위해 Refresh Token을 함께 사용하는 것이 일반적인 방법이다.
정리
JWT는 Session 기반 인증에 비해 서버 부담이 적고, 스케일링(확장성)에 유리한 인증 방식이다.
- Spring Security와 JWT 연동을 한다면 인증을 쉽고 안전하게 처리할 수 있다.
- 또한 클라이언트-서버 간 인증 상태를 유연하게 유지할 수 있다.
'Dev > Spring' 카테고리의 다른 글
[Spring] HTTP 201 CREATED 상태 코드와 Location 헤더 (0) | 2025.06.16 |
---|---|
[Spring] GET Method에 RequestBody 요청 적합한가? (0) | 2025.06.16 |
[Spring] Session & Cookie (로그인 인증) (0) | 2025.05.26 |
[Spring] Servlet Filter (0) | 2025.05.22 |
[Spring] 연관 관계 매핑 (0) | 2025.05.19 |