[Spring Security] 예외 처리
스프링 시큐리티에서 발생하는 예외는 AuthenticationException과 AccessDeniedException으로 나뉘며, ExceptionTranslationFilter가 예외를 처리한다.
AuthenticationException 발생 시 SecurityContext 에서 인증 정보를 삭제하고 Authentication을 초기화한다.
필터는 authenticationEntryPoint를 실행해 인증 실패를 공통적으로 처리하고 인증을 요청할 수 있는 화면으로 이동시킨다.
인증 프로세스의 요청 정보는 RequestCache, SavedRequest에 저장해 사용자가 인증을 마친 후 재사용한다.
AccessDeniedException 발생 시 필터는 사용자가 익명 사용자인지를 먼저 판단하고, 익명 사용자면 인증 예외 처리를 실행하고 익명 사용자가 아닌 경우 AccessDeniedHandler에게 처리를 위임한다.
public interface AuthenticationEntryPoint {
void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException)
throws IOException, ServletException;
}
public ExceptionHandlingConfigurer<H> authenticationEntryPoint(AuthenticationEntryPoint authenticationEntryPoint) {
this.authenticationEntryPoint = authenticationEntryPoint;
return this;
}
// SecurityConfig.java
http.exceptionHandling(exception -> exception
.authenticationEntryPoint((request, response, authException) -> {
// ..
})
.accessDeniedHandler((request, response, accessDeniedException) -> {
//...
})
);
이렇게 직접 EntryPoint와 DeniedHandler를 설정하면 직접 설정한 클래스가 가장 먼저 적용되고, 설정하지 않으면 각 인증 프로세스마다 기본적으로 제공되는 클래스들이 설정된다.
직접 설정하는 경우 기본 로그인 페이지 생성 작업이 무시된다.
사용자의 요청이 인증이 필요한 요청이지만 인증되지 않은 상태인 경우 AuthorizationFilter는 인가 예외를 뱉고, ExceptionTranslationFilter가 처리한다.
인가 예외는 AccessDeniedException 이지만, 익명 사용자 관련 예외이기에 인증 예외인 AuthenticationException으로 분류된다.
이후 SecurityContext를 비우고 HttpSessionRequestCache에 사용자의 요청을 저장한 후 AuthenticationEntryPoint를 호출해 공통된 작업을 처리한다.
AuthorizationFilter에서 발생한 예외는 ExceptionTranslationFilter가 처리하게 되고, AuthorizationFilter에서는 인가 예외만 던지고 해당 사용자의 인증 상태를 확인하고 인가 예외로 처리할지 인증 예외로 처리할지는 ExceptionTranslationFilter가 결정한다.
// AuthorizationFilter.java
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain)
throws ServletException, IOException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
if (this.observeOncePerRequest && isApplied(request)) {
chain.doFilter(request, response);
return;
}
if (skipDispatch(request)) {
chain.doFilter(request, response);
return;
}
String alreadyFilteredAttributeName = getAlreadyFilteredAttributeName();
request.setAttribute(alreadyFilteredAttributeName, Boolean.TRUE);
try {
AuthorizationDecision decision = this.authorizationManager.check(this::getAuthentication, request);
this.eventPublisher.publishAuthorizationEvent(this::getAuthentication, request, decision);
if (decision != null && !decision.isGranted()) {
throw new AccessDeniedException("Access Denied");
}
chain.doFilter(request, response);
}
finally {
request.removeAttribute(alreadyFilteredAttributeName);
}
}
// ExceptionTranslationFilter.java
private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws IOException, ServletException {
try {
chain.doFilter(request, response);
}
catch (IOException ex) {
throw ex;
}
catch (Exception ex) {
// Try to extract a SpringSecurityException from the stacktrace
Throwable[] causeChain = this.throwableAnalyzer.determineCauseChain(ex);
RuntimeException securityException = (AuthenticationException) this.throwableAnalyzer
.getFirstThrowableOfType(AuthenticationException.class, causeChain);
if (securityException == null) {
securityException = (AccessDeniedException) this.throwableAnalyzer
.getFirstThrowableOfType(AccessDeniedException.class, causeChain);
}
if (securityException == null) {
rethrow(ex);
}
if (response.isCommitted()) {
throw new ServletException("Unable to handle the Spring Security Exception "
+ "because the response is already committed.", ex);
}
handleSpringSecurityException(request, response, chain, securityException);
}
}
AccessDeniedException을 뱉으면 ExceptionTranslationFilter가 받아서 처리한다.
넘어온 예외가 AccessDeniedException인지를 확인하고 인가 예외인 경우 익명 사용자 여부를 체크한다.
스프링 시큐리티가 제공하는 exceptionHandling() api 는 인증 / 인가 관련 예외를 처리할 때 사용된다.
authenticationEntryPoint / accessDeniedHandler / accessDeinedPage 메서드로 인증되지 않은 사용자와 권한이 없는 사용자를 처리한다.
스프링 시큐리티에서는 401, 403 번 에러만 처리할 수 있으니.. 다른 에러도 처리해야 하는 경우 필터를 만들어서 처리하자.
'Spring > Spring Security' 카테고리의 다른 글
[Spring Security] 인가 프로세스 (0) | 2024.06.22 |
---|---|
[Spring Security] 인증 상태 영속성 (0) | 2024.06.10 |
[Spring Security] 인증 아키텍처 (0) | 2024.06.08 |
[Spring Security] 인증 메커니즘 (0) | 2024.05.30 |
[Spring Security] 초기화 과정 (0) | 2024.05.27 |
댓글
이 글 공유하기
다른 글
-
[Spring Security] 인가 프로세스
[Spring Security] 인가 프로세스
2024.06.22 -
[Spring Security] 인증 상태 영속성
[Spring Security] 인증 상태 영속성
2024.06.10 -
[Spring Security] 인증 아키텍처
[Spring Security] 인증 아키텍처
2024.06.08 -
[Spring Security] 인증 메커니즘
[Spring Security] 인증 메커니즘
2024.05.30