JWT는 기본적으로 Stateless 인증 방식이다.
즉,
문제는 로그아웃이다.
사용자가 로그아웃해도:
그래서 “즉시 무효화”를 위해 블랙리스트를 도입했다.
@Entity
@Table(name = "access_token_blacklist")
public class AccessTokenBlacklist {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, unique = true)
private String jti;
@Column(nullable = false)
private LocalDateTime expiresAt;
@Column(nullable = false)
private LocalDateTime createdAt;
}
왜 jti만 저장했을까?
요청이 들어올 때:
1. 토큰 유효성 검증
2. refresh 토큰이면 인증 처리 안 함
3. access 토큰이면 jti 추출
4. 블랙리스트에 존재하면 401 반환
String jti = jwtProvider.getClaims(token).getId();
if (blacklistService.isBlacklisted(jti)) {
sendErrorResponse(response, 401, "로그아웃된 토큰입니다");
return;
}
이 로직이 핵심.
로그아웃 시:
1. Access Token에서 jti 추출
2. 만료 시간 추출
3. 블랙리스트 테이블에 저장
4. Member의 refresh 제거
Claims claims = jwtProvider.getClaims(access);
blacklistService.blacklist(
claims.getId(),
toLocalDateTime(claims.getExpiration())
);
이제 로그아웃하면 즉시 access 사용 불가.
Member 엔티티에 추가한 필드:
private String refreshToken;
private String refreshJti;
private LocalDateTime refreshExpiresAt;
refresh 성공 시:
-> 멀티 디바이스를 허용하지 않기 때문에
이 방식이 블랙리스트 역할을 자연스럽게 해준다.
이번 작업을 하면서:
를 정확히 이해하게 되었다.