http.exceptionHandling()
.accessDeniedHandler(jwtAccessDeniedHandler)
.authenticationEntryPoint(jwtAuthenticationEntryPoint);
Security Config
파일을 확인하면 다음과 같은 시큐리티 설정을 만나볼 수 있다.
해당 파라미터에, 커스텀한 핸들러를 넣어주었는데 해당 핸들러들이 정확히 언제 동작하는지 확인하고 싶었다.
이 외에도, JWT를 인증/인가 도구로 사용 중인데, validateToken
이 잘못되었을 때는 어느 부분이 동작하는지 확인하고자 했다.
결과적으로 크게 3가지로
1. AccessDenidedHandler
가 동작하는 시점,
2. AuthenticationEntryPoint
가 동작하는 시점,
3. JWT validate
가 false한 시점(JWT Exceptions)
이 언제인지 확인하고자 한다.
해당 코드가 동작하는 시점에 log를 찍는 방식으로 진행했다.
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException {
//필요한 권한이 없이 접근하려 할때 403
log.error("JwtAccessDeniedHandler 동작!");
response.sendError(HttpServletResponse.SC_NOT_ACCEPTABLE);
}
@Override
public void commence(HttpServletRequest request,
HttpServletResponse response,
AuthenticationException authException) throws IOException {
// 유효한 자격증명을 제공하지 않고 접근하려 할때 401
log.error("JwtAuthenticationEntryPoint 동작!");
response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
}
public boolean validateToken(String token) {
try {
Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token);
return true;
} catch (Exception e) {
log.info("validateToken Exception 발생");
}
return false;
}
@ExceptionHandler(AccessDeniedException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public RestApiException accessDeniedException(AccessDeniedException e) {
log.error("AccessDeniedException 발생 - ControllerAdvice에서 잡기");
return new RestApiException(Status.USER_AUTHORITY);
}
다음과 같이 예외가 처리되는 부분에 이름과 함께 log를 찍어 두었다.
AccessDeniedHandler
의 경우 이름과 같이 접근이 제한되었을 때 동작한다.
즉, 접근 메서드에 대해 권한 인가가 정상적으로 이루어지지 않았을 때 동작한다.
해당 과정은 Controller
단에서 @PreAuthorize
애너테이션을 통해 진행되게 코드를 짰는데,
해당 과정을 RestControllerAdvice
를 통해 잡지 않는 경우를 먼저 살펴보았다.
RestControllerAdvice
가 동작하지 않을 때,ERROR 80660 --- [nio-8080-exec-3] s.bus10.security.JwtAccessDeniedHandler : JwtAccessDeniedHandler 동작!
에러 로그가 뜨면서, 우리가 직접 커스텀했던 JWTAccessDeniedHandler
가 동작한다는 것을 확인할 수 있다.
그렇다면, 해당 예외를 RestControllerAdvice
로 잡아내면 어떻게 될까?
RestControllerAdvice
가 동작할 때,ERROR 81031 --- [nio-8080-exec-1] sparta.bus10.advice.ExceptionAdvice : AccessDeniedException 발생 - ControllerAdvice에서 잡기
WARN 81031 --- [nio-8080-exec-1] .m.m.a.ExceptionHandlerExceptionResolver : Resolved [org.springframework.security.access.AccessDeniedException: Access Denied]
다음과 같이 ControllerAdvice
에서 잡아내고, 커스텀한 AccessDeniedHadndler
가 동작하지 않는 모습을 확인할 수 있다. - Security ExceptionHandlerExceptionResolver
는 동작한다!
ControllerAdvice
의 경우 컨트롤러부터 발생하는 예외에 대해 처리를 해주게 되는데, Spring Security의 경우 컨트롤러에 접근하기 전에 즉, 컨트롤러 상위에서 동작하는 기능이다.
하지만, 인가는 @PreAuthorize
를 통해 컨트롤러 쪽에서 진행하기 때문에, 해당 예외가 발생하는 경우 ControllerAdvice
가 우리가 명시한 예외를 잡아주게 되는 것이고, Security까지 예외가 넘어가지 않아 Custom한 예외 핸들러가 동작하지 않았던 것이다.
말 그대로, 인증이 정상적으로 이루어지지 않았을 때 발생하는 예외를 처리하기 위한 핸들러이다.
즉, 유효한 자격증명이 이루어지지 않는 경우 동작하는 핸들러인데, 이를 동작시키는 방법은 Postman에서 Authorization
헤더를 보내주지 않거나, Token Util에 명시해둔 헤더 키 밸류가 넘어오지 않을 시 동작하게 된다.
ERROR 81031 --- [nio-8080-exec-5] s.b.s.JwtAuthenticationEntryPoint : JwtAuthenticationEntryPoint 동작!
다음과 같이 ERROR LOG를 확인할 수 있는데,
@PreAuthorize
를 통해 ControllerAdvice
쪽에서 잡아낼 수 있었던 예외와 달리, 해당 예외의 경우 Controller
에 넘어오기 전에 잡아내서 커스텀한 핸들러로 처리해주는 방식이다.
즉, 스프링 시큐리티 내부에서 처리해주기 때문에, ControllerAdvice
쪽에서는 예외를 잡아내지 못했다 !
이를 바탕으로, ControllerAdvice 상위에서 동작하는 Security에 대해 파악할 수 있었다.
Token의 내용이 잘못되었을 때 동작하는 Exception
이다.
Jwt 라이브러리 사용시, 토큰을 파싱하는 메서드가 있는데, 해당 .parseClaimsJws()
내부에 들어가면 다음과 같은 doc을 확인할 수 있다.
UnsupportedJwtException – if the claimsJws argument does not represent an Claims JWS
MalformedJwtException – if the claimsJws string is not a valid JWS
SignatureException – if the claimsJws JWS signature validation fails
ExpiredJwtException – if the specified JWT is a Claims JWT and the Claims has an expiration time before the time this method is invoked.
IllegalArgumentException – if the claimsJws string is null or empty or only whitespac
Token의 유효성을 검증하는 과정에서
ERROR 81031 --- [nio-8080-exec-4] sparta.bus10.jwt.JwtUtil : validateToken Exception 발생
다음과 같은 ERROR LOG를 확인할 수 있었다.
즉, validation을 진행할 때 JWT가 유효하지 않다면 일어날 수 있는 다양한 예외가 존재하는 것이다.
프로젝트 구조 상위에서 작동하는 Security
에 대해 파악할 수 있었고,
왜 AccessDeniedException
은 ControllerAdvice
에서 잡아낼 수 있고, AuthenticationEntryPoint
는 못잡아내는지 파악할 수 있었다.
SpringSecurity
의 동작 범위에 대해 조금 파악할 수 있었고, 이를 바탕으로 가장 중요한 ContetxtHolder
의 생성 시기나 Scope, 내부 구조에 대해 더 파악해야겠다는 생각을 했다.
내가 뭐 하고 있는건지 알고 쓰자
어매이징