도입
Spring Security는 Filter를 사용해서 사용자가 인증받았는지 확인합니다. 만약 인증 과정 중에 예외가 발생한다면 어떻게 처리해야 할까요? 본 포스팅에선 Spring Security에서 인증 / 인가 과정에서 예외가 발생했을 때 처리 방법에 대해서 정리하고자 합니다.
ExceptionTranslationFilter
Spring Security는 기본적으로 필터를 통해 인증 과정을 구현합니다. 다양한 필터들 중 ExceptionTranslationFilter가 예외 처리를 담당해주는 필터입니다. 그 안에 AuthenticationEntryPoint 객체를 필드로 가지는데, 이 객체에서 예외 발생 시 어떻게 처리해줄지를 담당하는 객체입니다.
class JwtExceptionInterceptorFilter extends ExceptionTranslationFilter{
// AuthenticationEntryPoint 객체 의존성 주입
JwtExceptionInterceptorFilter(AuthenticationEntryPoint authenticationEntryPoint){
super(authenticationEntryPoint);
}
}
동작 방법
ExceptionTranslationFilter는 Security 필터들 중 앞단에 위치합니다. 앞단에 위치하여 try-catch 문으로 이후 필터들을 감싸주는 구조로 되어있어 예외처리를 수행합니다.
public class JwtExceptionInterpretorFilter extends ExceptionTranslationFilter{
// 생략
private void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws IOException, ServletException{
try{
filterChain.doFilter(request, response);
} catch (JwtAuthenticationException exception){
this.sendStartAuthentication(request, response, filterChain, exception);
} catch (JwtException exception){
JwtAuthenticationException authenticationException = new JwtAuthenticationException(ErrorCode.JWT_SERVER_ERROR.getMessage());
this.sendStartAuthentication(request, response, filterChain, authenticationException);
}
}
}
이런 식으로 filterchain.doFilter를 통해 다음 필터들을 통과시킵니다. 만약 이후에 예외가 발생하면, 예외가 ExceptionTranslationFilter까지 던져지고, 여기서 예외를 처리합니다.
protected void sendStartAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
AuthenticationException reason) throws ServletException, IOException {
// SEC-112: Clear the SecurityContextHolder's Authentication, as the
// existing Authentication is no longer considered valid
SecurityContext context = this.securityContextHolderStrategy.createEmptyContext();
this.securityContextHolderStrategy.setContext(context);
this.requestCache.saveRequest(request, response);
this.authenticationEntryPoint.commence(request, response, reason);
}
위 코드는 sendStartAuthentication 코드입니다. 여기서 authenticationEntryPoint의 commence 함수를 실행시킵니다. 아래 단에서 AuthenticationEntryPoint에 대해서 알아봅시다.
AuthenticationEntryPoint
위에서 설명했듯이 AuthenticationEntryPoint는 예외 발생 시 어떤 처리를 해줄지 결정해주는 객체입니다.
@Slf4j
public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint{
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
// response 헤더 설정
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setStatus(HttpStatus.UNAUTHORIZED.value());
response.setContentType("application/json; charset=UTF-8");
// 에러 생성
ApiErrorResult errorResponse = ApiErrorResult.builder()
.errorCode(ErrorCode._INTERNAL_SERVER_ERROR)
.cause(authException.getClass().getName())
.message(authException.getMessage())
.build();
// 에러를 response에 넣어 반환해라.
try {
String json = errorResponse.toString();
log.error(json);
response.getWriter().write(json);
} catch (IOException e) {
e.printStackTrace();
}
}
}
commence 함수에서 실제 예외 처리를 어떻게 처리하는지 볼 수 있습니다. 위의 custom 객체인 경우에는 에러 response를 던져주는 방식으로 예외를 처리해주었습니다.
이번 설명에 사용된 코드는 다음 프로젝트에서 사용되었습니다.
https://github.com/dbwp031/OurWorldcup
'Develop > Spring Security' 카테고리의 다른 글
[Spring Security] Spring Security 기본 구조 (0) | 2023.09.11 |
---|---|
[Spring Security] Spring Security OAuth Login (0) | 2023.09.10 |
[Spring Security] 개요 (0) | 2023.09.09 |