JWT 인증 패턴
백엔드JWT 인증 패턴 — 실전 적용 구조와 코드 예시
언제 쓰나 · SPA + API 구조에서 토큰 기반 인증이 필요할 때 · 로그아웃 시 토큰 무효화가 필요할 때
#backend#JWT
# 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<String, Long> 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, ...)` 등록jt · v1 · CC0-1.0 · 복사 0