Spring Security와 JWT
1. JWT
현대의 웹 서비스는 모바일 앱의 등장과 MSA(Microservices Architecture)로 인해 서버가 상태를 직접 관리하지 않는 Stateless(무상태) 아키텍처를 지

2. SecurityConfig: 커스텀 필터 체인의 설계
Spring Boot 3.x 환경에서는 SecurityFilterChain을 Bean으로 등록하여 보안을 설정합니다. JWT 방식을 채택했기 때문에 기존의 세션 기반 설정들을 과감히 비활성화했습니다.
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(auth -> auth.disable()) // Stateless 환경이므로 CSRF 보호 비활성화
.formLogin(auth -> auth.disable()) // 커스텀 필터를 사용할 것이므로 기본 폼 로그인 비활성화
.httpBasic(auth -> auth.disable()) // HTTP Basic 인증 비활성화
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)); // 세션 생성 정책을 무상태로 설정
return http.build();
}
}
JWT를 사용하는 REST API는 상태를 저장하지 않으므로 CSRF 공격으로부터 상대적으로 안전하며, 서버 자원을 효율적으로 사용하기 위해 세션을 생성하지 않도록 설정
2. 인증과 인가의 흐름: 필터의 역할 분담
로그인 성공 시 토큰을 발급하는 LoginFilter와, 매 요청마다 토큰을 검증하는 JWTFilter를 직접 구현하여 Spring Security 필터 체인에 등록
① JWTUtil: 최신 버전(0.12.x) 기반의 구현
보안 라이브러리의 버전 변화에 민감하게 대응했습니다. 기존 0.11.x 버전과 달리 0.12.3 버전에서는 verifyWith, parseSignedClaims 등을 사용하는 객체 중심의 검증 방식을 적용했습니다.
public String getUsername(String token) {
return Jwts.parser().verifyWith(secretKey)
.build().parseSignedClaims(token)
.getPayload().get("username", String.class);
}
② JWTFilter: 일시적 세션 생성
무상태 환경에서도 요청이 처리되는 동안에는 SecurityContextHolder를 통해 유저 정보를 참조해야 합니다. OncePerRequestFilter를 상속받아 요청마다 1회 실행을 보장하고, 검증 성공 시 일시적인 세션을 생성하여 유저의 Role에 따른 접근 제어를 가능
3. 실무의 난제: CORS 문제 해결
프론트엔드(3000번 포트)와 백엔드(8080번 포트) 간의 통신 시 브라우저에서 발생하는 CORS 문제는 필수적으로 해결해야 할 과제였습니다.
setExposedHeaders를 사용하여 클라이언트(프론트) 단에서 서버가 응답한 헤더 중 Authorization 필드에 접근할 수 있도록 명시적으로 허용했습니다. 이 설정을 놓칠 경우, 서버는 토큰을 보내도 브라우저가 이를 차단하게 됩니다.💡 고민
JWT 구현 중 가장 큰 모순에 빠졌던 지점은 "로그아웃과 토큰 탈취 대응"이다.
탈취된 토큰을 무효화하기 위해 서버측 Redis 저장소를 도입하고 토큰을 관리하려다 보니, 문득 의문이 생겼습니다.
"Stateless를 지향하며 세션을 버렸는데, 결국 Redis에 상태를 저장한다면 세션 방식과 무엇이 다른가?"
고민 을 더 해보았는데 . JWT의 기능은 단순히 '서버의 메모리 절약'만 있는게 아니다.
- 모바일 앱 환경: 모바일은 웹보다 토큰 탈취 우려가 적고, 앱 자체에서 토큰을 제거하는 로그아웃 방식이 확실하게 보장됩니다.
- 확장성: 여러 대의 서버가 운영되는 MSA 환경에서 중앙 집중형 세션 클러스터링 없이도 각 서버가 독립적으로 인증을 검증할 수 있다는 점이 핵심이었습니다.
결국 어느 정도의 상태(State)를 남기더라도 서비스의 환경(웹 vs 모바일)과 확장 가능성을 고려한다면 JWT는 여전히 강력한 도구라는 판단을 내렸습니다. 무조건적인 원칙 준수보다 '상황에 맞는 최선의 선택'을 내리는 것이 핵심인거 같다.
기술 면접 핵심 Q&A
A. BCrypt는 단방향 해시 알고리즘으로 복호화가 불가능하며, 솔팅(Salting) 기술을 사용하여 레인보우 테이블 공격으로부터 안전합니다. 사용자의 비밀번호를 데이터베이스에 안전하게 보관하기 위한 표준적인 선택입니다.
A. 이미 발급된 토큰이 있는 유저는 아이디/비밀번호 검증 필터를 거칠 필요 없이 바로 인증된 세션을 얻어야 하기 때문입니다. 효율적인 요청 처리를 위한 설계입니다.
A. JWT는 암호화가 아닌 서명(Signature)에 초점이 맞춰져 있습니다. Base64로 인코딩된 페이로드는 누구나 디코딩할 수 있으므로, 보안상 중요한 정보 대신 식별자(username)와 권한(role) 정보만을 포함시켰습니다.