TIL - #20 Security ExceptionHandling

Quann·2023년 1월 6일
1

00. 개요

WebSecurityConfig.java

http.exceptionHandling()
    .accessDeniedHandler(jwtAccessDeniedHandler)
    .authenticationEntryPoint(jwtAuthenticationEntryPoint);

Security Config 파일을 확인하면 다음과 같은 시큐리티 설정을 만나볼 수 있다.
해당 파라미터에, 커스텀한 핸들러를 넣어주었는데 해당 핸들러들이 정확히 언제 동작하는지 확인하고 싶었다.

이 외에도, JWT를 인증/인가 도구로 사용 중인데, validateToken이 잘못되었을 때는 어느 부분이 동작하는지 확인하고자 했다.

결과적으로 크게 3가지로
1. AccessDenidedHandler가 동작하는 시점,
2. AuthenticationEntryPoint가 동작하는 시점,
3. JWT validate가 false한 시점(JWT Exceptions)
이 언제인지 확인하고자 한다.


01. 시도해본 것

해당 코드가 동작하는 시점에 log를 찍는 방식으로 진행했다.

JwtAccessDeniedHandler.java

@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException {
    //필요한 권한이 없이 접근하려 할때 403
    log.error("JwtAccessDeniedHandler 동작!");
    response.sendError(HttpServletResponse.SC_NOT_ACCEPTABLE);
}

JwtAuthenticationEntryPoint.java

@Override
public void commence(HttpServletRequest request,
                     HttpServletResponse response,
                     AuthenticationException authException) throws IOException {
    // 유효한 자격증명을 제공하지 않고 접근하려 할때 401
    log.error("JwtAuthenticationEntryPoint 동작!");
    response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
}

JwtUtil.java

public boolean validateToken(String token) {
    try {
        Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token);
        return true;
    } catch (Exception e) {
        log.info("validateToken Exception 발생");
    }
    return false;
}

ExceptionAdvice.java

@ExceptionHandler(AccessDeniedException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public RestApiException accessDeniedException(AccessDeniedException e) {
    log.error("AccessDeniedException 발생 - ControllerAdvice에서 잡기");
    return new RestApiException(Status.USER_AUTHORITY);
}

다음과 같이 예외가 처리되는 부분에 이름과 함께 log를 찍어 두었다.


02. 결과

02.01. JWTAccessDeniedHandler

AccessDeniedHandler의 경우 이름과 같이 접근이 제한되었을 때 동작한다.
즉, 접근 메서드에 대해 권한 인가가 정상적으로 이루어지지 않았을 때 동작한다.
해당 과정은 Controller 단에서 @PreAuthorize 애너테이션을 통해 진행되게 코드를 짰는데,
해당 과정을 RestControllerAdvice를 통해 잡지 않는 경우를 먼저 살펴보았다.

즉, 권한이 맞지 않는 리소스에 접근하는 경우 발생하는 경우를 살펴보았다.

ex) User가 Admin 권한 리소스에 접근하는 경우

  1. RestControllerAdvice가 동작하지 않을 때,
    ERROR 80660 --- [nio-8080-exec-3] s.bus10.security.JwtAccessDeniedHandler : JwtAccessDeniedHandler 동작!

에러 로그가 뜨면서, 우리가 직접 커스텀했던 JWTAccessDeniedHandler가 동작한다는 것을 확인할 수 있다.
그렇다면, 해당 예외를 RestControllerAdvice로 잡아내면 어떻게 될까?

  1. 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한 예외 핸들러가 동작하지 않았던 것이다.

02.02. JWTAuthenticationEntryPoint

말 그대로, 인증이 정상적으로 이루어지지 않았을 때 발생하는 예외를 처리하기 위한 핸들러이다.
즉, 유효한 자격증명이 이루어지지 않는 경우 동작하는 핸들러인데, 이를 동작시키는 방법은 Postman에서 Authorization 헤더를 보내주지 않거나, Token Util에 명시해둔 헤더 키 밸류가 넘어오지 않을 시 동작하게 된다.

ERROR 81031 --- [nio-8080-exec-5] s.b.s.JwtAuthenticationEntryPoint : JwtAuthenticationEntryPoint 동작!

다음과 같이 ERROR LOG를 확인할 수 있는데,
@PreAuthorize를 통해 ControllerAdvice쪽에서 잡아낼 수 있었던 예외와 달리, 해당 예외의 경우 Controller에 넘어오기 전에 잡아내서 커스텀한 핸들러로 처리해주는 방식이다.
즉, 스프링 시큐리티 내부에서 처리해주기 때문에, ControllerAdvice 쪽에서는 예외를 잡아내지 못했다 !
이를 바탕으로, ControllerAdvice 상위에서 동작하는 Security에 대해 파악할 수 있었다.

02.03. Token Validation

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가 유효하지 않다면 일어날 수 있는 다양한 예외가 존재하는 것이다.


04. 결론

프로젝트 구조 상위에서 작동하는 Security에 대해 파악할 수 있었고,
AccessDeniedExceptionControllerAdvice에서 잡아낼 수 있고, AuthenticationEntryPoint는 못잡아내는지 파악할 수 있었다.

SpringSecurity의 동작 범위에 대해 조금 파악할 수 있었고, 이를 바탕으로 가장 중요한 ContetxtHolder의 생성 시기나 Scope, 내부 구조에 대해 더 파악해야겠다는 생각을 했다.

03. 오늘의 한 문단

내가 뭐 하고 있는건지 알고 쓰자

profile
코드 중심보다는 느낀점과 생각, 흐름, 가치관을 중심으로 업로드합니다!

4개의 댓글

comment-user-thumbnail
2023년 1월 9일

어매이징

1개의 답글
comment-user-thumbnail
2024년 1월 21일

포스팅 잘 읽었습니다.
질문이 있습니다!
AccessDeniedHandler, AuthenticationEntryPoint 를 상속해서 Config에 추가하는 방식은 @RestControllerAdvice 와는 별개의 방식인가요?

1개의 답글