ExceptionTranslationFilter, FilterSecurityInterceptor, MethodSecurityInterceptor
해당 포스팅은 Spring Security에서 인증(Authentication)과 인가(Authorization)에 대한 예외 처리를 담당하는 ExceptionTranslationFilter과 해당 필터에 이어서 동작하며, 권한을 검사하는 FilterSecurityInterceptor, MethodSecurityInterceptor의 동작 과정에 대해서 살펴본 내용입니다.
- ExceptionTranslationFilter
먼저 ExceptionTranslationFilter의 경우 스프링 시큐리티의 filter chain에서 인증과 인가에 대한 Exception을 터트리는 역할을 하는데요. (AccessDeniedException and AuthenticationException)
주석에서 볼 수 있는 것처럼, 해당 필터는 Java와 HTTP 응답 사이에 브릿지 역할을 하며, 실제 보안에 직접적으로 참여하지는 않습니다.
ExceptionTranslationFilter의 doFilter() 메서드를 살펴보면 try 부분에서 chain.doFilter() 메서드를 통해 다음 동작하는 Filter를 동작시키고, 여기서 발생하는 Exception(AuthenticationException || AccessDeniedException)을 잡아서 handleSpringSecurityException() 메서드를 통해 처리하게 되는데요.
try 부분의 chain.doFilter() 메서드를 통해 다음으로 동작하는 필터가 바로 이어서 살펴볼 SecurityInterceptor입니다.
- FilterSecurityInterceptor, MethodSecurityInterceptor
SecurityInterceptor는 Spring Security의 필터들 중에서 가장 마지막에 위치한 필터인데요. 접근 권한 즉, 인증된 사용자에 대한 인가를 검증하는 역할을 주로 합니다.
FilterSecurityInterceptor와 MethodSecurityInterceptor 두 가지가 있는 이유는 필터 단에서 발생하는 인가 검증을 하느냐, 메서드 단에서 발생하는 인가 검증을 하느냐의 차이인데요.
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/home").permitAll()
.antMatchers("/user").hasAnyRole("USER", "ADMIN")
.anyRequest().anthenticated();
}
이런 식으로 Filter를 통해 권한에 대한 검증을 진행한다면 FilterSecurityInterceptor가 동작하게 되며,
@PreAuthorize("hasRole('USER')")
@GetMapping("/user")
public String user() {
return "/user";
}
@PreAuthorize("hasRole('ADMIN')")
@GetMapping("/admin")
public String admin() {
return "/admin";
}
컨트롤러 단에서 @PreAuthorize, @Secured 등의 어노테이션으로 권한에 대한 검증을 진행하게 된다면 MethodSecurityInterceptor가 동작하게 되는 것입니다.
public class FilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter {
...
}
public class MethodSecurityInterceptor extends AbstractSecurityInterceptor implements MethodInterceptor {
...
}
중요한 것은 이 두 클래스 모두 AbstractSecurityInterceptor라는 추상 클래스를 상속받았다는 것인데요.
실제 동작 부분 역시 super.beforeInvocation(), super.finallyInvocation(), super.afterInvocation()처럼 상위에 있는 AbstractSecurityInterceptor의 메서드를 사용하여 동작하는 것을 볼 수 있습니다.
AbstractSecurityInterceptor에서 실제 권한을 확인하는 것은 AccessDecisionManager가 담당하며, 해당 객체의 decide() 메서드를 통해 동작하게 됩니다.
- 다시 ExceptionTranslationFilter
동작 과정 중 조금 신기했던 부분은 인증(로그인)이 안된 사용자가 접근했을 때, AuthenticationException이 발생하는 것이 아니라 AccessDeniedException으로 먼저 발생한다는 점이었습니다. 이유는 anonymous로 Authentication 객체가 생성된 상태이기 때문인데요.
디버깅을 해보면, 인증(로그인)이 안된 사용자가 접근 시, securityException은 AccessDeniedException인 상태로 handleSpringSecurityException() 메서드가 동작하게 됩니다.
이어지는 과정은 handleSpringSecurityException() 메서드를 거처 handleAccessDeniedException() 메서드가 동작하게 되는데요.
해당 메서드 내부에서 anonymous 여부를 확인하여 sendStartAuthentication() 메서드를 통해 처리되게 되는 것입니다.
< 참고 자료 >
'Programming > Spring Boot' 카테고리의 다른 글
Querydsl 서브쿼리 사용하는 방법(select절, where절) (0) | 2022.08.24 |
---|---|
FeignClient 기본적인 사용법 (Spring Cloud OpenFeign) (0) | 2022.08.12 |
세션을 사용한 스프링 시큐리티 구현(WebSecurityConfigurerAdapter deprecated) (0) | 2022.07.21 |
스프링 시큐리티 SecurityContextHolder에 Authentication(인증) 정보가 저장되는 과정 (0) | 2022.07.13 |
@Valid @Validated 동작 원리 파헤치기 (4) | 2022.07.01 |