Spring Security는 유저에 대한 인증 및 권한처리를 가능하게 해주는 spring 보안 프레임워크입니다.
저는 프로젝트를 진행하면서 @RestControllerAdvice를 사용해
전역적으로 예외 처리를 하도록 하였으나, 기대한 HTTP status code와 에러 메시지와는 달리
403 Fobidden만 응답받을 뿐이었습니다.
이 문제는 User가 로그인을 하지 않은 채, 서비스의 접근할 경우
발생한 예외였습니다. 즉 인증되지 않은 클라이언트가 서버에 요청을 보냈을 때의 발생한 상황이었습니다.
조사해 보니 Spring Security의 Filter Chain으로 발생한 예외는 서블릿 필터 단계에 속하는 부분이기 때문에 @RestControllerAdvice와 같은 어노테이션으로 예외 처리를 불가능하다는 사실을 알았습니다.
그래서 Spring Security가 발생시키는 Exception Handler를 따로 구현해야 할 필요가 있었습니다.
Spring Security에선 AccessDeniedHandler interface와 AuthenticationEntryPoint interface가 존재합니다.
AccessDeniedHandler는 서버에 요청을 할 때 액세스가 가능한지 권한을 체크후 액세스 할 수 없는 요청을 했을 시 동작되고,
AuthenticationEntryPoint는 인증이 되지않은 유저가 요청을 했을 때 동작됩니다.
제가 참여한 프로젝트에선 User의 권한보다 인증의 비중이 더 높아, AuthenticationEntryPoint만 따로 구현해주었습니다.
package com.thesurvey.api.exception;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
/**
* Handles authentication errors that occur during the Spring Security filter chain, such as when a
* user attempts to access a secured endpoint without authentication credentials.
*/
public class AuthenticationEntryPointHandler implements AuthenticationEntryPoint {
// method is called by the Spring Security filter chain when an authentication error occurs.
@Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException, ServletException {
response.setStatus(HttpStatus.UNAUTHORIZED.value());
response.setContentType("text/plain;charset=UTF-8");
response.setCharacterEncoding("UTF-8");
response.getWriter().write("권한이 없습니다.");
}
}
public class SecurityConfiguration {
private final AuthenticationProvider authenticationProvider;
private final SessionFilter sessionFilter;
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
// @formatter:off
return http
.csrf().disable()
.cors().and()
.authorizeRequests()
.antMatchers(
"/v2/api-docs/**",
"/v3/api-docs/**",
"/configuration/**",
"/swagger-ui.html",
"/swagger-ui/**",
"/docs/**"
).permitAll()
.antMatchers("/admin/**").hasAuthority("ADMIN")
.antMatchers("/surveys/**").authenticated()
.antMatchers("/users/**").authenticated()
.antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
.anyRequest().permitAll()
.and()
.exceptionHandling().authenticationEntryPoint(new AuthenticationEntryPointHandler())
.and()
...
AuthenticationEntryPoint interface를 구현한 AuthenticationEntryPointHandler 클래스를 만들고
.exceptionHandling()에 .authenticationEntryPoint(new AuthenticationEntryPointHandler())를 추가해 주었습니다.
참고 자료
https://velog.io/@park2348190/Spring-Security의-Unauthorized-Forbidden-처리