언어 및 프레임워크/Spring Boot
Spring Boot 시리즈 9편 – 로그인 및 인증 처리 전략: JWT 기반 인증 흐름과 보안 설계
B컷개발자
2025. 4. 24. 14:00
728x90
Spring Boot에서 JWT 기반 인증을 구현하는 방법을 설명합니다. 토큰 발급, 검증, Spring Security 연동, 보안 설계 전략까지 포함된 실전 인증 처리 가이드입니다.
Spring Boot 시리즈 9편 – 로그인 및 인증 처리 전략: JWT 기반 인증 흐름과 보안 설계
웹 애플리케이션이 성장하면서 로그인 처리 또한 단순 세션 기반에서 **토큰 기반 인증(JWT)**으로 빠르게 전환되고 있습니다.
이번 글에서는 Spring Boot 환경에서 JWT를 이용한 인증 흐름을 어떻게 설계하고 구현할 수 있는지,
그리고 Spring Security와 어떻게 통합하며 보안적으로 주의해야 할 점은 무엇인지 실무 중심으로 정리해보겠습니다.
📌 1. 왜 JWT인가?
✅ JWT(Json Web Token)의 특징
- 서버 상태를 저장하지 않는 Stateless 인증 구조
- 클라이언트가 토큰만 있으면 어디서나 인증 가능
- 로그인 이후 API 호출마다 Authorization 헤더만으로 인증 수행
✅ 전통적인 세션 방식과 비교
항목 세션 기반 JWT 기반
상태 저장 | 서버에 세션 저장 | 상태 없음 (Stateless) |
확장성 | 서버 수 늘릴수록 복잡 | 확장 용이 (Load Balancer 친화적) |
토큰 보관 위치 | 서버 | 클라이언트 (보통 LocalStorage or HttpOnly Cookie) |
🛠️ 2. JWT 인증 흐름 설계
[1] 로그인 요청 (ID/PW)
↓
[2] 인증 성공 → JWT 발급
↓
[3] 클라이언트가 토큰 저장
↓
[4] API 요청 시 Authorization: Bearer {토큰}
↓
[5] JWT 유효성 검사 → 인증된 사용자로 요청 처리
✅ 3. JWT 발급 및 검증 구현
1️⃣ JWT Utility 클래스
@Component
public class JwtTokenProvider {
private final String secretKey = "my-secret-key"; // 환경 변수로 분리 권장
private final long validityInMs = 3600000; // 1시간
public String createToken(String userId, String role) {
Claims claims = Jwts.claims().setSubject(userId);
claims.put("role", role);
Date now = new Date();
Date expiry = new Date(now.getTime() + validityInMs);
return Jwts.builder()
.setClaims(claims)
.setIssuedAt(now)
.setExpiration(expiry)
.signWith(SignatureAlgorithm.HS256, secretKey.getBytes())
.compact();
}
public boolean validateToken(String token) {
try {
Jws<Claims> claims = Jwts.parser()
.setSigningKey(secretKey.getBytes())
.parseClaimsJws(token);
return !claims.getBody().getExpiration().before(new Date());
} catch (Exception e) {
return false;
}
}
public String getUserId(String token) {
return Jwts.parser()
.setSigningKey(secretKey.getBytes())
.parseClaimsJws(token)
.getBody()
.getSubject();
}
}
🔐 4. Spring Security와 통합 – 필터 구성
1️⃣ SecurityConfig 설정
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {
private final JwtTokenProvider jwtTokenProvider;
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.csrf().disable()
.httpBasic().disable()
.authorizeHttpRequests()
.requestMatchers("/api/auth/**").permitAll()
.anyRequest().authenticated()
.and()
.addFilterBefore(new JwtAuthenticationFilter(jwtTokenProvider), UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}
2️⃣ JwtAuthenticationFilter
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final JwtTokenProvider tokenProvider;
public JwtAuthenticationFilter(JwtTokenProvider provider) {
this.tokenProvider = provider;
}
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain) throws ServletException, IOException {
String token = resolveToken(request);
if (token != null && tokenProvider.validateToken(token)) {
String userId = tokenProvider.getUserId(token);
UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(userId, "", List.of());
SecurityContextHolder.getContext().setAuthentication(auth);
}
chain.doFilter(request, response);
}
private String resolveToken(HttpServletRequest request) {
String bearer = request.getHeader("Authorization");
return (bearer != null && bearer.startsWith("Bearer ")) ? bearer.substring(7) : null;
}
}
🧠 실무 적용 시 고려 사항
항목 전략
비밀키 관리 | application.yml + 환경변수로 분리 |
토큰 만료 시간 | 액세스 토큰 15~60분, 리프레시 토큰은 7일 이상 |
사용자 권한 정보 | role/permission claim으로 포함 가능 |
리프레시 토큰 | 별도 DB에 저장, Access 토큰 갱신용으로 사용 |
토큰 폐기 처리 | 블랙리스트 저장소(Redis 등) 사용 |
📦 로그인 요청/응답 구조 예시
🔹 로그인 요청
{
"email": "user@example.com",
"password": "1234"
}
🔹 로그인 성공 응답
{
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6Ikp...",
"expiresIn": 3600
}
이후 API 호출 시 Authorization: Bearer {accessToken} 헤더를 사용
✅ 마무리 요약
항목 핵심 요약
인증 방식 | Stateless 구조의 JWT 기반 인증 |
핵심 구성 | JwtTokenProvider, JwtAuthenticationFilter, Spring Security |
토큰 구조 | Subject(사용자 ID), Role, Expiration 포함 |
보안 전략 | 토큰 유효성 검사, 리프레시 토큰 분리, 예외 처리 통일 |
테스트 | 로그인 → 토큰 발급 → 인증이 필요한 API 호출로 흐름 검증 |
📌 다음 편 예고
Spring Boot 시리즈 10편: Spring Security로 인가 처리 확장 – 권한 기반 API 보호 전략
728x90