# JWT 인증 패턴 ## 언제 쓰는가 - SPA + API 구조에서 토큰 기반 인증이 필요할 때 - 로그아웃 시 토큰 무효화가 필요할 때 ## 핵심 구조 - HS256 서명 (Secret Key 환경변수 주입) - userId / email / role 클레임 - 블랙리스트 기반 로그아웃 - 스케줄러로 만료 토큰 정리 ## 코드 예시 ### 토큰 생성 ```java public String generateToken(UUID userId, String email, String role) { return Jwts.builder() .setSubject(userId.toString()) .claim("email", email) .claim("role", role) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() + expiration)) .signWith(Keys.hmacShaKeyFor(secret.getBytes(UTF_8))) .compact(); } ``` ### 토큰 검증 + 블랙리스트 ```java private final ConcurrentHashMap blacklist = new ConcurrentHashMap<>(); public boolean validateToken(String token) { if (blacklist.containsKey(token)) return false; Jwts.parserBuilder().setSigningKey(getSigningKey()).build().parseClaimsJws(token); return true; } public void blacklistToken(String token) { long exp = getExpirationFromToken(token).getTime(); blacklist.put(token, exp); } @Scheduled(fixedRate = 3600000) public void cleanup() { long now = System.currentTimeMillis(); blacklist.entrySet().removeIf(e -> e.getValue() < now); } ``` ### 필터 (요청에서 토큰 추출) ```java String token = request.getHeader("Authorization"); if (token != null && token.startsWith("Bearer ")) { token = token.substring(7); if (jwtProvider.validateToken(token)) { String userId = jwtProvider.getUserIdFromToken(token); var auth = new UsernamePasswordAuthenticationToken(userId, null, authorities); SecurityContextHolder.getContext().setAuthentication(auth); } } ``` ## 주의사항 - JWT Secret은 반드시 환경변수로 주입 (하드코딩 금지) - 블랙리스트는 인메모리 → 서버 재시작 시 초기화 (멀티 인스턴스면 Redis 전환) - localStorage 저장은 XSS 취약 — HttpOnly 쿠키가 더 안전 - 만료 시간은 용도에 맞게 (웹 24h, 모바일 7~30d) ## 재사용 방법 - JwtTokenProvider + JwtFilter 복사 - `application.yml`에 `jwt.secret`, `jwt.expiration` 추가 - SecurityConfig에 `addFilterBefore(jwtFilter, ...)` 등록