
Spring Security JWT를 구현할 때 흔히 직접 필터를 만들지, OAuth2 Resource Server를 쓸지 고민합니다. 둘은 모두 Bearer token을 다룰 수 있지만 설계 의도와 책임 범위가 다릅니다.
먼저 봐야 할 기준은 내 서버가 토큰을 발급하는 로그인 서버인지, 이미 발급된 토큰을 검증하는 API 서버인지입니다. 이 구분이 흐려지면 Security 설정이 점점 복잡해집니다.

Spring Security JWT 직접 필터 방식은 무엇인가
직접 JWT 필터 방식은 OncePerRequestFilter 같은 필터에서 Authorization 헤더를 읽고, 토큰을 검증한 뒤 Authentication 객체를 만들어 SecurityContext에 넣는 구조입니다.
public class JwtAuthenticationFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(
HttpServletRequest request,
HttpServletResponse response,
FilterChain chain
) throws ServletException, IOException {
String token = resolveBearerToken(request);
Authentication authentication = authenticate(token);
SecurityContextHolder.getContext().setAuthentication(authentication);
chain.doFilter(request, response);
}
}이 방식은 자유도가 큽니다. 자체 토큰 규칙, 레거시 인증, 특수한 claim 변환이 많다면 직접 구현이 필요할 수 있습니다. 대신 만료, 서명, 예외, 권한 매핑, 테스트 책임도 직접 짊어집니다.
OAuth2 Resource Server는 무엇을 해줄까
OAuth2 Resource Server는 이미 발급된 access token을 검증하고 보호 API 접근을 판단하는 역할에 맞춰진 Spring Security 기능입니다. JWT라면 issuer, jwk set uri, JwtDecoder 같은 설정을 통해 검증 흐름을 구성할 수 있습니다.
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/public/**").permitAll()
.anyRequest().authenticated()
)
.oauth2ResourceServer(oauth2 -> oauth2.jwt());
return http.build();
}이 구조는 API 서버가 Authorization Server에서 발급한 Bearer token을 검증하는 상황에 잘 맞습니다. 표준 흐름 안에서 예외 처리와 인증 객체 생성이 연결됩니다.
로그인 서버와 리소스 서버를 구분한다
로그인 서버는 사용자 자격 증명을 확인하고 토큰을 발급합니다. 리소스 서버는 이미 발급된 토큰을 검증하고 API 접근을 허용합니다. 한 애플리케이션이 둘 다 맡을 수도 있지만 역할은 분리해서 봐야 합니다.
- Authorization Server: 로그인, 동의, 토큰 발급
- Resource Server: Bearer token 검증, 보호 API 제공
- Client: 토큰을 받아 API 호출
- User: 인증 주체
직접 필터가 어울리는 경우
- 레거시 JWT 규칙을 유지해야 한다
- 토큰 저장소나 블랙리스트 정책이 매우 특수하다
- 표준 OAuth2 Resource Server 모델과 맞지 않는 인증 흐름이다
- 학습 목적이거나 작은 내부 도구라서 책임 범위를 명확히 통제할 수 있다
단, 직접 구현은 보안 코드를 직접 운영한다는 뜻입니다. 작은 누락이 인증 우회나 권한 오류로 이어질 수 있으므로 테스트와 리뷰가 반드시 필요합니다.
Resource Server가 어울리는 경우
- API 서버가 외부 또는 별도 인증 서버의 JWT를 검증한다
- issuer, audience, 만료 시간, 서명 검증 같은 표준 흐름을 따른다
- Spring Security의 인증/인가 파이프라인 안에서 처리하고 싶다
- 필터 구현보다 설정과 converter 확장으로 해결 가능하다
권한 매핑은 어디에서 다룰까
JWT에는 scope, roles, authorities 같은 claim이 들어갈 수 있습니다. Resource Server를 쓰더라도 claim을 Spring Security 권한으로 어떻게 바꿀지는 별도 설계가 필요합니다.
JwtAuthenticationConverter converter = new JwtAuthenticationConverter();
converter.setJwtGrantedAuthoritiesConverter(jwt -> {
return extractAuthorities(jwt);
});이 지점에서 직접 필터 방식과 Resource Server 방식 모두 권한 모델을 명확히 해야 합니다. 인증은 사용자가 누구인지 확인하는 일이고, 인가는 무엇을 할 수 있는지 판단하는 일입니다.
실무 선택 기준
- 토큰 발급과 검증 역할을 먼저 분리한다
- 표준 Bearer token 검증이면 Resource Server를 우선 검토한다
- 특수한 레거시 규칙이 있으면 직접 필터 비용을 계산한다
- 권한 claim을 어떤 authority로 바꿀지 정한다
- 401/403 응답과 테스트 케이스를 함께 만든다
자주 하는 실수
- 로그인 API와 보호 API 검증 책임을 한 필터에 모두 넣는다
- JWT payload를 decode만 하고 서명 검증을 놓친다
- 만료 시간, issuer, audience 검증 기준을 흐린다
- 인증 실패 401과 권한 부족 403을 섞는다
- SecurityContext 설정 후 thread/local context 정리와 테스트를 소홀히 한다
정리
Spring Security JWT 직접 필터와 OAuth2 Resource Server는 경쟁 관계라기보다 책임 범위가 다른 선택지입니다. 표준 Bearer token 검증 API 서버라면 Resource Server가 기본 후보이고, 특수한 인증 흐름이라면 직접 구현을 검토할 수 있습니다.
공식 기준은 Spring Security OAuth2 Resource Server JWT 문서에서 확인할 수 있습니다. Spring 서비스 계층 설계는 Spring Boot @Transactional 글과 이어서 보면 좋습니다.