
이력서를 정리하던 도중 내가 이력서에 쓴 것이 그때 당시엔 확실히 이해하고 있었다.
지금에서 다시보니 이게 왜 발생했더라? 생각이 들어서 다시 한번 재현해보고자 했습니다.
재현하면서 다시 정리하는 상황입니다.
Spring Security 설정에서 아래처럼 모든 요청을 인증 필요로 보호하고 있었다.
.anyRequest().authenticated()
의도한 동작은 다음과 같았다.
로그인(토큰) 없음 → 401 Unauthorized
로그인은 했지만 권한 부족 → 403 Forbidden
하지만 실제로 Postman에서 토큰 없이 보호된 API를 호출했을 때 401이 아닌 403이 반환되었다.
Spring Security는 인증되지 않은 요청이라도 내부적으로 AnonymousAuthenticationFilter가 동작하여
SecurityContext에 AnonymousAuthenticationToken을 넣을 수 있다.
이를 확인하기 위해 Authentication 객체 타입을 출력해보았고, 실제로 AnonymousAuthenticationToken이 확인되었다.
즉, “인증 객체가 아예 없는 상태(null)”이 아니라
“anonymous 인증 객체가 들어있는 상태”였다.
Spring Security에서 예외는 크게 두 가지로 나뉜다.
로그인하지 않음
토큰이 없음 / 잘못됨
→ 보통 AuthenticationEntryPoint가 처리
→ REST API 기준으로는 401 반환이 일반적
→ AccessDeniedHandler가 처리
→ 403 반환
EntryPoint/AccessDeniedHandler를 별도로 설정하지 않았기 때문에
Spring Security는 기본 예외 처리 흐름을 사용했다.
이 과정에서 인증 실패 상황에서 기본 EntryPoint가
Http403ForbiddenEntryPoint로 잡히며, 미인증 요청임에도 403이 반환되는 현상이 발생했다.
즉, 원인은 다음과 같이 정리할 수 있다.
인증/인가 예외 처리 흐름을 명확히 분리하지 않음
기본 AuthenticationEntryPoint가 Http403ForbiddenEntryPoint로 동작
결과적으로 401이 아닌 403 반환
처음에는 .oauth2Login(...) 설정이 포함되어 있었다.
OAuth2Login을 활성화하면 Spring Security는 인증이 필요한 요청에서
로그인 페이지로 리다이렉트하는 웹 로그인 플로우를 사용할 수 있다.
실제로는 302 Redirect 발생
Postman이 리다이렉트를 따라가면서 로그인 HTML을 반환
최종적으로 200 + HTML이 출력되는 현상이 발생했다.
.oauth2Login()을 주석 처리하자 리다이렉트 흐름이 사라지고,
403 반환 현상이 명확히 확인되었다.
REST API에서는 인증 실패(401)와 인가 실패(403)를 명확히 분리하는 것이 중요하다.
따라서 아래처럼 EntryPoint와 AccessDeniedHandler를 직접 설정하여 응답을 표준화했다.
이렇게 처리하면:
토큰 없음 / 미로그인 → 401
권한 부족 → 403
으로 정확히 구분된다.
EntryPoint와 DeniedHandler를 명시적으로 구성한 뒤:
인증 실패(401)와 인가 실패(403)가 명확히 분리됨
API 응답 코드가 일관되게 유지됨
프론트/클라이언트에서 예외 처리 로직이 단순해짐
디버깅 및 유지보수 효율이 향상됨
anyRequest().authenticated()로 보호된 API 호출 시 미인증 요청이 401이 아닌 403 반환EntryPoint인 Http403ForbiddenEntryPoint가 적용됨AuthenticationEntryPoint/AccessDeniedHandler 커스터마이징으로 401/403 응답 분리 및 표준화