JWT Access Token 블랙리스트 적용

E.NO·2026년 2월 12일

1. 왜 블랙리스트가 필요했을까?

JWT는 기본적으로 Stateless 인증 방식이다.
즉,

  • 서버는 토큰을 저장하지 않는다.
  • 토큰이 만료되기 전까지는 항상 유효하다.

문제는 로그아웃이다.

사용자가 로그아웃해도:

  • 이미 발급된 Access Token은
  • 만료 전까지 계속 사용 가능하다.

그래서 “즉시 무효화”를 위해 블랙리스트를 도입했다.


2. 설계 방향

Access Token만 블랙리스트 적용

  • Refresh Token은 DB에 직접 저장하고 관리
  • Access Token은 서버에 저장하지 않기 때문에
    -> jti 기반 블랙리스트로 관리

멀티 디바이스 허용 X

  • 한 계정당 refresh 1개만 유지
  • Member 엔티티에 refresh 저장

3. AccessTokenBlacklist 엔티티 설계

@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만 저장했을까?

  • 토큰 전체를 저장할 필요 없음
  • 블랙리스트는 "이 토큰은 무효다"라는 식별만 필요
  • jti는 토큰 고유 식별자
  • 인덱스도 가볍고 조회도 빠름

4. JwtAuthenticationFilter 수정

요청이 들어올 때:
1. 토큰 유효성 검증
2. refresh 토큰이면 인증 처리 안 함
3. access 토큰이면 jti 추출
4. 블랙리스트에 존재하면 401 반환

String jti = jwtProvider.getClaims(token).getId();

if (blacklistService.isBlacklisted(jti)) {
    sendErrorResponse(response, 401, "로그아웃된 토큰입니다");
    return;
}

이 로직이 핵심.


5. logout 로직 수정

로그아웃 시:
1. Access Token에서 jti 추출
2. 만료 시간 추출
3. 블랙리스트 테이블에 저장
4. Member의 refresh 제거

Claims claims = jwtProvider.getClaims(access);

blacklistService.blacklist(
        claims.getId(),
        toLocalDateTime(claims.getExpiration())
);

이제 로그아웃하면 즉시 access 사용 불가.


6. refresh 구조 정리

Member 엔티티에 추가한 필드:

private String refreshToken;
private String refreshJti;
private LocalDateTime refreshExpiresAt;

refresh 성공 시:

  • 기존 refresh 폐기
  • 새 refresh 발급
  • DB 값 교체 (rotate)

-> 멀티 디바이스를 허용하지 않기 때문에
이 방식이 블랙리스트 역할을 자연스럽게 해준다.


7. 느낀 점

  • JWT는 Stateless라서 편하지만
  • 로그아웃, 강제 만료, 탈취 대응 같은 “상태 관리”가 필요하면 결국 서버에 최소한의 정보는 저장해야 한다.

이번 작업을 하면서:

  • jti의 역할
  • Access vs Refresh 책임 분리
  • 블랙리스트 설계의 의미

를 정확히 이해하게 되었다.


오늘 배운 핵심

  • JWT는 기본적으로 취소 불가능하다.
  • 로그아웃 즉시 무효화를 위해 jti 기반 블랙리스트를 도입한다.
  • 멀티 디바이스 미허용이면 refresh는 Member에 하나만 유지하면 충분하다.

0개의 댓글