1. Spring Security vs. JWT Security 차이점
Spring Security와 JWT Security는 서로 대체 관계가 아니라,
JWT가 Spring Security의 인증(Authentication) 방식 중 하나로 사용될 수 있다.
목적 | 애플리케이션 전반적인 인증(Authentication)과 권한(Authorization) 관리 | JWT(JSON Web Token) 기반의 인증 방식 |
기본 인증 방식 | 세션 기반 인증 (Session, Cookie, HTTP Basic, OAuth 등) | 토큰 기반 인증 (Bearer Token 사용) |
서버 상태 | 세션 저장소 필요 (Stateful) | 세션 없이 동작 (Stateless) |
사용 사례 | 내부 서비스, 관리자 페이지, 전통적인 웹 애플리케이션 | REST API, 모바일 앱, 마이크로서비스 |
보안 관리 | 세션/쿠키 기반 관리 | JWT 서명(Signature) 검증 |
2. 토큰(Token)이란?
- 사용자의 인증 정보를 포함하는 문자열로, 주로 JSON Web Token(JWT) 형태로 사용됨.
- 클라이언트가 로그인 후 토큰을 받아서 요청마다 포함해서 전송.
- 서버는 토큰을 검증하는 것만으로 인증이 가능하며, 세션을 저장할 필요가 없음 (stateless)
3. JWT Security 개념 및 동작 방식
🔹 JWT는 3가지 부분으로 구성됨 -> Header.Payload.Signature
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9. // Header (알고리즘, 토큰 타입)
eyJ1c2VySWQiOiIxMjM0NTYiLCJyb2xlIjoiVVNFUiJ9. // Payload (사용자 정보)
sfsdfIjd83jFksjf83JSdfLdsf9f8SDfdf // Signature (서명)
- Header → 어떤 알고리즘을 사용할지 (alg: HS256 등)
- Payload → 사용자 ID, 권한 정보 등 (Base64로 인코딩됨)
- Signature → 변조 방지를 위한 서명 (서버의 비밀 키로 서명됨)
🔹 JWT 인증 과정
- 로그인 요청
- 사용자가 ID/PW 입력 후 로그인 요청 (POST /login)
- JWT 토큰 발급
- 서버가 ID/PW 검증 후 JWT 토큰을 생성하여 클라이언트에 반환
- 클라이언트 저장
- 클라이언트(웹, 모바일)는 토큰을 로컬 스토리지, 세션 스토리지 또는 쿠키에 저장
- 요청 시 토큰 포함
- 이후 API 요청을 보낼 때, Authorization 헤더에 토큰을 포함
- Authorization: Bearer <JWT_TOKEN>
- 서버 검증 및 처리
- 서버는 토큰의 서명을 검증하고, 유저 정보를 확인한 후 요청을 처리
1. JWT 생성 (JwtUtil = JwtProvider)
2. JWT 필터 (JwtAuthenticationFilter)
3. Spring Security 설정 (SecurityConfig)
3-1. JwtProvider란?
JwtProvider(혹은 JwtUtil, JwtTokenProvider)는 JWT 토큰을 생성하고, 검증하는 역할을 하는 클래스이다.
(Spring Security에서 JWT 기반 인증을 사용할 때 필수적인 유틸리티 클래스)
🔹 JwtProvider의 주요 역할
- JWT 토큰 생성
- 사용자가 로그인하면, 사용자 정보를 포함한 JWT 액세스 토큰을 생성
- 보통 만료 시간을 설정하고, 비밀 키(secret key)로 서명함
- JWT 토큰 검증
- 클라이언트가 보낸 토큰이 유효한지 검증
- 변조 여부를 확인하고, 서명 검증을 수행
- 만료된 토큰인지 확인
- JWT에서 사용자 정보 추출
- 토큰에서 사용자 ID, 권한(role) 같은 정보를 가져와서 인증 객체를 생성.
✔JwtProvider 예제 코드
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.stereotype.Component;
import java.util.Date;
@Component
public class JwtProvider {
private final String SECRET_KEY = "mysecretkey"; // 🔹 비밀 키 (보안 강화를 위해 .env로 관리 권장)
private final long EXPIRATION_TIME = 1000 * 60 * 60; // 🔹 1시간 (토큰 유효 시간)
// ✅ 1. JWT 토큰 생성
public String generateToken(String username) {
return Jwts.builder()
.setSubject(username) // 사용자 이름 (주로 userId 사용)
.setIssuedAt(new Date()) // 발급 시간
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME)) // 만료 시간
.signWith(SignatureAlgorithm.HS256, SECRET_KEY) // 서명
.compact();
}
// ✅ 2. JWT 토큰 검증
public boolean validateToken(String token) {
try {
Jwts.parser()
.setSigningKey(SECRET_KEY)
.parseClaimsJws(token); // 서명 검증
return true;
} catch (Exception e) {
return false; // 유효하지 않은 토큰
}
}
// ✅ 3. 토큰에서 사용자 이름(username) 추출
public String getUsernameFromToken(String token) {
Claims claims = Jwts.parser()
.setSigningKey(SECRET_KEY)
.parseClaimsJws(token)
.getBody();
return claims.getSubject(); // setSubject()에 저장한 값 가져오기
}
}
- 로그인 과정
- 클라이언트가 ID/PW로 로그인 요청
- 서버가 사용자 인증 후 JWT 액세스 토큰을 생성 (JwtProvider.generateToken())하여 클라이언트에게 전달
- 클라이언트는 이후 API 요청마다 Authorization: Bearer <JWT> 헤더에 토큰을 포함해서 보냄
- JWT 검증 과정
- 클라이언트가 요청을 보낼 때 JWT를 헤더에 포함
- JwtAuthenticationFilter가 토큰을 추출하여 JwtProvider.validateToken()으로 검증
- 유효한 토큰이면 JwtProvider.getUsernameFromToken()으로 사용자 정보를 가져옴
- UserDetailsService를 통해 사용자 정보를 로드하고 인증을 완료
3-2. JwtProvider와 JwtAuthenticationFilter
Spring Security에서 JWT를 사용할 때, JwtAuthenticationFilter가 요청을 가로채서 인증을 수행하는데,
그 내부에서 JwtProvider를 이용해 토큰을 검증하고 사용자 정보를 가져오는 것이 핵심 흐름이다.
✔ JwtAuthenticationFilter 예제
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final JwtProvider jwtProvider;
private final UserDetailsService userDetailsService;
public JwtAuthenticationFilter(JwtProvider jwtProvider, UserDetailsService userDetailsService) {
this.jwtProvider = jwtProvider;
this.userDetailsService = userDetailsService;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
String token = getTokenFromRequest(request);
if (token != null && jwtProvider.validateToken(token)) { // ✅ 토큰 검증
String username = jwtProvider.getUsernameFromToken(token); // ✅ 토큰에서 사용자 정보 가져오기
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
UsernamePasswordAuthenticationToken auth =
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(auth); // ✅ 인증 완료
}
chain.doFilter(request, response); // 다음 필터로 이동
}
private String getTokenFromRequest(HttpServletRequest request) {
String bearerToken = request.getHeader("Authorization");
if (bearerToken != null && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7); // "Bearer " 제거 후 토큰만 추출
}
return null;
}
}
4. 전체 Spring Security + JWT 인증 흐름
1️⃣ 로그인 요청 (UsernamePasswordAuthenticationFilter)
- 사용자가 ID/PW로 로그인 요청을 보냄. (POST /login)
- UsernamePasswordAuthenticationFilter가 요청을 가로채고, UserDetailsService를 이용해 사용자 인증 수행.
- 인증이 성공하면 JwtProvider.generateToken()을 호출해 JWT 액세스 토큰을 생성.
- 생성된 토큰을 클라이언트에게 응답으로 반환. (헤더 또는 JSON 응답)
2️⃣ JWT 기반 인증 (JwtAuthenticationFilter)
- 클라이언트는 이후 요청마다 Authorization 헤더에 JWT 포함하여 보냄.
Authorization: Bearer <JWT>
- JwtAuthenticationFilter가 요청을 가로채고, 헤더에서 JWT를 추출.
- JwtProvider.validateToken()을 호출하여 토큰의 유효성을 검증.
- JwtProvider.getUsernameFromToken()을 호출하여 사용자 정보(ID)를 추출.
- UserDetailsService.loadUserByUsername()을 호출하여 사용자 정보 조회.
- 인증이 완료되면 SecurityContextHolder에 인증 정보를 저장하고, 해당 요청을 계속 진행하도록 허용.
요약
1️⃣ 로그인 요청 → UsernamePasswordAuthenticationFilter → 인증 성공 → JwtProvider로 JWT 생성
2️⃣ 클라이언트가 JWT 포함하여 요청 → JwtAuthenticationFilter가 검증 → 인증된 요청 처리
'Spring' 카테고리의 다른 글
Ports and Adapter 패턴 (Hexagonal) (0) | 2025.02.17 |
---|---|
@RequestPart 알아보기 (0) | 2025.02.12 |
열어봐요 @RequestBody로 데이터 받는 과정 (0) | 2025.02.10 |
@RequestBody 와 <form> 태그 (0) | 2025.02.05 |
Spring Pageable 안 쓰면 바보 (1) | 2025.01.31 |