12.16 TIL - Spring Security 예외 처리

이서준·2025년 12월 16일

SpringSecurity

목록 보기
3/4

Spring Security 예외 처리

기존 예외 처리 (Filter)

//1. 예외 응답 만들기
CustomException exception = new CustomException(ExceptionCode.FORBIDDEN);
CommonResponse<String> result = new CommonResponse<>(
exception.getExceptionCode().getStatus(), exception.getMessage());

//2. 응답 설정 : HttpStatus, ContentType 및 인코딩
response.setStatus(result.getCode());
response.setContentType("application/json; charset=UTF-8");

//3. object -> json으로 직렬화
String json = objectMapper.writeValueAsString(result);

//4. 응답에 json 작성
response.getWriter().write(json);
  • Filter에서 응답을 만들어서 보냄
  • 다른 Filter가 있다면 중복 코드 발생
  • SpringSecurity의 예외 흐름과 분리
  • 인증/인가 예외의 책임 분리 되지 않음

공식 사이트

  • 인증/인가 예외를 처리할 수 있는 확장 포인트 존재
  • 인증 예외 → AuthenticationEntryPoint
  • 인가 예외 → AccessDeniedHandler

AuthenticationEntryPoint

  • 인증되지 않은 사용자가 보호된 리소스에 접근할 때 호출
@Component
@RequiredArgsConstructor
public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {

    private final ObjectMapper objectMapper;

    @Override
    public void commence(
    	HttpServletRequest request, 
        HttpServletResponse response, 
        AuthenticationException authException) throws IOException, ServletException {

        String message = "인증이 필요합니다.";

        CommonResponse<Void> commonResponse = new CommonResponse<>(false, message, null);

        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        response.setContentType("application/json; charset=UTF-8");

        String json = objectMapper.writeValueAsString(commonResponse);

        response.getWriter().write(json);
    }
}

AccessDeniedHandler

  • 인증은 통과했지만, 권한이 없는 경우 호출
  • ex) USER권한을 가진 사용자가 ADMIN API에 접근할 때
@Component
@RequiredArgsConstructor
public class CustomAccessDeniedHandler implements AccessDeniedHandler {

    private final ObjectMapper objectMapper;

    @Override
    public void handle(
    		HttpServletRequest request, 
            HttpServletResponse response, 
            AccessDeniedException accessDeniedException) throws IOException, ServletException {

        String message = "접근 권한이 없습니다.";

        CommonResponse<Void> commonResponse = new CommonResponse<>(false, message, null);

        response.setStatus(HttpServletResponse.SC_FORBIDDEN);
        response.setContentType("application/json; charset=UTF-8");

        String json = objectMapper.writeValueAsString(commonResponse);

        response.getWriter().write(json);
    }
}

Spring Security Config

@Configuration
@EnableWebSecurity
@EnableMethodSecurity
@RequiredArgsConstructor
public class SecurityConfig {

    private final JwtFilter jwtFilter;
    private final CustomUserDetailService customUserDetailService;
    private final CustomAccessDeniedHandler customAccessDeniedHandler;
    private final CustomAuthenticationEntryPoint customAuthenticationEntryPoint;

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        return http
                .csrf(AbstractHttpConfigurer::disable)
                .httpBasic(AbstractHttpConfigurer::disable)
                .formLogin(AbstractHttpConfigurer::disable)
                .cors(cors -> cors.configurationSource(corsConfigurationSource()))
                .userDetailsService(customUserDetailService)
                .addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class)
                .exceptionHandling(e ->
                        e.authenticationEntryPoint(customAuthenticationEntryPoint)
                                .accessDeniedHandler(customAccessDeniedHandler)
                )
                .authorizeHttpRequests(auth -> auth
                        .requestMatchers("/api/users", "/api/auth/login").permitAll()
                        .anyRequest().authenticated()
                )
                .build();
    }
 }
  • 위에서 만든 두 Handler를 config에 설정
  • Spring Security가 자동으로 분기 처리

전체 흐름

요청
⬇️
JwtFilter (토큰 검증)
⬇️
SecurityContextHolder 설정 여부
⬇️
[인증 실패] ➡️ AuthenticationEntryPoint
[인가 실패] ➡️ AccessDeniedHandler
[정상] ➡️ Controller
  • 예외 책임이 명확해짐
  • Filter는 인증 로직에만 집중
  • Security는 예외 흐름과 자연스럽게 통합
  • 응답 포맷을 중앙에서 통제 가능
  1. JwtFilter에서 예외를 직접 응답하지 말기
    a. AuthenticationEntryPoint로 위임하는 구조가 이상적
    b. Filter는 throw 또는 SecurityContext 미설정만 담당
  2. Method Security (@PreAuthorize) 연계
    a. @EnableMethodSecurity 사용시 메서드 레벨 권한 예외도 AccessDeniedHandler가 처리
profile
Allons-y

0개의 댓글