Spring Security 작동 원리
Spring Security를 사용하면서 Filter나 사용자 인증, 인가 과정이 명확하지 않아 헷갈렸던 부분들이 있어 정리하도록 한다.
제대로 알고 사용하자!!
Spring Security는 FilterChain을 Servlet Container 기반의 필터 위에서 동작시키기 위해 DelegatingFilterProxy라는 클래스를 이용한다.
DelegatingFilterProxy는 IoC 컨테이너에서 관리하는 빈이 아닌 표준 서블릿 필터를 구현하고 있으며 내부에 위임대상(FilterChainProxy)을 가지고 있다.
따라서 DelegatingFilterProxy는 표준 서블릿 컨테이너와 Spring IoC 컨테이너의 다리 역할을 한다고 보면 된다.
즉, DelegatingFilterProxy는 서블릿 필터이고, Spring IoC 컨테이너가 관리하는 Filter Bean을 갖고 있고, 이 Filter Bean은 FilterChainProxy이며 이 객체 안에서 Security와 관련된 일들이 벌어진다!
위 그림에서 FilterChain은 Servlet container가 관리하는 ApplicationFilterChain이고
Bean Filter가 FilterChainProxy가 된다 ( FilterChainProxy에 SecurityFilterChain을 넣어 작동시킴)
정리를 간단히 하면
- Spring Security를 사용한다면, DelegatingFilterProxy가 생성됨.
- Spring Boot 사용 시 SecurityFilterAutoConfiguration을 통해서 DelegatingFilterProxyRegistrationBean 빈을 생성하며 이 빈은 DelegatingFilterProxy 필터를 생산해내는 빈
- ServletContextInitializer을 통해 서블릿 컨테이너의 필터 체인에 DelegatingFilterProxy을 등록하도록 지원
내부적으로 어떻게 생성이 되는지 간단히 이해만 하면 좋을 것 같다.
이제 Bean으로 등록하여 설정할 수 있는 FilterChainProxy를 살펴보자.
FilterChainProxy 또한 SecurityFilterChain에 처리를 위임하고 있다.
여기서 단순히 SecurityFilterChain이 한 개라는 의미가 아닌 게 핵심이다.
SecurityFilterChain을 여러개 Bean으로 등록해서 특정 url에 대해서 필터를 각기 다르게 설정할 수 있다.
정리
- FilterChain은 하나의 SecurityFilterChain으로 만들어지고 이런 SecurityFilterChain을 여러 개로 Chain을 만들어 사용할 수 있다.
- 실제로 Security에서 제공되는 FilterChainProxy는 SecurityFilterChain을 List로 담고 있다.
그렇다면 Filter를 여러개 두면 우선순위? 적용범위?를 어떻게 나눠야 하는지에 대해서 간단한 예제 코드를 가져와 정리하고 마무리한다.
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.annotation.Order;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@Configuration
@Import({SecurityConfig.FooSecurityConfig.class, SecurityConfig.BarSecurityConfig.class, SecurityConfig.AllSecurityConfig.class})
public class SecurityConfig {
@Order(100)
static class FooSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.mvcMatcher("/foo/**");
}
}
@Order(200)
static class BarSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.mvcMatcher("/bar/**");
}
}
@Order(300)
static class AllSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.mvcMatcher("/**");
}
}
여기에서 코드를 가져왔으며 간단히 설명드리도록 하겠습니다.
SecurityConfig를 @Configuration 빈으로 만들고 안쪽에 static inner 클래스를 작성하여 구현하였고 각각의 클래스들을 빈으로 등록하기 위해 @Import 어노테이션을 이용했다.
여기서 @Order 어노테이션을 붙이는 것이 중요한데, SecurityFilterChain들이 FilterChainProxy의 filterChains List에 담기는 순서가 중요하기 때문이다.
- Order에 따라 Filter의 순서가 다르다.
- AllSecurityConfig의 Order가 가장 낮다면 모든 요청은 /**에 가장 먼저 도달하기 때문에 필터에 걸리게 된다.
- AllSecurityConfig의 Order가 가장 크다면 /foo/** or /bar/** 필터에 걸리고, 맨 마지막에 /** 필터에 걸리게 된다.
- Order가 가장 큰 필터는 아무래도 공통적으로 처리해줘야 할 부분을 넣어주면 좋을 것 같다
따라서 여러 개의 SecurityFilterChain을 만든다면 URL 패턴 정의와 @Order 어노테이션에 따른 필터 순서 조정이 중요하다.
다음 글에서는 SpringSecurity를 통해 SecurityContext, Authentication,Principal과 같은 Security를 이용한 인증 과정, 메서드 시큐리티, 테스트에서 사용자 인증 방법, 권한 체크에 대해서 정리해보겠다.