ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Spring Security & JWT 사용할 때 조회 쿼리 여러번 발생 처리
    SpringBoot/Spring Security & JWT 회원 조회 이슈 2021. 6. 18. 21:53

    Spring Security & JWT 사용 시 회원 조회 쿼리가 여러 번 나가는 현상 

     

    문제점

    Spring Security와 JWT를 사용해 인증을 처리하는 프로젝트를 개발하던 중

    JWT 토큰을 통해 회원 정보를 조회하는

    UserDetails userDetails = memberService.loadUserByUsername(claims.getSubject());

    해당 로직이 2번 이상씩 반복되는 현상이 발생했습니다.

     

    원인

    확인해본 결과 JwtFilter 클래스에 

    @Componenet 컴포넌트를 선언하면 필터가 두번 등록되어 필터가 두번 돌아 조회 쿼리가 두번 나가는 것을 Jwt 멤버 조회 쿼리가 두번 나가는 것으로 착각한 것이었습니다.

    사실 조회 쿼리가 두번 나가는 것이 아닌 필터가 두번 등록되어 중첩된 현상

     

    JwtTokenProvider.java

        public Authentication getAuthentication(String token){
            Claims claims = Jwts
                    .parserBuilder()
                    .setSigningKey(secret)
                    .build()
                    .parseClaimsJws(token)
                    .getBody();
            UserDetails userDetails = memberService.loadUserByUsername(claims.getSubject());
            log.info("get Authentication : 멤버 쿼리 " );
            return new UsernamePasswordAuthenticationToken(userDetails,"", userDetails.getAuthorities());
        }

     

    예제로 사용할 간단한 조회 로직

        public List<MannerDto> getMannersByNickname(String nickname) {
            Member member = memberRepository.findMemberByNickname(nickname).orElseThrow(NoSuchElementException::new);
            log.info("겟 매너 멤버 쿼리 ");
            return member.getManners().stream()
                    .map(MannerDto::toDto)
                    .collect(Collectors.toList());
        }

    이 메소드를 실행하는 경우

    예상되는 쿼리와 로그는 JWT 사용자 인증에서 Member 조회 쿼리 1번, log.info("get Authentication : 멤버 쿼리 " );
     로그 1번

    Member member = memberRepository.findMemberByNickname(nickname).orElseThrow(NoSuchElementException::new);

     

    여기서 Member 조회 쿼리 1번 , log.info("겟 매너 멤버 쿼리 "); 로그 1번

    총 2번의 Member 조회 쿼리, 각 다른 로그가 1번씩 발생해야 합니다.

    하지만 막상 실행시켜서 확인해보니 

    문제가 되는 부분

    2번의 Member 조회 쿼리와 2번의 log.info("get Authentication : 멤버 쿼리 " );가 발생합니다.

     

    이를 해결하기 위해 방법을 찾던 중 OncePerRequesrFilter를 사용하면 해결된다는 글을 보았습니다.

    현재 Jwt 필터는 GenericFilterBean을 사용하고 있기에 바로 변경해서 적용해보았으나 결과는 변하지 않았습니다.

     

    JwtFilter.java

    @Component
    @RequiredArgsConstructor
    public class JwtFilter extends GenericFilterBean {
    
        private final JwtTokenProvider tokenProvider;
        public static final String AUTHORIZATION_HEADER = "Authorization";
    
    
        @Override
        public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
                throws IOException, ServletException {
            HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
            String jwt = resolveToken(httpServletRequest);
    
            if (StringUtils.hasText(jwt) && tokenProvider.validateToken(jwt)) {
                Authentication authentication = tokenProvider.getAuthentication(jwt);
                SecurityContextHolder.getContext().setAuthentication(authentication);
            }
    
            filterChain.doFilter(servletRequest, servletResponse);
        }
    
        private String resolveToken(HttpServletRequest request) {
            String bearerToken = request.getHeader(AUTHORIZATION_HEADER);
            if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
                return bearerToken.substring(7);
            }
            return null;
        }
    }

     

    해결방법 1

    FilterRegistrationBean을 따로 등록해 

    registrationBean.setFilter( 본인의 Filter ); setFilter로 현재 프로젝트의 필터를 등록하고

    registrationBean.setEnabled(false); setEnabled를 false로 지정합니다.

    import lombok.RequiredArgsConstructor;
    import org.springframework.boot.web.servlet.FilterRegistrationBean;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    @Configuration
    @RequiredArgsConstructor
    public class FilterConfig {
    
        private final JwtFilter jwtFilter;
        @Bean
        public FilterRegistrationBean filterRegistrationBean() {
            FilterRegistrationBean registrationBean = new FilterRegistrationBean();
            registrationBean.setFilter(jwtFilter);
            registrationBean.setEnabled(false);
            return registrationBean;
        }
    }

     

    필터를 등록한 뒤 같은 메서드를 호출한 결과 

    1번의 get Authentication 로그와

    1번의 Jwt Member 조회 쿼리가 발생함을 확인할 수 있었습니다.

     

    1번만 조회하는 결과

    해결방법 2

    JwtFilter의 Component 미등록

    @Component // 삭제!!! 
    @RequiredArgsConstructor
    public class JwtFilter extends GenericFilterBean {
    
        private final JwtTokenProvider tokenProvider;
        public static final String AUTHORIZATION_HEADER = "Authorization";
    
    
        @Override
        public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
                throws IOException, ServletException {
            HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
            String jwt = resolveToken(httpServletRequest);
    
            if (StringUtils.hasText(jwt) && tokenProvider.validateToken(jwt)) {
                Authentication authentication = tokenProvider.getAuthentication(jwt);
                SecurityContextHolder.getContext().setAuthentication(authentication);
            }
    
            filterChain.doFilter(servletRequest, servletResponse);
        }
    
        private String resolveToken(HttpServletRequest request) {
            String bearerToken = request.getHeader(AUTHORIZATION_HEADER);
            if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
                return bearerToken.substring(7);
            }
            return null;
        }
    }

    JwtFilter의 @Component 어노테이션을 삭제해

    컴포넌트 등록을 하지 않고 실행하는 경우도 정상적으로 동작함을 확인할 수 있었습니다. 

     

    댓글

Designed by Tistory.