본문 바로가기

Develop/Spring Security

[Spring Security] Authentication 예외 처리

반응형

도입

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

 

GitHub - dbwp031/OurWorldCup

Contribute to dbwp031/OurWorldCup development by creating an account on GitHub.

github.com

 

반응형