[Trouble Shooting] JWT 필터에서 토큰이 없는데 바로 401을 던지지 않는 이유

2026. 5. 26. 23:30·Trouble Shooting

문제 상황

JWT 인증 필터를 만들면서 이런 코드를 작성했고, 이 부분이 이상하게 느껴졌다.

토큰이 없다는 건 인증되지 않은 요청이라는 뜻인데, 왜 바로 예외를 던지거나 401 Unauthorize 응답을 던지지 않고 다음 필터로 넘기는 걸까?

JWT가 없으면 잘못된 요청이 아닐까, 바로 막아야 하는 게 아닐까?라는 생각을 했지만 Spring Security의 인증 흐름을 조금 더 보니 잘못된 생각이었음을 깨달았다.
String authorization = request.getHeader("Authorization");

if (authorization == null || !authorization.startsWith("Bearer ")) {
    filterChain.doFilter(request, response);
    return;
}

JWT 필터는 모든 요청을 지난다

JWT 필터는 보통 Spring Security 필터 체인에 등록된다.
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
이렇게 등록하면 JWT 필터는 로그인 요청, 회원가입 요청 등 여러 요청을 지나가게 된다.
즉, JWT 필터 입장에서는 지금 들어온 요청이 반드시 인증이 필요한 요청인지 알기 어렵다.

예를 들어 다음 요청들은 토큰이 없어도 정상이어야 한다.
POST /api/auth/login
POST /api/auth/signup
GET /api/regions/sidos
로그인하려는 사용자, 회원가입 하려는 사용자 등은 토큰이 없는 것이 정상적인 흐름이다.
그런데 JWT 필터에서 토큰이 없다는 이유로 바로 401을 던지면 로그인이나 회원가입 요청까지 전부 막혀버릴 수 있다.

JWT 필터의 역할

JWT 필터의 역할은 모든 요청을 무조건 막는 것이 아닌,
Authorization 헤더에 Bearer 토큰이 있으면 검증하고, 유효하면 인증 정보를 SecurityContext에 저장하는 역할이다.
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {

    String authorization = request.getHeader(AUTHORIZATION_HEADER);

    if (!hasAuthorization(authorization)) {
        filterChain.doFilter(request, response);
        return;
    }

    String token = authorization.substring(BEARER_PREFIX.length());

    if (!jwtProvider.validateToken(token)) {
        filterChain.doFilter(request, response);
        return;
    }

    String loginId = jwtProvider.getLoginId(token);

    UserDetails userDetails = customUserDetailService.loadUserByUsername(loginId);

    UsernamePasswordAuthenticationToken authentication =
            new UsernamePasswordAuthenticationToken(
                    userDetails,
                    null,
                    userDetails.getAuthorities()
            );

    SecurityContextHolder.getContext().setAuthentication(authentication);

    filterChain.doFilter(request, response);
}

private boolean hasAuthorization(String authorization) {
    return authorization != null && authorization.startsWith(BEARER_PREFIX);
}
위의 코드와 같이 토큰이 있으면 인증을 시도하고, 토큰이 없으면 인증 정보를 만들 수 없기 때문에 다음 필터로 넘긴다.
여기서 중요한 점은, 다음 필터로 넘긴다고 해서 무조건 요청이 성공하는 것은 아니라는 점이다.
filterChain.doFilter(request, response);

최종 판단은 SecurityConfig

토큰이 없는 요청을 허용할지 막을지는 JWT 필터가 아니라, SecurityConfig의 권한 설정이 판단한다.
예를 들어 다음과 같은 설정이 있다고 가정하겠다.
.authorizeHttpRequests(auth -> auth
        .requestMatchers("/api/auth/**", "/api/regions/**").permitAll()
        .anyRequest().authenticated()
)
위의 설정은 /api/auth/**, /api/regions/**의 요청 외에는 인증이 필요하다는 요청이다.
만약 토큰 없이 /api/user/me 같은 보호 API로 요청하면, JWT 필터는 그냥 넘기지만 Security 설정에서 authenticated() 조건을 만족하지 못한다.

그 결과 Spring Security가 인증되지 않은 요청으로 판단하고, 401 또는 403 응답을 내린다.

정리

처음에는 토큰이 없으면 JWT 필터에서 바로 막아야 한다고 생각했다.

하지만 JWT 필터는 모든 요청을 지나가기 때문에, 토큰이 없다는 이유만으로 바로 막으면 로그인, 회원가입, 공개 API까지 막힐 수 있다.

그래서 JWT 필터는 직접 최종 판단을 하기보다, 토큰이 있으면 인증 정보를 만들고 없으면 다음 필터로 넘긴다.
그리고 최종적으로 해당 요청이 인증이 필요한지는 SecurityConfig의 설정이 판단한다.

흐름

요청 들어옴
-> JWT 필터 실행
-> Bearer 토큰이 있으면 검증
-> 유효하면 SecurityContext에 Authentication 저장
-> 다음 필터로 이동
-> SecurityConfig의 권한 설정으로 최종 허용/거부 판단
이번 문제를 통해 JWT 필터의 역할을 더 명확히 이해할 수 있었다.
JWT 필터는 모든 요청을 막는 것이 아닌, 요청 토큰이 있을 때 Spring Security가 이해할 수 있는 인증 정보로 바꿔주는 역할에 가깝다.

'Trouble Shooting' 카테고리의 다른 글

[Trouble Shooting] RefreshTokenService가 필요한 이유  (0) 2026.05.28
[Trouble Shooting] Access Token과 Refresh Token은 왜 나눠서 보관할까?  (0) 2026.05.27
[Trouble Shooting] Spring Security formLogin이 React JSON API와 맞지 않았던 이유  (0) 2026.05.25
'Trouble Shooting' 카테고리의 다른 글
  • [Trouble Shooting] RefreshTokenService가 필요한 이유
  • [Trouble Shooting] Access Token과 Refresh Token은 왜 나눠서 보관할까?
  • [Trouble Shooting] Spring Security formLogin이 React JSON API와 맞지 않았던 이유
mins0on
mins0on
비전공자의 백엔드 개발자 공부 기록 일지입니다.
  • mins0on
    꾸준함의 가치
    mins0on
  • 전체
    오늘
    어제
    • 분류 전체보기 (65) N
      • Java (7)
      • Spring (9)
      • DataBase (1)
      • Algorithm (1)
      • Network (6)
      • 운영체제 (2)
      • 코드 분석 (26)
      • Trouble Shooting (4) N
      • Project (1)
      • Migration (3)
      • 기타 (1)
      • 개념 정리 (3)
      • Coding Test (1)
        • Baekjoon (1)
  • hELLO· Designed By정상우.v4.10.6
mins0on
[Trouble Shooting] JWT 필터에서 토큰이 없는데 바로 401을 던지지 않는 이유
상단으로

티스토리툴바