[Spring Security] JwtAuthenticationFilter 관련

JwtAuthenticationFilter 라고

UsernamePasswordAuthenticationFilter.class 이전에

//로그인전 UserPasswordAuthenticationFilter 를 통해 인증을 받도록 설정
                .addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
  • 항상 해당 필터를 거치도록 설정을 하였다.

초기

@Slf4j
public class JwtAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
    @Autowired
    private PasswordEncoder passwordEncoder;
    
    /**
     * authenticationManager 수동 설정을 위함임 AbstractAuthenticationProcessingFilter에서는 authenticationManager를 설정해주지 않으면, authenticate() 메서드를 호출할 때 NullPointerException이 발생한다.
     *
     * @param authenticationManager : 인증 매니저
     */
    public JwtAuthenticationFilter(AuthenticationManager authenticationManager) {
        super();
        setAuthenticationManager(authenticationManager);
    }
    
    @SneakyThrows
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        ObjectMapper objectMapper = new ObjectMapper();
        LoginRequestDTO loginRequestDTO = objectMapper.readValue(request.getInputStream(), LoginRequestDTO.class);
        
        UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(loginRequestDTO.getEmail(), loginRequestDTO.getPassword());
        
        return getAuthenticationManager().authenticate(token);
    }
}
  • 로서 UsernamePasswordAuthenticationFilter 를 상속받아서
    • 인증 수행에 대한 별도 로직을 짜주었다.
  • 하지만…
    • 토큰관련 통합테스트 수행후 무조건 성공할 것이라는 기대외에
    • 302 라는 상태코드가 넘어오는것이 아닌가..?
  • 이유는
    • UsernamePasswordAuthenticationFilter 안에 있었다.
      • 해당 필터의 성공시의 처리로직을 보면
        @Override
        	public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
        			Authentication authentication) throws ServletException, IOException {
        		SavedRequest savedRequest = this.requestCache.getRequest(request, response);
        		if (savedRequest == null) {
        			super.onAuthenticationSuccess(request, response, authentication);
        			return;
        		}
        		String targetUrlParameter = getTargetUrlParameter();
        		if (isAlwaysUseDefaultTargetUrl()
        				|| (targetUrlParameter != null && StringUtils.hasText(request.getParameter(targetUrlParameter)))) {
        			this.requestCache.removeRequest(request, response);
        			super.onAuthenticationSuccess(request, response, authentication);
        			return;
        		}
        		clearAuthenticationAttributes(request);
        		// Use the DefaultSavedRequest URL
        		String targetUrl = savedRequest.getRedirectUrl();
        		getRedirectStrategy().sendRedirect(request, response, targetUrl);
        	}
      		String targetUrl = savedRequest.getRedirectUrl();
      		getRedirectStrategy().sendRedirect(request, response, targetUrl);
      • 요 부분의 savedRequest 는 요청에 대한 redirectUrl 가 설정되어 있지 않는다면
        • 해당 스키마 : 포트 로 응답에 리다이렉트를 수행해버린다.
        • 그래서 /login → 요청했더라도 → 302, / URI 로 자동 리다이렉트 되어 버리는 것이다.

결론

  • 그래서 결론..
    @Slf4j
    public class JwtAuthenticationFilter extends OncePerRequestFilter {
        @Autowired
        private JwtProvider jwtProvider;
        
        @Autowired
        private UserDetailsService userDetailsService;
        
        @Override
        protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
            String authorization = request.getHeader("Authorization");
            if (!Objects.isNull(authorization)) {
                String atk = authorization.substring(7);
                try {
                    String email = jwtProvider.extractUserEmail(atk);
                    UserDetails userDetails = userDetailsService.loadUserByUsername(email);
                    Authentication token = new UsernamePasswordAuthenticationToken(userDetails, "", userDetails.getAuthorities());
                    SecurityContextHolder.getContext()
                            .setAuthentication(token);
                } catch (JwtException e) {
                    request.setAttribute("exception", e.getMessage());
                }
            }
            filterChain.doFilter(request, response);
        }
    }
    • OncePerRequestFilter 을 상속받는것으로 결정하였다.
      • API 방식으로 필터를 구현할때는 해당 필터를 사용하면 안되었는데, 사용 용례에 대해 제대로 모르고 사용한 것 같다.
    • + OncePerRequestFilter 는
      • HTTP 요청당 한 번씩만 실행되도록 보장하는 추상 클래스이다.
      • 서블릿이 실행되는 동안 다른 서블릿으로 요청이 전달될 수 있는데,
        • 필터에서 헤더를 확인 후 특정 URL 로 포워딩을 해버리는 경우, 하나의 필터가 중복해서 여러 번 호출될 가능성이 있음.
        • 이러한 중복 호출을 방지해주는 필터입니다.


Uploaded by N2T