spring

[spring security] 왜 500번 에러도 401(Unauthorized)에러가 될까?

박세준 2024. 4. 7. 20:31

문제상황

spring security에서 filterChain을 설정하면서 "/member/signUp" url에 대한 request는 permitAll()을 해주고 싶었다.

 

이 과정에서 분명 permitAll()를 해주었지만 401 error가 발생하였고 이를 해결하는 과정을 포스팅한다.

 

본문

String[] allowUrls = {"/", "/swagger-ui/**", "/member/signUp"};

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {

    http
            .csrf(AbstractHttpConfigurer::disable)
            .cors(AbstractHttpConfigurer::disable)
            .authorizeHttpRequests(request -> request
                    .requestMatchers(allowUrls).permitAll()
                    .anyRequest().authenticated())
            .addFilterBefore(loginAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
            .exceptionHandling(config -> config
                    .authenticationEntryPoint(authenticationEntryPoint)
                    .accessDeniedHandler(accessDeniedHandler));

    return http.build();
}

 

현재 springConfig 클래스의 filterChain을 설정하는 코드입니다.

 

 

swagger를 통해 request를 보냈을 시 401 에러가 발생합니다.

 

MDN의 설명에 따르면 401 에러는 다음과 같은 상황에서 발생한다.

 

 
401 Unauthorized :
 응답 상태 코드는 요청된 리소스에 대한 유효한 인증 자격 증명이 없기 때문에 클라이언트 요청이 완료되지 않았음을 나타냅니다.

 

 

permitAll()을 했는데 401이 뜬다라..

 

다시 로그를 확인을 해보았습니다.

 

 

Filter를 거치면서 AuthorizationFilter에서 권한을 확인하는 로그가 찍히던 중 DB에 조회 쿼리가 나가는 것을 확인할 수 있었습니다.

 

스프링 구조 (사진 출처 : https://gowoonsori.com/blog/spring/architecture/)

 

스프링 요청의 처리 구조를 보면 DB에 쿼리가 나간다는 것은 요청이 FilterChain을 거쳐 Spring container의 서비스 레이어까지 도달했다는 것이다.

 

이 말은 FilterChain에 속하는 spring security는 이미 지나쳤다는 말이므로 permitAll() 메서드는 정상 작동하였다.

 

문제 지점 확인

 

Post /member/signUp request가 filter를 거친 후 추가로 Post /error request가 filter를 거치는 것을 볼 수 있었다.

 

Spring Boot는 예외가 발생하면 구체적인 Exception Handler가 존재하지 않을 시 /error 경로로 다시 에러를 재요청한다.

 

Spring Boot의 Basic Error 처리 흐름

 

한번 WAS(톰캣)를 거쳤던 request가 다시 WAS(톰캣)을 거치게 되는 것이다.

 

이 과정에서 필터를 거치며 security filter를 거치게 되고 /error URI는 permitAll()이 되지 않았기 때문에 AccessDeniedException를 던지게된다.

 

최종 문제 발생 원인

ExceptionHandler를 만들어주지 않았기 때문이다.

 

/member/signUp으로 request를 보내면 직접 만들어둔 customException인 NotFoundException이 발생한다.

 

하지만 이를 만들어놓고 해당 예외를 처리해주는 ExceptionHandler를 만들지 않았기 때문에 spring boot의 기본 에러 방식 작동하였다.

 

@ControllerAdvice
public class GlobalExceptionHandler {

    /**
     * 아래와 같은 형식의 예외를 프론트에 던져줍니다
     * {
     * "message": "해당 부서를 찾을 수 없습니다.",
     * "httpStatus": "NOT_FOUND",
     * "timestamp": "2024-02-28T15:28:59.140106",
     * "detail": "해당 Id의 부서가 존재하지 않습니다."
     * }
     */
    @ExceptionHandler
    protected ResponseEntity<ErrorResponse> handleCustomException(CustomException e) {
        ErrorCode errorCode = e.getErrorCode();
        ErrorResponse errorResponse = ErrorResponse.of(errorCode, LocalDateTime.now());
        errorResponse.setDetail(e.getMessage());
        return new ResponseEntity<>(errorResponse, errorCode.getStatus());
    }
}

 

위와 같은 GlobalExceptionHandler 클래스를 만들고 CustomException을 처리해주는 메서드를 작성해주면 NotFoundException이 404 에러로 제대로 처리되는 것을 볼 수 있다.

 

404 에러가 나오는 모습