[Spring Security] 최신! AccessDeniedHandler, AuthenticationEntryPoint 사용하기

2024. 3. 6. 18:27카테고리 없음

728x90

본 포스팅은 Spring Security에서  AccessDeniedHandler, AuthenticationEntryPoint 구현체를 이용하는 것을 다룹니다.

문제 상황

  • 로그인하지 않은 사용자(HTTP STATUS CODE 401), 접근 권한이 없는 사용자(HTTP STATUS 403)가 있을 때, 해당 정보를 ResponseBody에 넘겨주고 싶음.
  • 다른 에러들은 에러가 발생했을 때, CustomException을 정의해서 처리를 했었는데, 위의 401, 403 에러들은 Custom Exception으로 잡으려고해도 잡히지 않았음.
  • 그것은 아래와 같이 controller보다 Security Filter Chain이 먼저 동작하기 때문임.

시도한 방법

  • Global Exception에서 잡으려고 시도함 -> Global Exception보다 Security Filter가 먼저 동작한다는 사실을 깨달음.
  • 많은 포스팅에서 WebSecurityConfigurerAdapter를 이용하여 에러를 처리하고 있었지만, 이는 deprecated 되어 사용할 수가 없음.

해결 방법

  • Spring Security에서는 Status 403에러가 발생하면, AccessDeniedHandler 인터페이스로 핸들링하고, Status 401에러가 발생하면 AuthenticationEntryPoint 인터페이스로 핸들링함.
    • 여기서 AuthenticationEntryPoint 인터페이스는 로그인하지 않은 사용자가 로그인이 필요한 접근을 수행할 때 Exception을 발생시킴
  • 따라서 아래와 같이 Security Config에서 exceptionHandling 메소드를 호출하여 두 개의 Exception을 처리할 수 있음.
  • 두 개의 Exception을 처리하기 위해 아래와 같이 Custom Handler를 정의하였음.
@Configuration
@EnableWebSecurity // Spring Security 지원을 가능하게 함
@EnableGlobalMethodSecurity(securedEnabled = true)
public class WebSecurityConfig {
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http.sessionManagement((sessionManagement) -> sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS))

                .authorizeHttpRequests((authorizeHttpRequests) -> authorizeHttpRequests.requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll() // resources 접근 허용 설정

                        .requestMatchers("/swagger-ui/**", "/v3/api-docs/**").permitAll()
                        .requestMatchers(HttpMethod.GET, "/lecture/**").permitAll()
                        .requestMatchers(HttpMethod.POST, "/user").permitAll()
                        .requestMatchers(HttpMethod.POST, "/lecture/sort").permitAll()
                        .requestMatchers(HttpMethod.POST, "/lecture", "/tutor").hasAuthority("ROLE_ADMIN")
                        .anyRequest().authenticated() // 그 외 모든 요청 인증처리
                )
                .exceptionHandling((e) -> e.accessDeniedHandler(accessDeniedHandler()).authenticationEntryPoint(authenticationEntryPoint()))
                .csrf().disable();


        return http.build();
    }

    @Bean
    public AccessDeniedHandler accessDeniedHandler() {
        return new CustomAccessDeniedHandler();
    }

    @Bean
    public AuthenticationEntryPoint authenticationEntryPoint() {
        return new CustomAuthenticationEntryPoint();
    }


}
@Slf4j(topic = "접근 권한 확인")
public class CustomAccessDeniedHandler implements AccessDeniedHandler {


    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
        log.info(ErrorCode.FORBIDDEN.getMessage());
        response.setStatus(HttpStatus.FORBIDDEN.value());
        response.setContentType("application/json");
        response.setCharacterEncoding("UTF-8");
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("resultCode", ErrorCode.FORBIDDEN.getKey());
        jsonObject.put("message", "접근 권한");
        jsonObject.put("data", ErrorCode.FORBIDDEN.getMessage());
        response.getWriter().print(jsonObject);
    }
}
@Slf4j(topic = "회원 인증 절차")
public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {


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


        log.info(ErrorCode.UNAUTHORIZED.getMessage());
        response.setStatus(HttpStatus.UNAUTHORIZED.value());
        response.setContentType("application/json");
        response.setCharacterEncoding("UTF-8");
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("resultCode", ErrorCode.UNAUTHORIZED.getKey());
        jsonObject.put("message", "접근 권한");
        jsonObject.put("data", ErrorCode.UNAUTHORIZED.getMessage());
        response.getWriter().print(jsonObject);
    }
}