引言

在现代 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 至关重要。

关键过滤器说明

  1. SecurityContextPersistenceFilter: 最重要的过滤器之一,负责在请求开始时从 Session 中恢复 SecurityContext,在请求结束时保存 SecurityContext。如果你的自定义 Filter 需要访问 SecurityContext,必须放在这个过滤器之后。
  2. UsernamePasswordAuthenticationFilter: 处理表单登录的过滤器。如果你要实现自定义的表单登录逻辑,通常需要替换或扩展这个过滤器。
  3. BearerTokenAuthenticationFilter: 处理 JWT Token 认证的过滤器。对于 RESTful API,通常需要在这个位置或附近添加自定义的 Token 处理逻辑。
  4. AnonymousAuthenticationFilter: 为未认证的用户提供匿名身份。如果你的应用需要区分"未认证"和"匿名用户",需要注意这个过滤器的位置。
  5. ExceptionTranslationFilter: 处理认证和授权异常的过滤器。它会捕获 AuthenticationExceptionAccessDeniedException,并根据情况进行重定向或返回错误响应。
  6. 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 的学习和实践中提供有价值的指导!

觉得上面的内容有用吗?快来点个赞吧!

点赞() 我要打赏

温馨提示 : 本站内容来自会员投稿以及互联网,所有源码及教程均为作者总结编辑,请大家在使用过程中提前做好备份,以免发生无法预知的错误,源码类教程请勿直接用于生产环境!

 可能感兴趣的文章