(시큐어코딩)Java에서 Spring Security 기반의 JWT 통합 방법
토큰 인증이 필수가 된 시대, 시큐어 코딩의 핵심은 'JWT 전략 통합'에 달려 있습니다. Spring Security와 함께 안전한 인증 시스템을 구축하는 법을 알려드립니다.
안녕하세요. 보안 중심의 Spring Boot 프로젝트를 준비하고 적용하는 사례가 늘어나고 있는 상황에 맞추어,
이번 포스트에서는 실무 환경에서 실제로 사용되는 Spring Security + JWT 인증 구조 통합 전략을 상세히 소개해드립니다. 단순한 토큰 발급을 넘어서, 보안 취약점을 고려한 설계 및 유지보수까지 고려한 구조 설계를 중심으로 풀어갑니다. 실제 프로젝트에 바로 적용 가능한 예제와 함께 진행하니, 끝까지 읽어보시면 실질적인 도움이 되실 거예요.
📌 목차
1. 왜 JWT인가? 인증 전략의 변화
기존의 세션(Session) 기반 인증 방식은 사용자 상태를 서버에 저장하고, 요청 시마다 이를 비교하여 인증하는 구조입니다. 하지만 이 방식은 서버 확장성에 제약이 있고, 클러스터링 환경에서는 세션 공유 문제도 발생합니다. 이러한 문제를 해결하고자 등장한 방식이 토큰 기반 인증입니다. 특히 JWT는 클라이언트가 자체적으로 토큰을 저장하고 전송하기 때문에 서버는 사용자 상태를 저장하지 않아도 되며, 이를 통해 무상태(stateless) 아키텍처를 구축할 수 있습니다.
또한, JWT는 JSON 형식으로 구성되어 있어 다양한 정보(Claims)를 담을 수 있고, 자체 서명(Signature)을 통해 무결성 검증이 가능해 보안성과 유연성을 동시에 확보할 수 있습니다. 이러한 장점 덕분에 RESTful API 기반의 백엔드 시스템에 매우 적합한 인증 방식으로 자리잡았습니다.
2. Spring Security와 JWT 인증 흐름
Spring Security는 인증(Authentication)과 권한(Authorization)을 필터 체인 기반으로 처리합니다. JWT를 사용할 경우, 다음과 같은 흐름으로 요청을 처리하게 됩니다:
| 단계 | 설명 |
|---|---|
| 1. 로그인 요청 | 사용자가 ID/PW를 통해 로그인하면 서버는 JWT를 발급합니다. |
| 2. 토큰 저장 및 요청 | 클라이언트는 토큰을 Authorization 헤더에 담아 이후 요청에 포함시킵니다. |
| 3. JWT 검증 | 서버는 JWT의 서명을 검증하고, 클레임 정보를 통해 인증 처리합니다. |
※ 세션이 존재하지 않기 때문에, 서버 간 상태 공유가 필요 없는 구조입니다.
3. 시큐어코딩 핵심 체크포인트
JWT 인증을 사용할 때 반드시 고려해야 할 보안 항목들을 아래와 같이 정리했습니다. 이 목록은 OWASP 가이드라인 및 다수의 실무 프로젝트에서 반복 검증된 항목입니다:
- 서명(Signature) 검증 로직은 필수이며, 잘못된 토큰을 거부해야 합니다.
- 만료 시간(Expiration)을 적절히 설정하여 무한 인증을 방지해야 합니다.
- HTTPS 환경에서만 토큰을 주고받도록 설정합니다.
- 토큰 저장은 localStorage 대신 httpOnly 쿠키 또는 secure 스토리지로 대체합니다.
- 클레임(Claims)에는 최소한의 정보만 포함하도록 설계합니다.
※ 위 항목은 JWT 인증 시스템에서의 보안 수준을 한층 끌어올리는 필수 체크리스트입니다.
4. 핵심 코드로 구현 전략 파악하기
JWT 인증을 구현할 때 가장 중요한 요소는 TokenProvider, JWTFilter, 그리고 SecurityConfig 클래스입니다.
1) TokenProvider 클래스는 JWT 생성 및 검증의 핵심 로직을 담당합니다. 로그인 성공 시 토큰을 생성하고, 이후 요청에서 전달된 토큰의 유효성을 확인하는 역할을 수행합니다. 토큰의 만료 시간, 서명 알고리즘, 시크릿 키 등이 이 클래스에 집중되어 있습니다.
// TokenProvider.java
public String createToken(String username, long expireTime) {
Date now = new Date();
return Jwts.builder()
.setSubject(username)
.setIssuedAt(now)
.setExpiration(new Date(now.getTime() + expireTime))
.signWith(SignatureAlgorithm.HS512, secretKey)
.compact();
}
public boolean validateToken(String token) {
try {
Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token);
return true;
} catch (JwtException e) {
return false;
}
}
📌 참고: secretKey는 반드시 환경변수 또는 Vault와 같은 보안 저장소에 보관하세요.
2) JWTFilter는 요청마다 헤더에 포함된 토큰을 추출하고, 유효성을 검사한 뒤 인증 객체를 SecurityContext에 저장하는 역할을 합니다. 이 필터는 Spring Security의 OncePerRequestFilter를 상속받아 매 요청마다 실행됩니다.
JWTFilter.java
// JWTFilter.java
public class JWTFilter extends OncePerRequestFilter {
private final TokenProvider tokenProvider;
public JWTFilter(TokenProvider tokenProvider) {
this.tokenProvider = tokenProvider;
}
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain)
throws ServletException, IOException {
String token = resolveToken(request);
if (token != null && tokenProvider.validateToken(token)) {
Authentication auth = tokenProvider.getAuthentication(token);
SecurityContextHolder.getContext().setAuthentication(auth);
}
filterChain.doFilter(request, response);
}
private String resolveToken(HttpServletRequest request) {
String bearerToken = request.getHeader("Authorization");
if (bearerToken != null && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7);
}
return null;
}
}
3) SecurityConfig는 Spring Security의 보안 설정을 담당하는 설정 클래스입니다. 여기서 세션 정책을 무상태로 설정하고, JWTFilter를 필터 체인에 등록함으로써 JWT 기반 인증 체계를 구성합니다. 인증이 필요한 경로와 허용 경로를 명확히 구분하는 것도 이곳에서 처리합니다.
SecurityConfig.java
// SecurityConfig.java
@EnableWebSecurity
public class SecurityConfig {
private final TokenProvider tokenProvider;
public SecurityConfig(TokenProvider tokenProvider) {
this.tokenProvider = tokenProvider;
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http
.csrf(csrf -> csrf.disable())
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/auth/**").permitAll()
.anyRequest().authenticated()
)
.addFilterBefore(new JWTFilter(tokenProvider), UsernamePasswordAuthenticationFilter.class)
.build();
}
}
5. AccessToken과 RefreshToken 안전하게 다루기
보안성을 높이기 위해 JWT 인증은 AccessToken과 RefreshToken을 조합하여 사용하는 방식이 일반적입니다. 각각의 역할과 저장 위치는 아래 표를 참고하세요.
| 항목 | 설명 |
|---|---|
| AccessToken | 짧은 유효 기간(5~15분), API 호출 시 사용, 만료되면 재발급 필요 |
| RefreshToken | 긴 유효 기간(1주~1개월), 보안 저장소 또는 DB에 저장, 토큰 재발급 요청 시 사용 |
6. 전문가가 추천하는 설계 구조 요약
실무 경험을 바탕으로 구성한 JWT 인증 구조의 핵심 요약입니다. 시스템 설계 시 반드시 고려해야 할 전략들입니다.
- 인증 로직은 필터 기반 구조로 처리하고, SecurityContext를 통해 상태 관리
- AccessToken과 RefreshToken은 반드시 분리하여 저장 위치와 수명을 다르게 설정
- 토큰 재발급 로직은 IP와 User-Agent 검증을 통해 보안 강화
- 토큰 유효성 검사는 전역 예외 핸들링과 연동하여 예외 상황 대응
7. 자주 묻는 질문 (FAQ)
아닙니다. JWT는 확장성과 무상태 서버에 유리하지만, 보안 및 토큰 관리 측면에서 더 많은 고려가 필요합니다. 시스템 특성에 따라 적절한 방식을 선택하는 것이 중요합니다.
탈취된 토큰은 유효 시간 내에는 누구나 사용할 수 있기 때문에 매우 위험합니다. 따라서 반드시 HTTPS를 사용하고, 짧은 만료 시간 및 재발급 검증 로직이 필요합니다.
AccessToken은 메모리 또는 세션 스토리지에, RefreshToken은 httpOnly 쿠키 또는 백엔드 DB에 저장하는 것이 권장됩니다. 특히 RefreshToken은 클라이언트에 노출되지 않아야 합니다.
대부분의 경우 HS256 또는 HS512가 널리 사용됩니다. 비대칭 키 기반 RSA도 가능하지만 설정이 복잡하므로 프로젝트 규모에 맞게 선택하는 것이 중요합니다.
가능은 하지만, 필요한 최소 정보만 포함해야 하며, 권한 정보 변경이 자주 일어난다면 DB 조회 기반 권한 검증을 병행하는 것이 좋습니다.
지금까지 Spring Security 기반 JWT 인증 전략에 대해 자세히 살펴보았습니다. 실무 환경에서 적용 가능한 구조를 이해하고, 보안 요소를 함께 고려하는 것이 무엇보다 중요합니다. 오늘 소개한 내용이 여러분의 프로젝트에 실질적인 도움이 되기를 바랍니다. 더 궁금하신 점은 댓글로 남겨주시고, 도움이 되셨다면 구독과 공유 부탁드립니다 😊
#JavaScript #DOM #웹개발기초 #프론트엔드 #HTML조작 #자바스크립트기초 #동적웹 #웹프로그래밍 #초보개발자 #코딩학습

댓글
댓글 쓰기