
📌 본 글은 Spring Security 공식 문서 를 기반으로 정리한 글입니다.
Spring Security의 필터 체인 구조, DelegatingFilterProxy의 동작 방식, 요청 저장 및 예외 처리 흐름 등을 예시 코드와 함께 설명합니다.
단, 공식 문서 전체를 번역하거나 완전히 포함한 글은 아니며, 일부 내용을 선별하여 정리하였습니다.


Filter들을 미리 등록해야 함DelegatingFilterProxy를 통해 이 시점 차이를 해결할 수 있다.
⭐️ 요약
- DelegatingFilterProxy는 서블릿 컨테이너와 Spring Context의 초기화 시점 차이를 안전하게 연결해주는 브릿지 역할을 한다.

FilterChainProxy에 디버깅 포인트를 찍으면 모든 필터 동작을 추적할 수 있음./..%2f..%2fadmin)FilterChainProxy는 Spring의 RequestMatcher를 통해 더 복잡한 조건도 가능하다.RequestMatcher matcher = new AndRequestMatcher(
new AntPathRequestMatcher("/admin/**"),
request -> request.getHeader("X-Secret-Key") != null
);⭐️ 요약
- FilterChainProxy는 Sprint Security의 핵심 필터 매니저 역할을 하며, 단순히 서블릿 필터를 위임하는
DelegatingFilterProxy보다 더 많은 역할을 수행한다.- 단순한 URL 패턴 매칭을 넘어, 유연한 요청 조건 처리와 보안 기능 적용을 위한 핵심 컴포넌트이다.
📌 실무에서의 사용
❓ 빈 SecurityFilterChain 설정
SecurityFilterChain은 보안 필터를 하나도 포함하지 않을 수 있다.| 구분 | WebSecurityCustomizer (web.ignoring()) | 빈 SecurityFilterChain 등록 |
|---|---|---|
| 목적 | Spring Security 자체를 완전히 우회 | Spring Security를 거치되, 아무 필터도 적용하지 않음 |
| 적용 시점 | FilterChainProxy 진입 전 | FilterChainProxy 진입 후, 필터가 없어서 바로 통과 |
| 보안 필터 동작 | 아예 동작 안 함 (서블릿 필터 체인 자체를 안 탐) | FilterChainProxy는 실행됨. 단, 필터가 없음 |
| 성능 | 가장 빠름 (Spring Security 필터 아예 안 거침) | 약간 느림 (Security 진입은 하니까) |
| 추천 사용처 | 정적 리소스, 에러 페이지 등 전혀 보안이 필요 없는 요청 | 요청은 보안 대상이지만, 명시적으로 허용해야 할 요청 (예: /public/**, 공개 API) |
| 예시 요청 | /favicon.ico, /css/style.css | /public/info, /docs/open-access |
FilterOrderRegistration를 통해 각 필터의 등록 순서를 확인할 수 있다.HttpSecurity DSL을 통해 선언된다.@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(Customizer.withDefaults())
.httpBasic(Customizer.withDefaults())
.formLogin(Customizer.withDefaults())
.authorizeHttpRequests(authorize -> authorize
.anyRequest().authenticated()
);
return http.build();
}
}
#addFilterBefore(Filter, Class<?>) : 다른 필터 전에 필터를 추가함#addFilterAfter(Filter, Class<?>) : 다른 필터 후에 필터를 추가함#addFilterAt(Filter, Class<?>) : 다른 필터를 필터로 교체함Filter 인터페이스를 직접 구현하는 대신, 요청마다 딱 한 번만 실행되는 필터의 기본 클래스인 OncePerRequestFilter를 상속할 수 있다. (doFilterInternal 메서드 제공)FilterChainProxy를 통해 이미 그 필터를 실행하고 있을 수 있기 때문에,FilterRegistrationBean을 사용해 enabled = false로 설정하여 서블릿 컨테이너에 중복 등록되지 않도록 해야 한다.@Bean
public FilterRegistrationBean<MyCustomFilter> myFilterRegistration(MyCustomFilter filter) {
FilterRegistrationBean<MyCustomFilter> registration = new FilterRegistrationBean<>(filter);
registration.setEnabled(false); // ✅ 컨테이너에 등록되지 않도록 설정
return registration;
}
HttpSecurity DSL을 통해 보안 필터를 자동으로 추가함.addFilterAt()으로 직접 추가하려 하면 중복 등록으로 예외 발생함.disable()로 비활성화해야 함..httpBasic((basic) -> basic.disable())

AccessDeniedException 및 AuthenticationException를 HTTP 응답으로 변환함try {
filterChain.doFilter(request, response);
} catch (AccessDeniedException | AuthenticationException ex) {
if (!authenticated || ex instanceof AuthenticationException) {
startAuthentication();
} else {
accessDenied();
}
}
ExceptionTranslationFilter는 요청을 그대로 다음 필터 체인에 전달함 AuthenticationException)SecurityContextHolder를 비워서 이전 인증 정보 제거RequestCache에 저장AuthenticationEntryPoint를 호출 -> 로그인 페이지로 리다이렉트 또는 401 Unauthorized 응답AccessDeniedException)AccessDeniedHandler 호출 -> 403 Forbidden 응답 반환RequestCache에 저장해 둔다.RequestCache: 원래 요청을 저장해두는 Spring Security의 인터페이스
HttpServletRequest: 사용자가 보낸 원래 요청 객체 (URL, 파라미터 포함)
리플레이(re-request): 로그인 성공 후, 저장된 요청을 다시 보내는 것
ExceptionTranslationFilterRequestCacheAwareFilterRequestCache에 저장된 요청이 있는지 확인해서 다시 실행함HttpSessionRequestCachecontinue라는 파라미터가 있을 때만 RequestCache를 확인하도록 구현 가능@Bean
DefaultSecurityFilterChain springSecurity(HttpSecurity http) throws Exception {
HttpSessionRequestCache requestCache = new HttpSessionRequestCache();
requestCache.setMatchingRequestParameterName("continue");
http
// ...
.requestCache((cache) -> cache
.requestCache(requestCache)
);
return http.build();
}
📌 요청을 저장하지 않으려는 이유
✅ 해결 방법: NullRequestCache
NullRequestCache는 이름 그대로, 요청을 전혀 저장하지 않는 구현체이다.@Bean
SecurityFilterChain springSecurity(HttpSecurity http) throws Exception {
RequestCache nullRequestCache = new NullRequestCache();
http
// ...
.requestCache((cache) -> cache
.requestCache(nullRequestCache)
);
return http.build();
}
logging:
level:
org.springframework.security: TRACE
Spring Security 공식 문서를 읽기 전에는 구조가 너무 복잡하고 어렵게 느껴졌으나 하나씩 따라가며 정리해보니 잘 몰랐던 동작 원리들을 조금씩 이해할 수 있었다. (물론 ChatGPT의 도움을 많이 받았다.😉)
이번 글이 Spring Security의 구조를 이해하는 데 조금이나마 도움이 되었길 바라며, 앞으로도 Spring Security에 대한 내용을 차근차근 더 정리해볼 예정이다. 🙇