본문 바로가기
TIL

[240106] permitAll()과 shouldNotFilter

by 진진리 2024. 1. 6.
728x90
프로젝트를 해오면서 사용자의 권한이 필요하지 않은 요청의 경우 WebSecurityConfig에서 permitAll()을 통해 허가해주었다.
하지만 이렇게 설정한 api에 대해서도 성공적으로 반환값이 돌아오지 않는 경우가 있었다.
바로 유효하지 않은 JWT 토큰이 헤더나 쿠키에 들어가 있는 경우이다.

permitAll()을 설정해줬을 경우에는 잘못된 토큰이 들어있는 경우에도 요청을 허가해줘야 하는 것이 아닌가? 라는 생각이 들어서 이에 대해 공부해보았다.

 

문제 상황

토큰이 없는 경우 & 유효하지 않은 토큰이 있는 경우

 

WebSecurityConfig 일부

@Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        // CSRF 설정
        http.csrf((csrf) -> csrf.disable());

        // 기본 설정인 Session 방식은 사용하지 않고 JWT 방식을 사용하기 위한 설정
        http.sessionManagement((sessionManagement) ->
            sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
        );

        http.authorizeHttpRequests((authorizeHttpRequests) ->
            authorizeHttpRequests
                .requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll() // resources 접근 허용 설정
                .requestMatchers("/").permitAll() // 메인 페이지 요청 허가
                .requestMatchers("/api/user/**").permitAll() // '/api/user/'로 시작하는 요청 모두 접근 허가
                .anyRequest().authenticated() // 그 외 모든 요청 인증처리
        );

	// 생략

        return http.build();
    }

 

requestMatchers("/api/user/**").permitAll()로 설정해주었음에도

헤더에 잘못된 토큰 값이 들어있는 경우에는 예외처리한다.

그 이유는 permitAll()로 설정한 api의 요청에 대해서도 필터 체인을 거치기 때문이다.

 

permitAll()에 대해 좀 더 이해하기 위해서 JwtAuthorizationFilter에 대해 알아보자.

JwtAuthorizationFilter 일부

@Override
    protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain filterChain) throws ServletException, IOException {

        String tokenValue = jwtUtil.getJwtFromHeader(req);

        if (StringUtils.hasText(tokenValue)) {

            if (!jwtUtil.validateToken(tokenValue)) {
                insertStatusInfoIntoResponse(res, HttpStatus.BAD_REQUEST.value(), "유효하지 않은 토큰입니다.");
                return;
            }

            Claims info = jwtUtil.getUserInfoFromToken(tokenValue);

            try {
                setAuthentication(info.getSubject());
            } catch (Exception e) {
                log.error(e.getMessage());
                return;
            }
        }

        filterChain.doFilter(req, res);
    }

    // 인증 처리
    public void setAuthentication(String username) {
        SecurityContext context = SecurityContextHolder.createEmptyContext();
        Authentication authentication = createAuthentication(username);
        context.setAuthentication(authentication);

        SecurityContextHolder.setContext(context);
    }

    // 인증 객체 생성
    private Authentication createAuthentication(String username) {
        UserDetails userDetails = userDetailsService.loadUserByUsername(username);
        return new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
    }

 

성공적인 반환값을 얻었을 때와 같이 header에 아무런 값이 들어있지 않는 경우에는 

SecurityContext에 Authentication 객체를 생성 및 설정하지 않고 다음 필터로 넘어가게 된다.

따라서 permitAll()에 의해서 SecurityContext에 인증 객체가 존재하지 않더라도 즉, 권한이 없더라도 해당 요청을 승인한다는 것을 알 수 있다.

 

하지만 잘못된 토큰값이 필터에 들어오게 되면 예외가 발생하면서 요청이 제대로 받아들여지지 않게 된다.

 

이에 대해 찾아보는 과정에서 아예 필터를 거치지 않는 ignoring을 새로 알게 되었다.

WebSecurityCustomizer를 WebSecurityConfig에 추가하여 사용할 수 있다.

    @Bean
    public WebSecurityCustomizer webSecurityCustomizer(){
        return web -> {
            web.ignoring()
                    .requestMatchers("/users/**")
                    .requestMatchers(HttpMethod.GET, "/boards/**");
        };
    }

 


shouldNotFilter()

permitAll()에 대해 알아보기 위해 요청을 보내는 과정에서 똑같이 jwt 토큰을 이용하는 프로젝트임에도 잘못된 토큰을 담아 요청을 보내도 올바른 응답이 돌아오는 경우가 있었다.
팀 프로젝트에서 다른 팀원분이 구현한 부분이었는데 다시한번 살펴보니 shouldNotFilter라는 메서드가 있었다.

 

토큰이 없는 경우 & 유효하지 않은 토큰을 사용한 경우 모두 정상적인 반환값이 돌아온다.

 

WebSecurityConfig에서는 앞과 같이 .permitAll()로 회원가입과 관련된 api url을 권한 없이도 요청 가능하도록 설정하고 있었다.

차이가 있었던 부분은 JwtAuthorizationFilter 부분의 코드였다.

 

JwtAuthorizationFilter

@RequiredArgsConstructor
public class JwtAuthorizationFilter extends OncePerRequestFilter {

    private static final List<RequestMatcher> whiteList =
            List.of(new AntPathRequestMatcher("/api/v1/users/signup/**", HttpMethod.POST.name()));
    
    // . . . 생략
    
    @Override
    protected boolean shouldNotFilter(HttpServletRequest request) {
        // 현재 URL 이 화이트 리스트에 존재하는지 체크
        return whiteList.stream().anyMatch(whitePath -> whitePath.matches(request));
    }
}

 

OncePerRequestFilter를 상속받는 클래스 내부에 shouldNotFilter를 오버라이드 하여서 특정 path를 포함한 요청은

필터를 거치지 않도록 설정할 수 있다고 한다.  

shouldNotFilter의 디폴트 값은 false로 필터링에서 제외하고 싶은 요청의 경우 true를 반환해주면 된다.

해당 메서드를 사용하니 잘못된 토큰이 담긴 요청의 경우에도 정상적인 반환값을 받을 수 있었다.


참고

https://velog.io/@choidongkuen/Spring-Security-SecurityConfig-%ED%81%B4%EB%9E%98%EC%8A%A4%EC%9D%98-permitAll-%EC%9D%B4-%EC%A0%81%EC%9A%A9%EB%90%98%EC%A7%80-%EC%95%8A%EC%95%98%EB%8D%98-%EC%9D%B4%EC%9C%A0

 

[Spring Security] - SecurityConfig 클래스의 permitAll() 이 적용되지 않았던 이유

안녕하세요 이번 포스팅에서는 Better 팀의 Iter 프로젝트 에서 진행했던 Spring Security 을 이용한 회원 인증/인가 시스템에서 제가 겪었던 문제점과 새롭게 알게된 점을 주제로 작성하고자합니다

velog.io

https://velog.io/@skyjoon34/OncePerRequestFilter-%EA%B7%B8%EB%A6%AC%EA%B3%A0-shouldNotFilter

 

OncePerRequestFilter, shouldNotFilter And ignore

문제 OncePerRequestFilter vs permitAll() 시큐리티를 이용한 인증 인가를 구현하는 과정에서 의문이 생겼다. 무조건 한번 거치는 필터 vs 허가한다. 우선순위가 무엇일까?? /users/signup

velog.io