[토이프로젝트] 감정일기장-2 : JWT 토큰 검증 프로세스

onlydev7777·2024년 9월 12일
post-thumbnail

1️⃣ Any Request API 요청 프로세스

Authorization Process

1. ExceptionTranslationFilter

  • AuthenticationException, AccessDeniedException 오류 처리 클래스
  • AuthenticationException 발생 시
    1. AuthenticationEntryPoint.commence 메서드 호출
    2. 401 UNAUTHORIZED 응답
    3. 미인증 사용자의 요청에 대한 오류 응답
  • AccessDeniedException 발생 시
    1. AccessDeniedHandler.handle 메서드 호출
    2. 403 FORBIDEN 응답
    3. 인증 사용자 이지만 권한 없음에 대한 오류 응답
      private void handleSpringSecurityException(HttpServletRequest request, HttpServletResponse response,
              FilterChain chain, RuntimeException exception) throws IOException, ServletException {
          if (exception instanceof AuthenticationException) {
              handleAuthenticationException(request, response, chain, (AuthenticationException) exception);
          }
          else if (exception instanceof AccessDeniedException) {
              handleAccessDeniedException(request, response, chain, (AccessDeniedException) exception);
          }
      }

2. CustomAuthenticationFailureEntryPoint

  • AuthenticationEntryPoint 구현체
  • 미인증 사용자의 요청에 대한 401 UNAUTHORIZED 오류 응답 처리 담당
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException)
        throws IOException, ServletException {
      response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
      response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);

      PrintWriter writer = response.getWriter();
      writer.write(authException.getMessage());
      writer.flush();
      writer.close();
    }

3. CustomAccessDeniedHandler

  • AccessDeniedHandler 구현체
  • 인증 사용자이지만 권한이 없는 요청에 대한 403 FORBIDEN 오류 응답 처리 담당
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException)
        throws IOException, ServletException {
      response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
      response.setStatus(HttpServletResponse.SC_FORBIDDEN);

      PrintWriter writer = response.getWriter();
      writer.write(accessDeniedException.getMessage());
      writer.flush();
      writer.close();
    }

4. ReceivingJwtExceptionFilter

  • Jwt 관련 오류 처리 담당
  • Jwt 관련 오류 발생 시 401 UNAUTHORIZED 응답
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
        throws ServletException, IOException {
      try {
        filterChain.doFilter(request, response);
      } catch (ExpiredJwtException eje) { // JWT 만료 오류
        log.error(eje.getMessage(), eje);
        sendError(response, HttpServletResponse.SC_UNAUTHORIZED, "Access-Token is expired");
      } catch (JwtException je) {  // JWT 인증 오류
        log.error(je.getMessage(), je);
        sendError(response, HttpServletResponse.SC_UNAUTHORIZED, je.getMessage());
      }
    }

5. JwtAuthorizationFilter

  • Jwt 토큰 인증 담당
  • Jwt 토큰 인증 프로세스
    1. skip URL 검증
    2. Resolve Token
    3. Check Blacklist Token
    4. Verify Token
    5. 인증 상태의 LoginAuthentication 생성
    6. SecurityContext에 LoginAuthentication 저장
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
        throws ServletException, IOException {

      boolean skip = Arrays.stream(skipUrlList)
          .anyMatch(url -> antPathMatcher.match(url, request.getRequestURI()));

      if (skip) {
        filterChain.doFilter(request, response);
        return;
      }

      String token = jwtProvider.resolveToken(
          request.getHeader(jwtProvider.getAccessTokenHeader())
      );

      log.info("login blacklist token = {}", token);
      if (redisService.blackListTokenGet(token)) {
        throw new JwtException("Token is blacklisted");
      }

      Payload payload = jwtProvider.verifyToken(token);

      Authentication authenticated = LoginAuthentication.authenticated(payload, List.of());
      SecurityContextHolder.getContext().setAuthentication(authenticated);

      filterChain.doFilter(request, response);
    }

6. AuthorizationFilter

  • Spring Security 인가 처리 기본 제공 클래스
  • RequestMatcherDelegatingAuthorizationManager 에 인가 처리 여부 위임
  • RequestMatcherDelegatingAuthorizationManager는 AuthenticationAuthorizationManager에 인가 처리 위임
  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);
  }

7. AuthenticatedAuthorizationStrategy

  • 인가처리 담당
  • AuthenticationTrustResolver에 인가여부 최종 위임
	private static class AuthenticatedAuthorizationStrategy extends AbstractAuthorizationStrategy {

		@Override
		boolean isGranted(Authentication authentication) {
			return this.trustResolver.isAuthenticated(authentication);
		}

	}

8. AuthenticationTrustResolver

  • 인증여부 검증 수행
	default boolean isAuthenticated(Authentication authentication) {
		return authentication != null && authentication.isAuthenticated() && !isAnonymous(authentication);
	}

2️⃣ 완성화면

★ GitHub URL

front-end : https://github.com/onlydev7777/emotion-diary-react
back-end : https://github.com/onlydev7777/emotion-diary-monolithic

profile
https://github.com/onlydev7777

0개의 댓글