引言
在现代 Web 应用开发中,安全控制是不可或缺的重要组成部分。Spring Security 作为 Spring 生态中最强大的安全框架,为开发者提供了全面的认证(Authentication)和授权(Authorization)解决方案。而 Filter(过滤器) 正是 Spring Security 实现安全控制的核心机制之一。
本文将深入探讨 Spring Security 中的过滤器机制,重点讲解如何创建自定义 Filter 并将其无缝集成到 Spring Security 的过滤器链中。我们将从基础概念入手,逐步深入到实际应用场景,通过丰富的代码示例帮助你掌握这一重要技能。
Spring Security 过滤器机制概述
什么是 Filter?
在 Java Web 开发中,Filter 是 Servlet 规范的一部分,用于在请求到达目标资源(如 Servlet、Controller)之前或响应返回客户端之前对请求和响应进行预处理或后处理。Filter 可以执行诸如日志记录、身份验证、数据压缩、字符编码设置等操作。
Spring Security 正是基于 Servlet Filter 构建的,它通过一系列有序的 Filter 来实现完整的安全控制流程。
Spring Security 过滤器链的工作原理
Spring Security 的核心是一个名为 FilterChainProxy 的特殊 Filter,它负责管理所有 Spring Security 相关的 Filter。
每个 Filter 都有特定的职责:
- SecurityContextPersistenceFilter: 在请求开始时加载 SecurityContext,在请求结束时清除它
- UsernamePasswordAuthenticationFilter: 处理基于表单的登录认证
- BearerTokenAuthenticationFilter: 处理 JWT Token 认证
- AnonymousAuthenticationFilter: 为未认证用户提供匿名身份
- ExceptionTranslationFilter: 处理安全相关的异常
- FilterSecurityInterceptor: 执行最终的授权决策
为什么需要自定义 Filter?
虽然 Spring Security 提供了丰富的内置 Filter,但在实际项目中,我们经常需要实现一些特定的业务逻辑,比如:
- 📊 自定义认证方式:如短信验证码登录、第三方 OAuth2 登录
- 🛡️ 增强安全控制:如 IP 白名单检查、频率限制、防重放攻击
- 📝 日志和监控:记录用户操作日志、性能监控
- 🔁 数据预处理:请求参数解密、响应数据加密
- 🌐 多租户支持:根据域名或请求头识别租户信息
这些需求往往无法通过简单的配置实现,需要我们创建自定义 Filter 并将其集成到 Spring Security 的过滤器链中。
创建自定义 Filter
继承 OncePerRequestFilter
Spring 提供了 OncePerRequestFilter 抽象类,它是创建自定义 Filter 的最佳选择。这个类确保每个请求只被过滤一次,即使在包含(include)或转发(forward)的情况下也是如此。
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class CustomSecurityFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain)
throws ServletException, IOException {
// 在这里实现自定义逻辑
// 继续执行过滤器链
filterChain.doFilter(request, response);
}
}
实现 Filter 接口
你也可以直接实现 javax.servlet.Filter 接口,但需要注意处理多次调用的问题:
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class CustomFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// 初始化逻辑
}
@Override
public void doFilter(ServletRequest request,
ServletResponse response,
FilterChain chain)
throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
// 自定义逻辑
chain.doFilter(request, response);
}
@Override
public void destroy() {
// 清理资源
}
}
使用 @Component 注解
为了让 Spring 容器管理你的自定义 Filter,可以使用 @Component 注解:
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
@Component
public class LoggingFilter extends OncePerRequestFilter {
private static final Logger logger = LoggerFactory.getLogger(LoggingFilter.class);
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain)
throws ServletException, IOException {
long startTime = System.currentTimeMillis();
String requestURI = request.getRequestURI();
String method = request.getMethod();
logger.info("开始处理请求: {} {}", method, requestURI);
try {
filterChain.doFilter(request, response);
} finally {
long duration = System.currentTimeMillis() - startTime;
logger.info("请求处理完成: {} {} 耗时: {}ms", method, requestURI, duration);
}
}
}
将自定义 Filter 加入 Spring Security 过滤器链
方法一:使用 addFilterBefore/addFilterAfter/addFilterAt
这是最常用的方法,通过 HttpSecurity 配置来指定自定义 Filter 的位置。
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
private final CustomSecurityFilter customSecurityFilter;
public SecurityConfig(CustomSecurityFilter customSecurityFilter) {
this.customSecurityFilter = customSecurityFilter;
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authz -> authz
.requestMatchers("/public/**").permitAll()
.anyRequest().authenticated()
)
.formLogin(form -> form
.loginPage("/login")
.permitAll()
)
// 在 UsernamePasswordAuthenticationFilter 之前添加自定义 Filter
.addFilterBefore(customSecurityFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}
方法二:使用 addFilterAt
如果你需要将自定义 Filter 放在特定位置,可以使用 addFilterAt:
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authz -> authz
.anyRequest().authenticated()
)
// 替换原有的 UsernamePasswordAuthenticationFilter
.addFilterAt(customAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
方法三:完全自定义过滤器链
在某些复杂场景下,你可能需要完全控制过滤器链的构建:
@Bean
public SecurityFilterChain customFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authz -> authz
.anyRequest().authenticated()
)
.setSharedObject(FilterChainProxy.class, createCustomFilterChain());
return http.build();
}
private List<Filter> createCustomFilterChain() {
List<Filter> filters = new ArrayList<>();
filters.add(new SecurityContextPersistenceFilter());
filters.add(new CustomSecurityFilter());
filters.add(new UsernamePasswordAuthenticationFilter());
filters.add(new FilterSecurityInterceptor());
return filters;
}
实际应用案例
案例一:IP 白名单过滤器
在某些企业应用中,可能需要限制只有特定 IP 地址才能访问系统。我们可以创建一个 IP 白名单过滤器:
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
@Component
public class IpWhitelistFilter extends OncePerRequestFilter {
@Value("${security.ip.whitelist:}")
private String ipWhitelist;
private List<String> allowedIps;
@Override
protected void initFilterBean() throws ServletException {
if (ipWhitelist != null && !ipWhitelist.trim().isEmpty()) {
allowedIps = Arrays.stream(ipWhitelist.split(","))
.map(String::trim)
.collect(Collectors.toList());
} else {
allowedIps = Arrays.asList(); // 空列表表示不限制
}
}
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain)
throws ServletException, IOException {
// 如果没有配置白名单,直接放行
if (allowedIps.isEmpty()) {
filterChain.doFilter(request, response);
return;
}
String clientIp = getClientIpAddress(request);
if (!allowedIps.contains(clientIp)) {
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
response.getWriter().write("Access denied: Your IP is not in the whitelist");
return;
}
filterChain.doFilter(request, response);
}
private String getClientIpAddress(HttpServletRequest request) {
String xForwardedFor = request.getHeader("X-Forwarded-For");
if (xForwardedFor != null && !xForwardedFor.isEmpty()) {
// X-Forwarded-For 可能包含多个 IP,取第一个
return xForwardedFor.split(",")[0].trim();
}
String xRealIp = request.getHeader("X-Real-IP");
if (xRealIp != null && !xRealIp.isEmpty()) {
return xRealIp;
}
return request.getRemoteAddr();
}
}
在配置文件中添加白名单配置:
# application.yml
security:
ip:
whitelist: 192.168.1.100,10.0.0.50,127.0.0.1
然后在 SecurityConfig 中添加这个过滤器:
@Configuration
@EnableWebSecurity
public class SecurityConfig {
private final IpWhitelistFilter ipWhitelistFilter;
public SecurityConfig(IpWhitelistFilter ipWhitelistFilter) {
this.ipWhitelistFilter = ipWhitelistFilter;
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authz -> authz
.anyRequest().authenticated()
)
// 在最前面添加 IP 白名单过滤器
.addFilterBefore(ipWhitelistFilter, SecurityContextPersistenceFilter.class);
return http.build();
}
}
案例二:JWT Token 认证过滤器
对于 RESTful API 应用,通常使用 JWT Token 进行无状态认证。我们可以创建一个自定义的 JWT 认证过滤器:
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.crypto.SecretKey;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Date;
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final UserDetailsService userDetailsService;
private final SecretKey secretKey;
public JwtAuthenticationFilter(UserDetailsService userDetailsService) {
this.userDetailsService = userDetailsService;
// 从配置文件读取密钥,这里为了演示使用固定值
this.secretKey = Keys.hmacShaKeyFor("your-secret-key-here".getBytes(StandardCharsets.UTF_8));
}
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain)
throws ServletException, IOException {
String token = extractTokenFromRequest(request);
if (token != null && validateToken(token)) {
String username = extractUsernameFromToken(token);
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authentication);
}
filterChain.doFilter(request, response);
}
private String extractTokenFromRequest(HttpServletRequest request) {
String bearerToken = request.getHeader("Authorization");
if (bearerToken != null && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7);
}
return null;
}
private boolean validateToken(String token) {
try {
Jwts.parserBuilder()
.setSigningKey(secretKey)
.build()
.parseClaimsJws(token);
return true;
} catch (Exception e) {
// Token 无效或已过期
return false;
}
}
private String extractUsernameFromToken(String token) {
Claims claims = Jwts.parserBuilder()
.setSigningKey(secretKey)
.build()
.parseClaimsJws(token)
.getBody();
return claims.getSubject();
}
// 生成 JWT Token 的方法(用于登录接口)
public String generateToken(String username) {
return Jwts.builder()
.setSubject(username)
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + 86400000)) // 24小时过期
.signWith(secretKey, SignatureAlgorithm.HS256)
.compact();
}
}
对应的 SecurityConfig 配置:
@Configuration
@EnableWebSecurity
public class SecurityConfig {
private final JwtAuthenticationFilter jwtAuthenticationFilter;
public SecurityConfig(JwtAuthenticationFilter jwtAuthenticationFilter) {
this.jwtAuthenticationFilter = jwtAuthenticationFilter;
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf().disable() // JWT 通常禁用 CSRF
.authorizeHttpRequests(authz -> authz
.requestMatchers("/api/auth/login").permitAll()
.requestMatchers("/api/public/**").permitAll()
.anyRequest().authenticated()
)
// 在 UsernamePasswordAuthenticationFilter 之后添加 JWT 过滤器
.addFilterAfter(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
@Bean
public UserDetailsService userDetailsService() {
// 这里应该从数据库加载用户信息
return username -> {
// 模拟用户数据
if ("admin".equals(username)) {
return org.springframework.security.core.userdetails.User
.withUsername("admin")
.password("{noop}password") // {noop} 表示不使用密码编码
.roles("ADMIN")
.build();
}
throw new UsernameNotFoundException("User not found");
};
}
}
登录控制器示例:
@RestController
@RequestMapping("/api/auth")
public class AuthController {
private final JwtAuthenticationFilter jwtAuthenticationFilter;
private final AuthenticationManager authenticationManager;
public AuthController(JwtAuthenticationFilter jwtAuthenticationFilter,
AuthenticationManager authenticationManager) {
this.jwtAuthenticationFilter = jwtAuthenticationFilter;
this.authenticationManager = authenticationManager;
}
@PostMapping("/login")
public ResponseEntity<?> login(@RequestBody LoginRequest loginRequest) {
try {
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
loginRequest.getUsername(),
loginRequest.getPassword()
)
);
SecurityContextHolder.getContext().setAuthentication(authentication);
String token = jwtAuthenticationFilter.generateToken(loginRequest.getUsername());
return ResponseEntity.ok(new JwtResponse(token));
} catch (BadCredentialsException e) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
.body("Invalid username or password");
}
}
public static class LoginRequest {
private String username;
private String password;
// getters and setters
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public String getPassword() { return password; }
public void setPassword(String password) { this.password = password; }
}
public static class JwtResponse {
private String token;
public JwtResponse(String token) {
this.token = token;
}
public String getToken() { return token; }
}
}
案例三:请求频率限制过滤器
为了防止恶意用户对 API 进行频繁调用,我们可以实现一个简单的频率限制过滤器:
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
@Component
public class RateLimitingFilter extends OncePerRequestFilter {
// 存储每个 IP 的请求计数和时间戳
private final ConcurrentHashMap<String, RequestInfo> requestCounts = new ConcurrentHashMap<>();
// 每分钟最大请求数
private static final int MAX_REQUESTS_PER_MINUTE = 100;
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain)
throws ServletException, IOException {
String clientIp = getClientIpAddress(request);
long currentTime = System.currentTimeMillis();
RequestInfo requestInfo = requestCounts.computeIfAbsent(clientIp, k -> new RequestInfo());
synchronized (requestInfo) {
// 清理过期的请求记录(超过1分钟的)
if (currentTime - requestInfo.lastResetTime > 60000) {
requestInfo.count.set(0);
requestInfo.lastResetTime = currentTime;
}
int currentCount = requestInfo.count.incrementAndGet();
if (currentCount > MAX_REQUESTS_PER_MINUTE) {
response.setStatus(HttpServletResponse.SC_TOO_MANY_REQUESTS);
response.getWriter().write("Too many requests. Please try again later.");
return;
}
}
filterChain.doFilter(request, response);
}
private String getClientIpAddress(HttpServletRequest request) {
String xForwardedFor = request.getHeader("X-Forwarded-For");
if (xForwardedFor != null && !xForwardedFor.isEmpty()) {
return xForwardedFor.split(",")[0].trim();
}
String xRealIp = request.getHeader("X-Real-IP");
if (xRealIp != null && !xRealIp.isEmpty()) {
return xRealIp;
}
return request.getRemoteAddr();
}
private static class RequestInfo {
AtomicInteger count = new AtomicInteger(0);
long lastResetTime = System.currentTimeMillis();
}
}
在 SecurityConfig 中添加:
@Configuration
@EnableWebSecurity
public class SecurityConfig {
private final RateLimitingFilter rateLimitingFilter;
public SecurityConfig(RateLimitingFilter rateLimitingFilter) {
this.rateLimitingFilter = rateLimitingFilter;
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authz -> authz
.anyRequest().authenticated()
)
// 在最前面添加频率限制过滤器
.addFilterBefore(rateLimitingFilter, SecurityContextPersistenceFilter.class);
return http.build();
}
}
过滤器执行顺序详解
理解 Spring Security 过滤器的执行顺序对于正确实现自定义 Filter 至关重要。
关键过滤器说明
- SecurityContextPersistenceFilter: 最重要的过滤器之一,负责在请求开始时从 Session 中恢复 SecurityContext,在请求结束时保存 SecurityContext。如果你的自定义 Filter 需要访问 SecurityContext,必须放在这个过滤器之后。
- UsernamePasswordAuthenticationFilter: 处理表单登录的过滤器。如果你要实现自定义的表单登录逻辑,通常需要替换或扩展这个过滤器。
- BearerTokenAuthenticationFilter: 处理 JWT Token 认证的过滤器。对于 RESTful API,通常需要在这个位置或附近添加自定义的 Token 处理逻辑。
- AnonymousAuthenticationFilter: 为未认证的用户提供匿名身份。如果你的应用需要区分"未认证"和"匿名用户",需要注意这个过滤器的位置。
- ExceptionTranslationFilter: 处理认证和授权异常的过滤器。它会捕获
AuthenticationException和AccessDeniedException,并根据情况进行重定向或返回错误响应。 - FilterSecurityInterceptor: 最后的守门员,负责执行最终的授权决策。所有在它之前的 Filter 都应该完成认证和权限设置。
选择合适的插入位置
当你决定在哪里插入自定义 Filter 时,需要考虑以下因素:
- 是否需要访问 SecurityContext? 如果需要,必须放在
SecurityContextPersistenceFilter之后 - 是否需要处理认证? 如果是新的认证方式,通常放在
UsernamePasswordAuthenticationFilter附近 - 是否需要处理异常? 如果需要自定义异常处理,应该放在
ExceptionTranslationFilter之前 - 是否需要在授权前执行? 如果是预处理逻辑,应该放在
FilterSecurityInterceptor之前
常见问题和最佳实践
1. Filter 执行多次的问题
在使用 OncePerRequestFilter 时,通常不会遇到这个问题。但如果你直接实现 Filter 接口,需要注意在转发(forward)或包含(include)的情况下,Filter 可能会被多次调用。
解决方案: 使用 OncePerRequestFilter 或在 Filter 中添加标志位来避免重复执行。
2. 异常处理
在自定义 Filter 中抛出的异常可能会被 Spring Security 的异常处理机制捕获,导致你无法按预期处理异常。
解决方案: 在 Filter 中捕获异常并手动设置响应:
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain)
throws ServletException, IOException {
try {
// 自定义逻辑
filterChain.doFilter(request, response);
} catch (CustomSecurityException e) {
// 手动处理异常
response.setStatus(HttpStatus.FORBIDDEN.value());
response.setContentType("application/json");
response.getWriter().write("{\"error\": \"" + e.getMessage() + "\"}");
}
}
3. 性能考虑
Filter 会在每个请求中执行,因此性能至关重要。避免在 Filter 中执行耗时操作,如数据库查询、网络调用等。
最佳实践:
- 使用缓存来存储频繁访问的数据
- 异步处理非关键逻辑
- 对于复杂的认证逻辑,考虑使用专门的认证服务
4. 线程安全
Filter 是单例的,会被多个线程同时调用,因此必须保证线程安全。
最佳实践:
- 避免使用实例变量存储请求相关数据
- 使用 ThreadLocal 存储线程特定的数据
- 使用并发安全的集合类
5. 测试自定义 Filter
为自定义 Filter 编写单元测试和集成测试非常重要:
@Test
public void testIpWhitelistFilter_AllowedIp_ShouldProceed() throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest();
request.setRemoteAddr("192.168.1.100");
MockHttpServletResponse response = new MockHttpServletResponse();
FilterChain mockFilterChain = mock(FilterChain.class);
IpWhitelistFilter filter = new IpWhitelistFilter();
// 设置允许的 IP 列表
ReflectionTestUtils.setField(filter, "allowedIps",
Arrays.asList("192.168.1.100", "10.0.0.50"));
filter.doFilterInternal(request, response, mockFilterChain);
verify(mockFilterChain).doFilter(request, response);
assertEquals(200, response.getStatus());
}
高级技巧和扩展
动态过滤器链
在某些场景下,你可能需要根据运行时条件动态调整过滤器链。例如,不同的租户可能需要不同的安全策略:
@Component
public class TenantAwareFilterChainProxy extends FilterChainProxy {
private final TenantService tenantService;
private final Map<String, List<SecurityFilterChain>> tenantFilterChains = new ConcurrentHashMap<>();
public TenantAwareFilterChainProxy(TenantService tenantService,
List<SecurityFilterChain> filterChains) {
super(filterChains);
this.tenantService = tenantService;
}
@Override
protected List<Filter> getFilters(HttpServletRequest request) {
String tenantId = tenantService.getTenantIdFromRequest(request);
List<SecurityFilterChain> chains = tenantFilterChains.computeIfAbsent(tenantId,
id -> createTenantSpecificFilterChains(id));
return chains.stream()
.filter(chain -> chain.matches(request))
.findFirst()
.map(SecurityFilterChain::getFilters)
.orElse(Collections.emptyList());
}
private List<SecurityFilterChain> createTenantSpecificFilterChains(String tenantId) {
// 根据租户 ID 创建特定的过滤器链
// 这里可以加载租户特定的配置
return Collections.singletonList(createDefaultFilterChain());
}
}
条件化 Filter 注册
你可以使用 Spring 的条件注解来根据配置条件注册不同的 Filter:
@Component
@ConditionalOnProperty(name = "security.ip-whitelist.enabled", havingValue = "true")
public class ConditionalIpWhitelistFilter extends OncePerRequestFilter {
// 实现略
}
@Configuration
@EnableWebSecurity
public class ConditionalSecurityConfig {
@Autowired(required = false)
private ConditionalIpWhitelistFilter ipWhitelistFilter;
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
if (ipWhitelistFilter != null) {
http.addFilterBefore(ipWhitelistFilter, SecurityContextPersistenceFilter.class);
}
// 其他配置
return http.build();
}
}
Filter 与 Spring AOP 结合
有时你可能希望在 Filter 中使用 Spring 的 AOP 功能,比如事务管理。由于 Filter 不是由 Spring 容器直接管理的(虽然是通过 DelegatingFilterProxy 代理),所以需要特殊处理:
@Component
public class TransactionalFilter extends OncePerRequestFilter {
@Autowired
private PlatformTransactionManager transactionManager;
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain)
throws ServletException, IOException {
TransactionDefinition def = new DefaultTransactionDefinition();
TransactionStatus status = transactionManager.getTransaction(def);
try {
filterChain.doFilter(request, response);
transactionManager.commit(status);
} catch (Exception e) {
transactionManager.rollback(status);
throw e;
}
}
}
总结
自定义 Filter 是 Spring Security 中非常强大且灵活的功能,它允许我们在标准的安全流程之外添加自定义的业务逻辑。通过本文的学习,你应该已经掌握了:
- ✅ Spring Security 过滤器链的基本工作原理
- ✅ 如何创建自定义 Filter(推荐使用
OncePerRequestFilter) - ✅ 如何将自定义 Filter 集成到 Spring Security 过滤器链中
- ✅ 实际应用场景的实现(IP 白名单、JWT 认证、频率限制等)
- ✅ 过滤器执行顺序的重要性及选择合适位置的策略
- ✅ 常见问题的解决方案和最佳实践
记住,Filter 是在每个请求中都会执行的组件,因此性能和安全性至关重要。在实现自定义 Filter 时,始终要考虑线程安全、异常处理和性能影响。
通过合理使用自定义 Filter,你可以构建出既安全又灵活的 Web 应用程序,满足各种复杂的业务需求。希望本文能为你在 Spring Security 的学习和实践中提供有价值的指导!













