2026-01-16 JWT

Ckd gus·2026년 2월 2일
post-thumbnail

JWT 정리 (Spring Security + JWT 구현 흐름)

1) JWT란?

JWT(JSON Web Token)는 서명된 JSON 기반 토큰으로, 서버가 클라이언트를 식별/인가하기 위해 HTTP 헤더(주로 Authorization)에 실어 보내는 인증 방식이다.

  • 서버는 세션을 서버 메모리에 들고 있지 않아도 됨(무상태, Stateless)
  • 토큰 자체에 사용자 식별자/권한(Claim)을 담고 서명(Signature)으로 위·변조를 막는다

2) JWT 구조 (Header.Payload.Signature)

JWT는 . 으로 구분된 3개 문자열로 구성된다.

  1. Header: 토큰 타입(JWT), 서명 알고리즘(HS256/HS512 등)
  2. Payload: 사용자 식별자(sub), 권한(auth), 만료(exp) 등 Claim
  3. Signature: Header+Payload를 인코딩한 뒤 서버 비밀키로 서명한 값
    → 클라이언트가 Payload를 바꿔치기하면 서명이 깨져서 검증 실패


3) 쿠키/세션 vs JWT 비교

구분쿠키·세션 인증JWT 인증
인증 방식서버가 세션 상태 유지서버 무상태(Stateless)
인증 정보 저장서버 메모리 / DB / Redis클라이언트(토큰)
클라이언트 저장세션ID(쿠키)Access / Refresh Token
서버 확장성낮음(세션 공유 필요)높음(수평 확장 용이)

4) Access Token / Refresh Token

  • Access Token: API 접근(Authorization)에 사용, 보통 짧은 TTL
  • Refresh Token: Access Token 재발급에 사용, 보통 긴 TTL
    → Refresh는 서버 저장소(DB/Redis)에 저장하고 탈취/폐기 관리하는 게 일반적

정리:

  • Access Token = 접근
  • Refresh Token = 재발급

5) JWT 구현 시 보통 등장하는 클래스

필수급:

  • JwtAuthenticationFilter : 요청마다 토큰 추출/검증 후 SecurityContext에 인증 저장
  • JwtTokenProvider : 토큰 생성/파싱/검증 유틸
  • CustomUserDetails : Security가 이해하는 사용자 모델
  • CustomUserDetailsService : DB에서 사용자 로딩
  • SecurityConfig : FilterChain 등록/인가 규칙 설정

Refresh Token까지 하면 추가:

  • Refresh Token 저장소(DB/Redis) + 재발급 API
  • 로그인 성공 시 토큰 발급용 처리(보통 로그인 컨트롤러/서비스에서 발급하거나, 커스텀 필터로 발급)

6) 전체 요청 흐름(핵심)

  1. 클라이언트가 요청 보냄 (Authorization: Bearer {token})
  2. JwtAuthenticationFilter가 헤더에서 토큰 추출
  3. JwtTokenProvider.validateToken()으로 검증
  4. 검증 성공 시 getAuthentication()으로 Authentication 생성
  5. SecurityContextHolder에 저장
  6. 이후 컨트롤러/서비스에서는 인증된 사용자로 처리 가능

7) 코드 리뷰/정리: JwtAuthenticationFilter

역할

  • Authorization 헤더에서 Bearer 토큰을 꺼냄
  • 유효하면 Authentication을 만들어 SecurityContext에 넣음
  • 만료/위조 시 보통 401 또는 익명 처리(프로젝트 정책에 따라)

예제 (네 코드 구조 유지 + 핵심 포인트 주석)

@RequiredArgsConstructor
public class JwtAuthenticationFilter extends OncePerRequestFilter {

    private final JwtTokenProvider jwtTokenProvider;

    public static final String AUTHORIZATION_HEADER = "Authorization";
    public static final String BEARER_PREFIX = "Bearer ";

    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain filterChain)
            throws ServletException, IOException {

        String token = resolveToken(request);

        try {
            if (StringUtils.hasText(token) && jwtTokenProvider.validateToken(token)) {
                Authentication authentication = jwtTokenProvider.getAuthentication(token);
                SecurityContextHolder.getContext().setAuthentication(authentication);
            }
        } catch (ExpiredJwtException e) {
            // 만료 토큰 정책: 여기서 401로 끊어버릴지, 그냥 익명으로 넘길지 팀 정책으로 고정
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            return;
        }

        filterChain.doFilter(request, response);
    }

    private String resolveToken(HttpServletRequest request) {
        String bearerToken = request.getHeader(AUTHORIZATION_HEADER);
        if (StringUtils.hasText(bearerToken) && bearerToken.startsWith(BEARER_PREFIX)) {
            return bearerToken.substring(BEARER_PREFIX.length()); // 7
        }
        return null;
    }
}

다음에는 Refresh토큰도 설정하는법을 알아보겠다.

profile
백엔드 공부중입니다.

0개의 댓글