Filter 2번 실행 이슈

Choco·2024년 2월 5일
post-thumbnail

문제발생

JWT를 검증하기 위한 필터를 구현하였는데, 특정 상황에서 필터가 2번 실행되는 경우가 발생하였다.

기존 구현 방법

필터

Spring에 특화된 Filter 구현체인 GenericFIlterBean을 이용하여 JWT token 값이 유효하지 않을떄 에러를 Response하는 필터를 구현 하였다.

@Component
@RequiredArgsConstructor
public class JwtAuthenticationFilter extends GenericFilterBean {

    private final JwtTokenResolver jwtTokenResolver;
    private final JwtTokenValidator jwtTokenValidator;
    private final ApiResponseBuilder apiResponseBuilder;

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println("필터 실행");
        // 1. Request Header에서 JWT 토큰 추출
        String token = jwtTokenResolver.extractToken((HttpServletRequest) request);

        // 2. validateToken 으로 토큰 유효성 검사
        if (token != null) {
            if (jwtTokenValidator.validateToken(token)){
                // 토큰이 유효할 경우 토큰에서 Authentication 객체를 가져와 SecurityContext에 저장
                Authentication authentication = jwtTokenResolver.getAuthentication(token);
                SecurityContextHolder.getContext().setAuthentication(authentication);
                chain.doFilter(request, response);
            }
            else{
                apiResponseBuilder.buildErrorResponse((HttpServletResponse) response, BAD_REQUEST, INVALID_JWT);
            }
        }
        else{
            chain.doFilter(request, response);
        }
    }
}

필터 등록

필터 등록은 SecurityFilterChain에 등록해주었다

@Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        return http
                {생략}
                .addFilterBefore(customGenericFilterBean, UsernamePasswordAuthenticationFilter.class)
                .build();
    }

API 호출

중복 ID가 존재하면 에러를 반환하고 아니면 회원가입을 하는 단순한 회원가입 API를 호출 하면 Filter가 2번 실행된다.

문제 원인

스프링부트는 Filter가 스프링 빈에 등록되어있으면 이를 필터에 등록해준다. 하지만 지금 현재 구현한 코드상엔 addFilterBefore을 이용하여 Filter체인에 등록했기에 같은 필터가 두번 등록된 것이다.

해결방법

한 서블릿 요청에 한번에 Filter 호출만을 하는 OncePerRequestFilter를 이용하여 구현한다.

@Component
@RequiredArgsConstructor
public class JwtAuthenticationFilter extends OncePerRequestFilter {

    private final JwtTokenResolver jwtTokenResolver;
    private final JwtTokenValidator jwtTokenValidator;
    private final ApiResponseBuilder apiResponseBuilder;
    
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        // 1. Request Header에서 JWT 토큰 추출
        String token = jwtTokenResolver.extractToken((HttpServletRequest) request);

        // 2. validateToken 으로 토큰 유효성 검사
        if (token != null) {
            if (jwtTokenValidator.validateToken(token)){
                // 토큰이 유효할 경우 토큰에서 Authentication 객체를 가져와 SecurityContext에 저장
                Authentication authentication = jwtTokenResolver.getAuthentication(token);
                SecurityContextHolder.getContext().setAuthentication(authentication);
                filterChain.doFilter(request, response);
            }
            else{
                apiResponseBuilder.buildErrorResponse((HttpServletResponse) response, BAD_REQUEST, INVALID_JWT);
            }
        }
        else{
            filterChain.doFilter(request, response);
        }
    }
}
profile
주니어 백엔드 개발자 입니다:)

0개의 댓글