이번 포스트에서는 Servlet Application에서 동작하는 Spring Security의 Filter Chain에 대해서 살펴보자.
Filter
Filter와 DelegatingFilterProxy
Servlet 기반의 Application에서 동작하는 Spring Security는 filter에서부터 시작한다. Filter는 Container가 요청을 접수한 후 어떤한 Servlet 요청을 처리하기 전에 동작하는 웹 컴포넌트이다. 이 filter를 통해서 Servlet에서 필요한 전/후 처리를 모듈화할 수 있다.
이 필터는 하나만 존재하는 것은 아니고 목적에 따라 여러가지가 존재한다. 쉽게 생각하면 encoding, logging, session 관리를 위한 필터등을 예로 들수 있다.
이필터들은 따로 따로 동작하지 않고 연결되서 동작한다. 공기청정기가 공기를 깨끗하게 하기 위해 여러 필터를 거치는 것과 같은 이치이다.
이 Filter는 Spring과 무관한 기술이다. 따라서 Spring Framework의 다양한 빈들을 사용하는게 기본적으로 말이 안된다. 이에 Spring에서는DelegatingFilterProxy라는 이름의 filter를 제공한다.
DelegatingFilterProxy는 Servlet 컨테이너의 라이프사이클과 Spring의 ApplicationContext 사이의 브릿지 역할을 수행한다. 기본 동작은 Spring의 ApplicationContext에서 @Bean으로 선언된 Filter의 목록을 가져와서 실행시켜준다.
DelegatingFilterProxy는 getFilterBean() 메서드로 빈을 가져오면서 Lazy 하게 가져온다. 왜냐면 일반적으로 Spring은 ContextLoaderListener를 사용해서 Spring Bean을 로드하는데 이 작업은 Filter 등록 후이기 때문이다.
FilterChainProxy과 SecurityFilterChain
Spring Security에서 추가로FilterChainProxy라는 특별한 필터를 제공한다. FilterChainProxy는 빈이면서 Filter인데 주요 역할은 SecurityFilterChain을 통해서 많은 Filter 객체로 처리를 위임한다. FilterChainProxy는 DelegatingFilterProxy로 래핑된다. (Filter로 등록되는 것은 DelegatingFilterProxy인데 동작하는 것은 FilterChainProxy)
FilterChainProxy는 모든 보안 필터들을 관리하고 조율하는 역할을 수행한다. 우리가 만드는 SecurityFilter들은 직접 Servlet Container에 등록되지 않고 FilterChainProxy가 사용하는 SecurityFilterChain에 의해 FilterChainProxy를 통해서 관리한다.
디버깅이 쉬워진다. 모든 보안 관련 처리가 FilterChainProxy를 통과하므로 여기에서 디버깅 하면 보안 관련 문제를 쉽게 추적할 수 있다.
메모리 관리가 편해진다. 현재 로그인한 사용자 정보를 SecurityContext에 담아서 ThreadLocal에서 관리하는데 FilterChainProxy는 요청 처리가 끝나면 자동으로 이를 정리한다.
보안이 강화된다. HttpFirewall을 적용되어있어 경로조작 시도(/../), 이중 슬래시(//), 세미콜론 주입(;) 등 다양한 보안 공격으로부터 애플리케이션을 보호한다.
무엇보다 큰 장점은 다양한 조건에 따라 보안 필터를 적용할 수 있다. 기본적인 Servlet의 filter는 url-mapping 만으로 필터를 적용하지만 FilterChainProxy를 이용하면 url-mapping과 함께 메서드나 IP 등 여러가지 조건을 적용할 수 있다.
SecurityFilterChain은 현재의 요청에 대해 호출해야 하는 Spring의 보안 필터 객체를 결정하기 위해 FilterChainProxy에서 사용된다.
Multiple SecurityFilterChain
FilterChainProxy는 여러개의 SecurityFilterChain을 가지고 있다가 상황에 따라 적절한 SecurityFilterChain을 결정할 수 있는데 순서 기반으로 처음 매칭된 것을 사용한다.
Multiple SecurityFilterChain
만약 /api/messages/로 요청이 오는 경우는 SecurityFilterChain0이 동작한다.
/messages/로 요청이 오는 경우 SecurityFilterChain0에서 적용되지 않으므로 FilterChainProxy는 다음 SecurityFilterChain을 순서대로 찾아보고 최종적으로 /**에서 처리된다.
다음은 여러개의 SecurityFilterChain을 동작시키는 예이다.
// API endpoint용@Bean@Order(1)public SecurityFilterChain apiFilterChain(HttpSecurity http)throws Exception {
http.securityMatcher("/api/**") // API 경로에만 이 설정 적용
.authorizeHttpRequests(authorize -> authorize
.requestMatchers("/api/public/**").permitAll()
.requestMatchers("/api/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()).
httpBasic(Customizer.withDefaults());
return http.build();
}
// 웹 애플리케이션용 보안 설정@Bean@Order(2)public SecurityFilterChain webFilterChain(HttpSecurity http)throws Exception {
http.authorizeHttpRequests(authorize -> authorize
.requestMatchers("/public/**").permitAll()
.requestMatchers("/admin/**").hasRole("ADMIN")
.anyRequest().authenticated())
.formLogin(form -> form.loginPage("/login").permitAll());
return http.build();
}
@Order 어노테이션을 사용해 필터 체인의 우선순위 지정
/api/** 경로와 일반 웹 경로에 대해 다른 보안 규칙 적용
API 엔드포인트는 HTTP Basic 인증 사용하며 웹 경로는 폼 로그인 방식 사용
각 필터 체인은 securityMatcher()로 적용 범위 지정 가능
SecurityFilter
SecurityFilter는 SecurityFilterChain API를 통해 FilterChainProxy에 삽입되는데 사용자 인증(authentication), 권한 부여(authorization), 공격으로 부터의 보호등 다양한 기능을 수행한다. 이러한 필터들은 특정 순서로 실행되서 적시에 호출되도록 보장되어야 한다. 예를 들어 인증필터와 권한 필터가 있을 때 당연히 인증 필터가 먼저 호출되어야 한다.
다음은 일반적으로 SecurityFilterChain에서 사용하는 Filter의 목록이다.
Security filter chain: [
DisableEncodeUrlFilter // URL 인코딩 비활성화 (보안상의 이유로)
WebAsyncManagerIntegrationFilter // 비동기 웹 요청에 대한 SecurityContext 통합 관리
SecurityContextHolderFilter // SecurityContext를 스레드 로컬 저장소에 설정
HeaderWriterFilter // 보안 관련 HTTP 응답 헤더 추가 (X-Frame-Options 등)
CsrfFilter // CSRF(Cross-Site Request Forgery) 공격 방지
LogoutFilter // 로그아웃 처리 및 관련 핸들러 실행
UsernamePasswordAuthenticationFilter // 사용자명/비밀번호 기반 인증 처리
RequestCacheAwareFilter // 인증 전 요청된 URL 캐싱 및 리다이렉트 관리
SecurityContextHolderAwareRequestFilter // HttpServletRequest에 보안 관련 메서드 추가
RememberMeAuthenticationFilter // 'Remember Me' 토큰 기반 자동 로그인 처리
AnonymousAuthenticationFilter // 인증되지 않은 사용자에게 익명 인증 객체 제공
ExceptionTranslationFilter // 보안 관련 예외(인증/인가 실패) 처리
AuthorizationFilter // 최종적인 요청 권한 검증 및 접근 제어
]