이 문서는 서블렛 기반 어플리케이션의 고수준 아키텍쳐에 대해 설명한다. 스프링 시큐리티의 Authentication, Authorization과 Protection Against Exploits 은 이 고수준 아키텍쳐로 구현되었다. (이걸 이해해야 스프링 시큐리티의 각 기능을 이해할 수 있다.)

스프링 시큐리티는 클라이언트의 요청을 서블렛(서버)으로 보내기 전에 그 요청을 거르고 거르고 거르고 거르고... 거르는 방식으로 동작한다.
거른다는 동작을 하는 모듈은 Filter라고 하며, 여기서 거른다는 것은 다음의 동작을 일컫는다.
위의 동작들을 하는 Filter들을 묶어서 FilterChain이라고 한다.
이때 거르는 전략은 유저마다, 로그인 시간 (세션이나 토큰 만료), URL마다 천차만별로 달라진다. 이런 변화무쌍한 전략을 구현하기 위하여 Spring Security는 메카니즘 (사용자의 요청을 거르는 큰 흐름)과 정책(범위와 권한을 때에 따라 다르게 적용하는 법)을 분리한다. 여기서 이 메카니즘은 DelegatingFilterProxy 내에서 구현하며, 정책들은 SecurityFilter에서 구현한다.

실제 웹에서 FilterChain은 사용자의 동작마다 수많은 종류가 나올 수 있다. 이 모든 Chain을 한 번에 동작시키기 위한 DelegatingFilterProxy를 제공한다. 이는 Filter의 동작들을 Delegate(위임)하는 Proxy(대리자)이다. 즉, 이는 Filter가 하는 모든 일을 누군가에게 위임하는 객체이다. 서블렛은 각기 다른 Filter 인스턴스들을 등록할 수 있지만 스프링에 등록된 빈들(실제 필터링 객체)에 대해선 알지 못한다. 그래서 기본적인 서블렛 컨테이너 메카니즘에 DelegatingFilterProxy 를 등록하여서 Filter를 구현하는 스프링 Bean들이 하는 모든 일들을 위임시킬 수 있다.
그림에는 DelegatingFilterProxy 내부에 Bean Filter_0 가 있다. 상황에 맞는 Bean Filter_0 를 어딘가에서 가져와서 그에게 필터링 작업을 위임하는 일을 한다. 그 어딘가는 SecurityFilterChain이며, 상황에 맞는 것을 고르는 것은 FilterChainProxy이다.

FilterChainProxy는 SecurityFilterChain 안에 있는 다수의 Filter 인스턴스들에게 보안 작업을 위임한다. FilterChainProxy 또한 Bean이라서, DelegatingFilterProxy가 가지고 있다.

SecurityFilterChain은 여러가지의 SecurityFilter의 집합이다. FilterChainProxy가 현재 요청을 거를 때 어떤 Filter 인스턴스를 쓸 지 정할 때 이 SecurityFilterChain이 사용된다.
아키텍쳐를 원문 그대로 이해한다면 이쯤에서 상당히 헷갈릴 수가 있다. 왜냐면 Filter라는 단어가 '거르는 주체'와 '거름망 종류'의 두 가지 뜻으로 혼용되기 때문이다.
DelegatingFilterProxy는 거름망을 사용하는 주체이며, SecurityFilter가 진짜 거름망이다.
DelegatingFilterProxy 는 FilterChainProxy를 사용해서 상황에 맞는 적절한 SecurityFilterChain을 고른다.
SecurityFilterChain은 Security Filter들로 사용자의 요청 (=HttpServletRequest)를 거른다.
SecurityFilterChain은 Bean으로써 FilterChainProxy 가 등록한다.
DelegatingFilterProxy 와 SecurityFilterChain(=SecurityFilter들의 집합) 들 사이에 FilterChainProxy를 둔 이유는 다음과 같다.
(SecurityContext: 이 모든 Filter 작업이 완료됐을 때 사용자 정보, 암호, 권한을 담아두는 객체)

이 그림은 FilterChainProxy 가 어떤 SecurityFilterChain이 사용될 지 고르는 것을 보여준다. URL과 매칭되는 첫 SecurityFilterChain이 선택된다. 만약에 /api/messages/가 요청된다면 /api/** 패턴을 가진 SecurityFilterChain_0 이 선택될 것이다. SecurityFilterChain_n이 /** 로 매칭되더라도, 처음이 아니기 때문에 매칭되지 않는다.
만약에 그냥 /messages/라는 요청이 들어왔다면 SecurityFilterChain_0 의 /api/** 와 매칭되지 않기 때문에 FilterChainProxy 는 계속해서 다른 SecurityFilterChain들을 시도해본다. 맞는 SecurityFilterChain가 아무것도 없다면, SecurityFilterChain_n이 참조된다.
위의 과정을 통해 Security Filters 는 SecurityFilterChain API와 같이 FilterChainProxy 에 삽입된다. SecuriyFilter는 인증, 권한 부여, 취약점 보호 등의 다양한 일을 한다.
Kotlin
@Configuration
@EnableWebSecurity
class SecurityConfig {
@Bean
fun filterChain(http: HttpSecurity): SecurityFilterChain {
http {
csrf { }
authorizeHttpRequests {
authorize(anyRequest, authenticated)
}
httpBasic { }
formLogin { }
}
return http.build()
}
}
Java
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(Customizer.withDefaults())
.authorizeHttpRequests(authorize -> authorize
.anyRequest().authenticated()
)
.httpBasic(Customizer.withDefaults())
.formLogin(Customizer.withDefaults());
return http.build();
}
}
위는 SecurityFilterChain의 예시이다. 여기서 저 한줄 한줄이 Filter가 되는 것이다.
CsrfFilter, UsernamePasswordAuthenticationFilter, BasicAuthenticationFilter, ...