java
CHAPTER 71 / 99
읽기 약 2분
FUNCTION
JWT 인증 시스템 구축
핵심 개념
JWT 구조·생성/검증·Refresh 토큰·Spring Security 통합 — REST API 표준 인증.
본문
JWT 구조
header.payload.signature
Header: {"alg": "HS256", "typ": "JWT"}
Payload: {"sub": "user123", "exp": 1234567890, "role": "USER"}
Signature: HMAC-SHA256(base64(header) + "." + base64(payload), secret)JWT 생성/검증
// build.gradle: implementation 'io.jsonwebtoken:jjwt-api:0.12.5'
@Component
@RequiredArgsConstructor
public class JwtUtil {
private final Key secretKey;
public JwtUtil(@Value("${jwt.secret}") String secret) {
this.secretKey = Keys.hmacShaKeyFor(secret.getBytes());
}
public String generate(String username, String role) {
Date now = new Date();
Date exp = new Date(now.getTime() + 1000 * 60 * 30); // 30분
return Jwts.builder()
.setSubject(username)
.claim("role", role)
.setIssuedAt(now)
.setExpiration(exp)
.signWith(secretKey, SignatureAlgorithm.HS256)
.compact();
}
public Claims parse(String token) {
return Jwts.parserBuilder()
.setSigningKey(secretKey)
.build()
.parseClaimsJws(token)
.getBody();
}
public boolean isValid(String token) {
try {
parse(token);
return true;
} catch (JwtException e) {
return false;
}
}
}JWT 필터
@Component
@RequiredArgsConstructor
public class JwtAuthFilter extends OncePerRequestFilter {
private final JwtUtil jwtUtil;
@Override
protected void doFilterInternal(HttpServletRequest req,
HttpServletResponse res,
FilterChain chain) throws ServletException, IOException {
String header = req.getHeader("Authorization");
if (header != null && header.startsWith("Bearer ")) {
String token = header.substring(7);
if (jwtUtil.isValid(token)) {
Claims claims = jwtUtil.parse(token);
String username = claims.getSubject();
String role = claims.get("role", String.class);
var auth = new UsernamePasswordAuthenticationToken(
username,
null,
List.of(new SimpleGrantedAuthority("ROLE_" + role))
);
SecurityContextHolder.getContext().setAuthentication(auth);
}
}
chain.doFilter(req, res);
}
}Refresh 토큰
@Service
@RequiredArgsConstructor
public class AuthService {
private final JwtUtil jwtUtil;
private final RedisTemplate<String, String> redis;
public TokenPair login(String username, String password) {
// ... 비밀번호 검증
String accessToken = jwtUtil.generate(username, "USER");
String refreshToken = UUID.randomUUID().toString();
// Redis에 refresh 토큰 저장 (7일)
redis.opsForValue().set(
"refresh:" + refreshToken,
username,
Duration.ofDays(7)
);
return new TokenPair(accessToken, refreshToken);
}
public String refresh(String refreshToken) {
String username = redis.opsForValue().get("refresh:" + refreshToken);
if (username == null) {
throw new InvalidRefreshTokenException();
}
// 새 access 토큰 발급
return jwtUtil.generate(username, "USER");
}
}컨트롤러
@RestController
@RequiredArgsConstructor
public class AuthController {
private final AuthService authService;
@PostMapping("/auth/login")
public TokenPair login(@RequestBody LoginRequest req) {
return authService.login(req.username(), req.password());
}
@PostMapping("/auth/refresh")
public AccessTokenResponse refresh(@RequestBody RefreshRequest req) {
String newAccess = authService.refresh(req.refreshToken());
return new AccessTokenResponse(newAccess);
}
}다음 챕터
CH.3 "OAuth2 소셜 로그인" — Google/Kakao 연동.
AI 프롬프트
🤖 AI에게 잘 물어보는 법 — 모델·전략별 프롬프트
Claude
무료: Sonnet 4.6 / Pro $20/mo: Opus 4.6
내 Spring 코드의 JWT 인증 부분을 분석해서 토큰 만료·revoke 전략와 개선 우선순위를 알려줘.
ChatGPT
무료: GPT-5.5 / Plus $20/mo: GPT-5.5 Pro
JWT 인증 vs 다른 패턴 비교를 실전 사례 5개로 보여주고 JWT vs Session 트레이드오프를 알려줘.
Gemini
무료: 2.5 Flash / Pro $19.99/mo: 3.1 Pro
내 코드베이스 전체를 분석해서 JWT 인증 관련 토큰 검증 누락 위치를 보고해줘.
Grok
무료: Grok 4.1 / SuperGrok $30/mo
2026년 한국 기업의 JWT 인증 채택률과 한국 SaaS의 JWT 채택 패턴를 솔직히 알려줘.
⭐ 이것만 기억하세요
JWT 인증 시스템 구축은 이 3가지만 확실히 잡으세요
1.JWT는 stateless — 서버 세션 불필요, 수평 확장 쉬움
2.Refresh 토큰은 Redis에 저장 + 짧은 access 토큰으로 보안과 UX 균형
3.다음 챕터 CH.3에서 OAuth2 — 소셜 로그인 표준
공유하기
진행도 71 / 99