Spring Filter 예외 처리

나도잘몰라·2024년 5월 13일
0

spring

목록 보기
5/5

  • Spring Security는 Spring Context 바깥 쪽의 Filter에서 처리되기 때문에 Spring Context 내에서 예외 처리하는 @ControllerAdivce로 예외 처리할 수 없습니다.
  • Filter 단의 예외는 해당 필터 앞에서 예외 처리 해주는 필터를 통해 핸들링합니다.

SecurityConfig

@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {

    private final JwtAuthenticationFilter jwtAuthenticationFilter;
    private final JwtExceptionFilter jwtExceptionFilter;

    @Bean
    public BCryptPasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
                .csrf(AbstractHttpConfigurer::disable) // rest api : csrf, httpBasic, formLogin 미사용
                .httpBasic(AbstractHttpConfigurer::disable)
                .formLogin(AbstractHttpConfigurer::disable)
                .sessionManagement((sessionManagement) ->
                        sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) // jwt 인증 : session 미사용
                .authorizeHttpRequests((authz) -> authz // 권한 설정
                        .requestMatchers("/auth/**").permitAll() // 로그인, 로그아웃 관련
                        .requestMatchers("/register/**").permitAll() // 회원가입 관련
                        .requestMatchers("/swagger-ui/**", "/v3/**").permitAll() // api 명세 관련
                        .requestMatchers("/admin/**").hasRole("ADMIN") // admin 관련
                        .anyRequest().authenticated()
                )
                .exceptionHandling(authenticationManager -> authenticationManager
                        .authenticationEntryPoint(new CustomAuthenticationEntryPoint())
                        .accessDeniedHandler(new CustomAccessDeniedHandler()))
                .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
                .addFilterBefore(jwtExceptionFilter, JwtAuthenticationFilter.class);
        return http.build();
    }

}
  • 인증과 인가 과정에서 발생한 예외를 처리를 위한 AuthenticationEntryPoint, AccessDeniedHandler
  • JWT 예외 처리를 위한 JwtExceptionFilter

CustomAuthenticationEntryPoint

public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException {
        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
        response.setStatus(HttpStatus.UNAUTHORIZED.value());
        response.setCharacterEncoding("UTF-8");

        final Map<String, Object> body = new HashMap<>();
        body.put("status", ErrorCode.ANONYMOUS_USER.getStatus());
        body.put("error", ErrorCode.ANONYMOUS_USER.getError());
        body.put("message", "로그인이 필요합니다.");
        body.put("path", request.getServletPath());

        final ObjectMapper mapper = new ObjectMapper();
        response.getWriter().write(mapper.writeValueAsString(body));
    }
}

AuthenticationEntryPoint는 인증되지 않은 사용자가 인증이 필요한 요청 엔드포인트로 접근하려 할 때, 예외를 핸들링 할 수 있도록 도와준다. Spring Security의 기본 설정으로는 HttpStatus 401과 함께 스프링의 기본 오류페이지를 보여준다. 이를 커스텀하게 사용하기 위해 구현하였다.


CustomAccessDeniedHandle

public class CustomAccessDeniedHandler implements AccessDeniedHandler {

    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException{
        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
        response.setStatus(HttpStatus.FORBIDDEN.value());
        response.setCharacterEncoding("UTF-8");

        final Map<String, Object> body = new HashMap<>();
        body.put("status", ErrorCode.ACCESS_DENIED.getStatus());
        body.put("error", ErrorCode.ACCESS_DENIED.getError());
        body.put("message", "접근 권한이 없습니다.");
        body.put("path", request.getServletPath());
        final ObjectMapper mapper = new ObjectMapper();
        response.getWriter().write(mapper.writeValueAsString(body));
    }
}

인증은 완료되었으나 요청에 대한 권한을 가지고 있지 않은 사용자가 엔드포인트에 접근하려고 할 때, 예외를 핸들링 할 수 있도록 도와준다. Spring Security의 기본 설정으로는 403 Forbidden 오류과 함께 스프링의 기본 오류 페이지를 보여준다. 이를 JSON 형태로 응답하기 위해 구현하였다.


JwtExceptionFilter

@Component
public class JwtExceptionFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
        try {
            chain.doFilter(request, response);
        } catch (JwtException ex) {
            setErrorResponse(request, response, ex);
        }
    }

    public void setErrorResponse(HttpServletRequest request, HttpServletResponse response, Throwable ex) throws IOException {
        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);

        final Map<String, Object> body = new HashMap<>();
        body.put("status", ErrorCode.JWT_FAILURE.getStatus());
        body.put("error", ErrorCode.JWT_FAILURE.getError());
        body.put("message", ex.getMessage());
        body.put("path", request.getServletPath());
        final ObjectMapper mapper = new ObjectMapper();
        mapper.writeValue(response.getOutputStream(), body);
    }
}

인증 오류가 아닌, JWT 관련 오류는 이 필터에서 잡아내며, 이를 통해 JWT 만료 에러와 인증 에러를 따로 잡아낼 수 있다.

0개의 댓글