Kiến Trúc Filter Chain: Cơ Chế Bảo Vệ Trước Servlet
Tìm hiểu kiến trúc Servlet Filter, vai trò DelegatingFilterProxy, FilterChainProxy và cách cấu hình SecurityFilterChain chuẩn Spring Security 6.
TL;DR: Spring Security triển khai bảo mật dưới dạng một chuỗi Servlet Filter chèn trước DispatcherServlet — request bị từ chối ngay ở tầng web container mà không bao giờ chạm tới controller. DelegatingFilterProxy giải quyết bài toán lệch pha khởi động giữa Tomcat và Spring IoC bằng cơ chế lazy-lookup. Bên trong Spring, FilterChainProxy quản lý các bean SecurityFilterChain chứa khoảng 15 filter chuyên biệt cho từng nhiệm vụ: context, CORS, CSRF, authentication và authorization. Spring Security 6 loại bỏ WebSecurityConfigurerAdapter và bắt buộc dùng lambda DSL.
Tại sao bảo mật lại dựa trên Servlet Filter thay vì Controller?
Nguyên tắc cốt lõi: bảo mật không phải là nghiệp vụ kinh doanh (business logic), mà là điều kiện kiểm soát cổng vào. Mọi truy vấn không hợp lệ cần bị từ chối càng sớm càng tốt để bảo vệ tài nguyên hệ thống.
Nếu triển khai bảo mật qua HandlerInterceptor của Spring MVC, request buộc phải đi qua các bước: so khớp routing, deserialize JSON body và khởi tạo handler method rồi mới bị chặn. Ngược lại, với Servlet Filter, request bị chặn ngay tại tầng Servlet container.

Hệ quả là khi một filter quyết định từ chối (401 Unauthorized hoặc 403 Forbidden), DispatcherServlet và các Controller không bao giờ được gọi.
Servlet Filter hoạt động theo cơ chế chuỗi tuyến tính ra sao?
Tiêu chuẩn Servlet (jakarta.servlet) định nghĩa giao diện Filter với phương thức duy nhất:
public interface Filter {
void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException;
}
Các filter vận hành theo chuỗi mắt xích. Mỗi filter xử lý tiền kỳ, gọi chain.doFilter() để chuyển giao request cho filter tiếp theo, rồi đón nhận kết quả ở hậu kỳ:
public class LoggingFilter implements Filter {
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
long start = System.currentTimeMillis();
// Forward request downstream
chain.doFilter(req, res);
// Process response on return path
log.info("Request duration: {}ms", System.currentTimeMillis() - start);
}
}
Nếu một filter không gọi chain.doFilter(), chuỗi xử lý lập tức ngắt quãng tại điểm đó.
Ba tầng kiến trúc: DelegatingFilterProxy, FilterChainProxy và SecurityFilterChain là gì?
Thay vì đăng ký hàng chục filter riêng lẻ vào container Tomcat, Spring Security trừu tượng hóa hệ thống thành 3 tầng rõ rệt:
| Tầng | Lớp phụ trách | Vai trò chính |
|---|---|---|
| Servlet Container | DelegatingFilterProxy | Cầu nối từ Tomcat sang Spring IoC Container |
| Spring Infrastructure | FilterChainProxy | So khớp URL và chọn SecurityFilterChain phù hợp |
| Application Config | SecurityFilterChain | Danh sách các filter cụ thể cho từng URL pattern |
Bài toán khởi động và DelegatingFilterProxy
Tomcat khởi tạo các Servlet Filter trong giai đoạn ServletContextListener, diễn ra trước khi Spring ApplicationContext được dựng hoàn tất. Nếu đăng ký một Spring bean trực tiếp với Tomcat, container sẽ thất bại vì bean chưa tồn tại.
DelegatingFilterProxy giải quyết vấn đề này bằng kỹ thuật nạp lười (lazy-lookup): tại thời điểm khởi động, nó chỉ ghi nhớ tên bean mục tiêu (springSecurityFilterChain). Đến khi request đầu tiên thực sự truyền tới, Spring context đã sẵn sàng, proxy mới lấy bean từ context và ủy quyền xử lý.

FilterChainProxy điều phối các chain
FilterChainProxy đóng vai trò cổng vào duy nhất. Nó duyệt qua các cấu hình SecurityFilterChain theo thứ tự ưu tiên @Order để tìm ra chain đầu tiên khớp với URL request.
Danh sách 15+ filter chuyên dụng thực hiện nhiệm vụ gì?
Bên trong mỗi SecurityFilterChain là một chuỗi các filter được sắp xếp nghiêm ngặt theo thứ tự trách nhiệm đơn nhất (Single Responsibility Principle):
| Thứ tự tiêu biểu | Filter | Trách nhiệm chính |
|---|---|---|
| 1 | SecurityContextHolderFilter | Nạp SecurityContext từ session/kho lưu trữ vào ThreadLocal |
| 2 | CorsFilter | Xử lý yêu cầu CORS preflight và gắn HTTP headers |
| 3 | CsrfFilter | Xác thực CSRF token cho các request thay đổi trạng thái |
| 4 | UsernamePasswordAuthenticationFilter | Xử lý đăng nhập form login truyền thống |
| 5 | BearerTokenAuthenticationFilter | Bóc tách và xác thực JWT Bearer token |
| 6 | AnonymousAuthenticationFilter | Gán danh tính khách vô danh nếu chưa có xác thực |
| 7 | ExceptionTranslationFilter | Đón bắt ngoại lệ bảo mật và ánh xạ thành mã HTTP 401/403 |
| 8 | AuthorizationFilter | Kiểm tra quyền truy cập (GrantedAuthority) trên tài nguyên |
Thứ tự sắp xếp mang tính quyết định: SecurityContextHolderFilter buộc phải chạy đầu tiên để thiết lập ngữ cảnh cho toàn bộ thread, trong khi ExceptionTranslationFilter phải bọc trước AuthorizationFilter để bắt AccessDeniedException.
Spring Security 6 cấu hình SecurityFilterChain qua Lambda DSL như thế nào?
Spring Security 6 (tích hợp từ Spring Boot 3+) xóa bỏ hoàn toàn lớp kế thừa WebSecurityConfigurerAdapter. Toàn bộ thiết lập chuyển sang bean @Bean SecurityFilterChain với cú pháp lambda DSL:
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable()) // Disable for stateless REST APIs
.sessionManagement(session ->
session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.cors(cors -> cors.configurationSource(corsConfigurationSource()))
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/auth/**", "/public/**").permitAll()
.requestMatchers("/api/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
)
.oauth2ResourceServer(oauth2 ->
oauth2.jwt(Customizer.withDefaults())
);
return http.build();
}
}
Sử dụng lambda DSL giúp mỗi phương thức cấu hình trả về chính thể hiện HttpSecurity, loại bỏ hoàn toàn các lời gọi .and() cồng kềnh của phiên bản cũ.
VirtualFilterChain điều phối request qua từng filter ra sao?
Khi request đi qua FilterChainProxy, nó không duyệt trực tiếp chuỗi của Tomcat mà được gói trong một VirtualFilterChain. Đối tượng nội bộ này giữ con trỏ chỉ mục và lần lượt kích hoạt từng filter đã đăng ký.

Nhờ kiến trúc này, Tomcat ở tầng Servlet chỉ nhận diện đúng một filter duy nhất, trong khi Spring toàn quyền sắp đặt và bổ sung các logic bảo mật phức tạp.
Cơ chế tự động cấu hình (Autoconfiguration) kích hoạt những gì?
Khi bạn khai báo dependency spring-boot-starter-security:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
Spring Boot tự động kích hoạt:
SecurityAutoConfiguration: Đăng kýFilterChainProxyvà chuỗi mặc định yêu cầu đăng nhập toàn bộ endpoint.UserDetailsServiceAutoConfiguration: Tạo sẵn tài khoản in-memoryuservới mật khẩu ngẫu nhiên hiển thị tại log console.- Khi bạn định nghĩa một
@Bean SecurityFilterChaincủa riêng mình, cấu hình mặc định sẽ tự động nhường chỗ.
Những sai lầm phổ biến khi thiết lập Filter Chain là gì?
- Kế thừa WebSecurityConfigurerAdapter: Lớp này đã bị loại bỏ hoàn toàn trong Spring Security 6. Cần chuyển sang khai báo
@Bean SecurityFilterChain. - Sử dụng antMatchers(): Phương thức cũ đã bị thay thế bởi
requestMatchers(), có khả năng nhận biết ngữ cảnhDispatcherServletthông minh hơn. - Dùng sai HandlerInterceptor cho Auth: Interceptor chạy sau khi đã tiêu tốn tài nguyên parse HTTP request body, không thể thay thế Servlet Filter cho các tác vụ gating bảo mật.
Những điểm mấu chốt cần ghi nhớ về Filter Chain là gì?
- Gating ở tầng Servlet: Ngăn chặn truy cập bất hợp pháp ngay trước khi
DispatcherServletkhởi chạy. - Cơ chế 3 tầng:
DelegatingFilterProxykết nối Tomcat với Spring;FilterChainProxyđịnh tuyến;SecurityFilterChainlưu danh sách filter. - Quy tắc thứ tự nghiêm ngặt: Khởi tạo context đầu chuỗi, xác thực danh tính ở giữa, và kiểm tra phân quyền ở cuối.
- Lambda DSL hiện đại: Định hình cấu hình rõ ràng, module hóa cao và tương thích chuẩn Spring Boot 3+.
Bạn đã hiểu rõ kiến trúc Filter Chain của Spring Security chưa?
- Q1Tại sao Spring Security lại được thiết kế dựa trên Servlet Filter thay vì HandlerInterceptor của Spring MVC?
- Q2Vai trò then chốt của DelegatingFilterProxy trong kiến trúc Spring Security là gì?
- Q3Trong chuỗi SecurityFilterChain mặc định, filter nào bắt buộc phải thực thi đầu tiên?
- Q4Cú pháp nào sau đây là chuẩn mực khi cấu hình phân quyền trong Spring Security 6 (Spring Boot 3+)?
- Q5Nếu một ứng dụng có 2 bean SecurityFilterChain được đánh số @Order(1) và @Order(2), quy trình xử lý của FilterChainProxy diễn ra như thế nào?
Bài tiếp theo: Authentication flow & SecurityContext
Bài này có giúp bạn hiểu bản chất không?
Bài này đáng gửi cho bạn học cùng?
Copy link đã gắn nguồn — dán group, chat, hoặc LinkedIn.
Security Architecture & Filter Chain — tổng quan
Bài tiếpLuồng Xác Thực và SecurityContext Trong ThreadLocal
Lộ trình Junior Java Backend Developer
Chặng 18/21 · Authentication & Authorization — bài 2/19 của chặng
Thảo luận & góp ý
Hỏi đáp về bài này