Everyone-s-delivery / Web

모두의 배달 Web Service Repository
https://everyone-s-delivery.com
2 stars 1 forks source link

[bug/JWT] 없는 회원 email을 가진 JWT token 기반으로 요청을 보내는 경우 Error Handling #44

Closed rnjstjdgh closed 2 years ago

rnjstjdgh commented 2 years ago

문제 상황

원인 분석

rnjstjdgh commented 2 years ago

기대하는 동작

rnjstjdgh commented 2 years ago

테스트 시 주의사항

rnjstjdgh commented 2 years ago

해결을 위한 추론

1. 토큰이 없을때 -> AuthenticationEntryPoint

@Slf4j
@Component
public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException ex) throws IOException,
            ServletException {
        response.setStatus(HttpServletResponse.SC_SEE_OTHER);
        response.setHeader("Location","/JWTException/EmptyJWTToken");
    }
}

2. 권한 없는 토큰일 때 -> AccessDeniedHandler

@Slf4j
@Component
public class CustomAccessDeniedHandler implements AccessDeniedHandler {
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException exception) throws IOException,
            ServletException {
        response.setStatus(HttpServletResponse.SC_SEE_OTHER);
        response.setHeader("Location","/JWTException/accessdenied");
    }
}

이 추론은 실패...

    public UserDetails loadUserByUsername(String email) {
        Optional<UserEntity> userEntityOp = userRepository.findByEmail(email);
        if(!userEntityOp.isPresent()){
            log.error("There is no user for the token. email : {}", email);
            throw new AccessDeniedException("There is no user for the token.");
        }
        return convertEntityToDto(userEntityOp.get());
    }
rnjstjdgh commented 2 years ago

해결을 위한 추론 2

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
            throws IOException, ServletException {
        String token = jwtTokenProvider.resolveToken((HttpServletRequest) request);
        if (token != null && jwtTokenProvider.validateToken(token)) {
            Authentication auth = jwtTokenProvider.getAuthentication(token);
            SecurityContextHolder.getContext().setAuthentication(auth);
        }
        filterChain.doFilter(request, response);
    }

1. auth 필드에 적절한 권한이 컨텍스트에 할당이 안되면 AccessDeniedHandler

2. auth 필드 자체가 할당이 안되면 AuthenticationEntryPoint

이 추론이 맞았습니다!

rnjstjdgh commented 2 years ago

해결 방법

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
            throws IOException, ServletException {
        String token = jwtTokenProvider.resolveToken((HttpServletRequest) request);
        if (token != null && jwtTokenProvider.validateToken(token)) {
            try{
                Authentication auth = jwtTokenProvider.getAuthentication(token);
                SecurityContextHolder.getContext().setAuthentication(auth);
            }catch (LogicalRuntimeException ex){
                filterChain.doFilter(request, response);
                return;
            }
        }
        filterChain.doFilter(request, response);
    }
rnjstjdgh commented 2 years ago

결론

JwtAuthenticationFilter -> doFilter 에서

  1. 정상 동작 CASE
    • SecurityContextHolder 컨택스트에 정상 적으로 auth 가 바인딩 된 경우
  2. AccessDeniedHandler CASE
    • SecurityContextHolder 컨택스트에 auth 가 바인딩 되었으나 Role이 부적절한 경우
  3. AuthenticationEntryPoint CASE
    • SecurityContextHolder 컨택스트에 auth 가 바인딩 안된 경우