AuthenticationManager를 bean등록해야하는 이유
문제 상황
jwt관련하여 발생하는 모든 에러를 403 에러로 반환함
토큰을 누락하는 경우에도 403, 토큰이 유효하지 않은 경우에도 403
개발할때 원인을 찾기 어려울뿐더러 클라이언트에서 구분하기 어려움
고려할점 - 어디까지 에러 코드를 구분할 것인가 (보안차원에서)
authenticationEntryPoint
정의
authenticationEntryPoint: 인증되지 않은 사용자가 보호된 리소스에 접근했을 때 무엇을 할지 정하는 시작 지점
인증되지 않은 사용자란
- 토큰 없음
- 토큰 만료
- 로그인 안함
요청
↓
Security Filter Chain // 필터 체인으로 가장 먼저 호출된다
↓
Authentication 확인 // 인증 정보를 확인한다 토큰 등..
↓
❌ 인증 정보 없음
↓
AuthenticationException 발생 // 인증 정보가 없으므로 예외를 발생시킨다
↓
ExceptionTranslationFilter
↓
AuthenticationEntryPoint 호출 ✅
AuthenticationEntryPoint를 만들지 않으면 모든 에러가 403으로 내려온다
왜 이런 현상이 발생할까 ?
-> Spring Security는 원래 “웹 사이트용” 프레임워크다
그래서 기본 동작이 인증 안되면 로그인 페이지로 다시 보내도록 기본 설정이 되어있다
그럼 여기서 AuthenticationEntryPoint를 만들지 않았다면 기본 설정대로 LoginUrlAuthenticationEntryPoint를 등록한다
이 EntryPoint의 역할은 로그인 페이지로 리다이렉트 시키는 일을 한다
전혀문제될게 없어보이지만 api를 구현한다면 보통 json으로 데이터를 주고 받을 것이라고 예상하지만
스프링 시큐리티는 로그인 페이지로 응답할 것이다 (기본 설정 때문에)
그러면 파싱에러가 날것이다 기대한 타입과 다르므로
따라서 내가 커스텀하게 EntryPoint을 바꾸는것이 authenticationEntryPoint을 사용하는 것과 같다
주의할점
AuthenticationEntryPoint는 Security Filter Chain 안에서 AuthenticationException이 발생했을 때만 호출된다
즉 다른 에러를 사용해서 다음과 같이 코드가 작성되어 있었기 때문에 인식을 못했다.
RuntimeException / IllegalStateException는 EntryPoint를 호출하지 않는다
try {
Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token);
return true;
} catch (JwtException | IllegalArgumentException e) {
log.warn("잘못된 JWT 토큰: {}", e.getMessage());
return false;
}
의문점 1 - EntryPoint 설정을 추가하지 않고 jwt를 예외처리하면 안되나?
AuthenticationEntryPoint가 필요한 이유는 무엇일까
예외처리는 기본적으로 호출한 곳의 상위로 throws 예외를 던지기 때문에 globalException에서 처리할 수 없다
jwt필터는 인증 보다 ,컨트롤러 호출 보다도 앞에서 호출된다.
JwtAuthenticationFilter (OncePerRequestFilter)
↓
UsernamePasswordAuthenticationFilter
↓
AuthorizationFilter
↓
ExceptionTranslationFilter
여기서 살펴볼 것은 예외처리란 무엇인가이다.
숫자를 0으로 나눌때와 같은 예외를 들어보자
이는 “이 입력은 내 책임 범위 밖이다 호출자가 처리해라”를 명시하면서 ArithmeticException를 던진다
그런데 jwt를 보자 null,만료,위조 모두 시스템 오류가 아니다.
다시말해 실패 != 예외임을 헷갈려선 안된다
jwt를 검증할 수 없다 == 보안 판단을 할 수 없다
jwt를 검증할 수 없다 != 심각한 오류가 발생했다
parserBuilder도중 실패 = 라이브러리 오류 = 심각한 오류 발생 = 예외처리 필요
즉 스프링 시큐리티 입장에서 jwt검증 실패는 처리할 수 있는 정상 범위니까 예외가 아니라 판단해야 하는 상태라고 본다
의문점 2 - EntryPoint 설정을 추가하지 않고 jwt에서 AuthenticationException을 던지면 되지 않을까?
필터에서 인증 실패가 발생하면, 바로 다음 단계로 넘어가지 않고 ExceptionTranslationFilter가 잡음
클라이언트 요청 →
SecurityContextPersistenceFilter →
JwtFilter / UsernamePasswordAuthenticationFilter →
ExceptionTranslationFilter →
기타 인증/인가 필터 →
DispatcherServlet →
Controller
그리고 AuthenticationManager 내부에서 발생한 에러만 ExceptionTranslationFilter가 catch
AuthenticationManager authManager = ...;
try {
authManager.authenticate(token); // <- 이 라인에서 실패하면 AuthenticationException 발생
} catch (AuthenticationException ex) {
// Security는 이걸 인증 실패로 간주
}
ExceptionTranslationFilter의 핵심 로직
try {
filterChain.doFilter(request, response); // 다음 필터로 진행
} catch (AuthenticationException ex) {
authenticationEntryPoint.commence(request, response, ex); // 인증 실패 처리
} catch (AccessDeniedException ex) {
accessDeniedHandler.handle(request, response, ex); // 권한 없음 처리
}
필터에서 직접 던지면:
- filterChain.doFilter() 바깥에서 RuntimeException 발생
- ExceptionTranslationFilter는 try 안에서 발생한 인증 시도 실패만 보고 catch
- 그래서 Security 입장에서는 “내가 만든 인증 실패가 아니다” → 그냥 RuntimeException
- 결과: EntryPoint 호출 안 됨
어째서 바깥에서 발생했는가?
말 그대로 doFilter를 호출하기 전에 jwt검증을 끝냈다
그래서 이게 왜 바깥이냐? doFilter 호출로 try-catch를 하기 때문임
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
String jwt = resolveToken(request);
if (StringUtils.hasText(jwt) && jwtTokenProvider.validateToken(jwt)) {
Authentication authentication = jwtTokenProvider.getAuthentication(jwt);
SecurityContextHolder.getContext().setAuthentication(authentication);
}
filterChain.doFilter(request, response);
}
결론
Spring Security에서 인증 실패를 자동으로 EntryPoint로 연결시키려면 인증 시도가 반드시 AuthenticationManager를 거쳐야 함.
AccessDeniedHandler: 로그인 했지만(=인증 통과) 권한 부족한 경우
'TIL' 카테고리의 다른 글
| TIL/ 260112 (0) | 2026.01.13 |
|---|---|
| TIL/251219 (0) | 2025.12.19 |
| TIL/ 연관관계 매핑 (0) | 2025.10.21 |
| TIL / RESTful API (0) | 2025.10.17 |