Spring Security深度解析:过滤器链、认证授权架构与现代集成方案
一、过滤器链架构:请求处理的核心管道
Spring Security的核心是Servlet Filter链模式,所有请求都经过一系列安全过滤器的检查。
1.1 过滤器链完整流程
请求流程:
HTTP Request
│
└─▶ DelegatingFilterProxy (Servlet容器级别)
│
└─▶ FilterChainProxy (Spring Security入口)
│
└─▶ SecurityFilterChain (匹配请求的过滤器链)
│
├─▶ SecurityContextPersistenceFilter [恢复/保存SecurityContext]
├─▶ LogoutFilter [处理登出]
├─▶ UsernamePasswordAuthenticationFilter [表单认证]
├─▶ BasicAuthenticationFilter [Basic认证]
├─▶ BearerTokenAuthenticationFilter [JWT/OAuth2令牌]
├─▶ RequestCacheAwareFilter [请求缓存]
├─▶ SecurityContextHolderAwareRequestFilter [包装请求]
├─▶ AnonymousAuthenticationFilter [匿名认证]
├─▶ SessionManagementFilter [会话管理]
├─▶ ExceptionTranslationFilter [异常转换]
│
└─▶ FilterSecurityInterceptor [最终鉴权]
│
└─▶ 调用Controller/拒绝访问
关键组件关系:
java
// Spring Security 6.x 配置
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
// 禁用CSRF(REST API场景)
.csrf(csrf -> csrf.disable())
// 配置过滤器
.addFilterBefore(new RequestIdFilter(), UsernamePasswordAuthenticationFilter.class)
// 认证配置
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/public/ **").permitAll()
.requestMatchers("/api/admin/** ").hasRole("ADMIN")
.anyRequest().authenticated()
)
// OAuth2 JWT 配置
.oauth2ResourceServer(oauth2 -> oauth2
.jwt(jwt -> jwt.jwtAuthenticationConverter(jwtAuthenticationConverter()))
);
return http.build();
}
}
1.2 核心过滤器详解
1.2.1 SecurityContextPersistenceFilter(上下文管理)
java
// 职责:请求开始时从Session加载SecurityContext,结束时保存
public class SecurityContextPersistenceFilter extends GenericFilter {
private final SecurityContextRepository repository = new HttpSessionSecurityContextRepository();
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse resp = (HttpServletResponse) response;
// 1. 从Session或请求属性加载SecurityContext
SecurityContext contextBeforeChainExecution = repository.loadContext(req);
SecurityContextHolder.setContext(contextBeforeChainExecution);
try {
// 2. 继续过滤器链
chain.doFilter(request, response);
} finally {
// 3. 清理ThreadLocal,防止内存泄漏
SecurityContext contextAfterChainExecution = SecurityContextHolder.getContext();
SecurityContextHolder.clearContext();
// 4. 保存到Session(如果有变化)
repository.saveContext(contextAfterChainExecution, req, resp);
}
}
}
1.2.2 UsernamePasswordAuthenticationFilter(表单认证)
java
// 处理POST /login请求
public class UsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username";
public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password";
@Override
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response)
throws AuthenticationException {
String username = obtainUsername(request);
String password = obtainPassword(request);
// 构建未认证Authentication
UsernamePasswordAuthenticationToken authRequest =
UsernamePasswordAuthenticationToken.unauthenticated(username, password);
// 委托给AuthenticationManager
return this.getAuthenticationManager().authenticate(authRequest);
}
}
1.2.3 FilterSecurityInterceptor(最终鉴权)
java
// Spring Security过滤器链最后一个过滤器
public class FilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
// 1. 构建FilterInvocation(包装request, response, chain)
FilterInvocation fi = new FilterInvocation(request, response, chain);
// 2. 鉴权决策
InterceptorStatusToken token = super.beforeInvocation(fi);
try {
// 3. 通过后放行
fi.getChain().doFilter(request, response);
} finally {
// 4. 后置处理
super.finallyInvocation(token);
}
super.afterInvocation(token, null);
}
}
1.3 自定义过滤器实战
场景:请求ID追踪与日志
java
// 1. 自定义过滤器
public class RequestIdFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
// 生成或获取请求ID
String requestId = request.getHeader("X-Request-ID");
if (StringUtils.isEmpty(requestId)) {
requestId = UUID.randomUUID().toString();
}
// 放入MDC(日志上下文)
MDC.put("requestId", requestId);
response.setHeader("X-Request-ID", requestId);
try {
filterChain.doFilter(request, response);
} finally {
MDC.clear();
}
}
}
// 2. 注册过滤器(Spring Boot 3.x方式)
@Configuration
public class FilterConfig {
@Bean
public FilterRegistrationBean<RequestIdFilter> requestIdFilter() {
FilterRegistrationBean<RequestIdFilter> registration = new FilterRegistrationBean<>();
registration.setFilter(new RequestIdFilter());
registration.addUrlPatterns("/api/*");
registration.setOrder(Ordered.HIGHEST_PRECEDENCE); // 最高优先级
return registration;
}
// 或直接在SecurityFilterChain中配置
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.addFilterBefore(new RequestIdFilter(), UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}
二、认证/授权架构:核心组件与流程
2.1 认证架构:AuthenticationManager
核心组件关系:
java
// 1. 认证请求
Authentication unauthenticated =
UsernamePasswordAuthenticationToken.unauthenticated(username, password);
// 2. AuthenticationManager委托给Provider
authenticationManager.authenticate(unauthenticated)
│
└─▶ ProviderManager.delegateToProviders()
│
├─▶ DaoAuthenticationProvider.authenticate() [表单认证]
│ │
│ ├─▶ UserDetailsService.loadUserByUsername()
│ │ └─▶ 查询数据库返回UserDetails
│ │
│ └─▶ PasswordEncoder.matches() [密码校验]
│
└─▶ JwtAuthenticationProvider.authenticate() [JWT认证]
│
└─▶ JwtDecoder.decode() [解析令牌]
│
└─▶ JwtAuthenticationConverter [转换Authentication]
2.2 AuthenticationManager配置
Spring Security 6.x 方式:
java
@Configuration
@EnableWebSecurity
public class AuthConfig {
@Bean
public AuthenticationManager authenticationManager(
UserDetailsService userDetailsService,
PasswordEncoder passwordEncoder) {
DaoAuthenticationProvider daoProvider = new DaoAuthenticationProvider();
daoProvider.setUserDetailsService(userDetailsService);
daoProvider.setPasswordEncoder(passwordEncoder);
JwtAuthenticationProvider jwtProvider = new JwtAuthenticationProvider(jwtDecoder());
return new ProviderManager(daoProvider, jwtProvider);
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public UserDetailsService userDetailsService(UserRepository userRepository) {
return username -> userRepository.findByUsername(username)
.map(user -> User.builder()
.username(user.getUsername())
.password(user.getPassword())
.authorities(mapRoles(user.getRoles()))
.accountExpired(!user.isActive())
.build())
.orElseThrow(() -> new UsernameNotFoundException("用户不存在"));
}
}
2.3 自定义AuthenticationProvider
场景:多因素认证(MFA)
java
// 1. 自定义认证Token
public class OtpAuthenticationToken extends AbstractAuthenticationToken {
private final String username;
private final String otpCode;
public OtpAuthenticationToken(String username, String otpCode) {
super(null);
this.username = username;
this.otpCode = otpCode;
setAuthenticated(false);
}
public OtpAuthenticationToken(String username, Collection<? extends GrantedAuthority> authorities) {
super(authorities);
this.username = username;
this.otpCode = null;
setAuthenticated(true);
}
@Override
public Object getCredentials() {
return otpCode;
}
@Override
public Object getPrincipal() {
return username;
}
}
// 2. 自定义认证提供者
@Component
public class OtpAuthenticationProvider implements AuthenticationProvider {
@Autowired
private OtpService otpService;
@Autowired
private UserDetailsService userDetailsService;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
OtpAuthenticationToken token = (OtpAuthenticationToken) authentication;
String username = token.getUsername();
String otpCode = token.getOtpCode();
// 验证OTP
if (!otpService.validateOtp(username, otpCode)) {
throw new BadCredentialsException("OTP验证失败");
}
// 加载用户详情
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
// 返回已认证Token
return new OtpAuthenticationToken(username, userDetails.getAuthorities());
}
@Override
public boolean supports(Class<?> authentication) {
return OtpAuthenticationToken.class.isAssignableFrom(authentication);
}
}
// 3. 注册到AuthenticationManager
@Bean
public AuthenticationManager authenticationManager(
UserDetailsService userDetailsService,
PasswordEncoder passwordEncoder,
OtpAuthenticationProvider otpProvider) {
DaoAuthenticationProvider daoProvider = new DaoAuthenticationProvider();
daoProvider.setUserDetailsService(userDetailsService);
daoProvider.setPasswordEncoder(passwordEncoder);
return new ProviderManager(daoProvider, otpProvider);
}
// 4. 自定义过滤器使用
public class OtpAuthenticationFilter extends OncePerRequestFilter {
@Autowired
private AuthenticationManager authenticationManager;
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain) throws ServletException, IOException {
String otpCode = request.getHeader("X-OTP-Code");
String username = request.getHeader("X-Username");
if (StringUtils.isNotEmpty(otpCode) && StringUtils.isNotEmpty(username)) {
try {
OtpAuthenticationToken token = new OtpAuthenticationToken(username, otpCode);
Authentication authentication = authenticationManager.authenticate(token);
SecurityContextHolder.getContext().setAuthentication(authentication);
} catch (AuthenticationException e) {
SecurityContextHolder.clearContext();
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
return;
}
}
chain.doFilter(request, response);
}
}
三、JWT集成:无状态认证
3.1 JWT过滤器模式(比认证提供者更常见)
java
// 1. JWT工具类
@Component
public class JwtTokenProvider {
private final String secret = "my-secret-key";
private final long validityMs = 3600000; // 1小时
public String generateToken(String username, Collection<? extends GrantedAuthority> authorities) {
Claims claims = Jwts.claims().setSubject(username);
claims.put("roles", authorities.stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.toList()));
Date now = new Date();
Date expiry = new Date(now.getTime() + validityMs);
return Jwts.builder()
.setClaims(claims)
.setIssuedAt(now)
.setExpiration(expiry)
.signWith(SignatureAlgorithm.HS256, secret)
.compact();
}
public Authentication getAuthentication(String token) {
Claims claims = Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws(token)
.getBody();
String username = claims.getSubject();
List<String> roles = claims.get("roles", List.class);
List<GrantedAuthority> authorities = roles.stream()
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
return new UsernamePasswordAuthenticationToken(username, "", authorities);
}
public boolean validateToken(String token) {
try {
Jwts.parser().setSigningKey(secret).parseClaimsJws(token);
return true;
} catch (JwtException | IllegalArgumentException e) {
return false;
}
}
}
// 2. JWT过滤器
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
@Autowired
private JwtTokenProvider tokenProvider;
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
String token = getTokenFromRequest(request);
if (StringUtils.hasText(token) && tokenProvider.validateToken(token)) {
// 从令牌获取认证信息
Authentication authentication = tokenProvider.getAuthentication(token);
// 设置到SecurityContext
SecurityContextHolder.getContext().setAuthentication(authentication);
}
filterChain.doFilter(request, response);
}
private String getTokenFromRequest(HttpServletRequest request) {
String bearerToken = request.getHeader("Authorization");
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7);
}
return null;
}
}
// 3. Security配置
@Configuration
@EnableWebSecurity
public class JwtSecurityConfig {
@Autowired
private JwtAuthenticationFilter jwtFilter;
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable())
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS) // 无状态
)
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/auth/ **").permitAll()
.requestMatchers("/api/admin/** ").hasRole("ADMIN")
.anyRequest().authenticated()
)
.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class); // 添加JWT过滤器
return http.build();
}
}
3.2 Spring Security OAuth2 Resource Server
Spring Boot 3.x 推荐方式:
java
// application.yml
spring:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: https://auth.example.com
jwk-set-uri: https://auth.example.com/.well-known/jwks.json
// Security配置
@Configuration
@EnableWebSecurity
public class OAuth2ResourceServerConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/public").permitAll()
.anyRequest().authenticated()
)
.oauth2ResourceServer(oauth2 -> oauth2
.jwt(jwt -> jwt
.jwtAuthenticationConverter(jwtAuthenticationConverter())
)
);
return http.build();
}
// 自定义JWT转换器(提取权限)
@Bean
public JwtAuthenticationConverter jwtAuthenticationConverter() {
JwtAuthenticationConverter converter = new JwtAuthenticationConverter();
converter.setJwtGrantedAuthoritiesConverter(jwt -> {
List<String> roles = jwt.getClaimAsStringList("roles");
return roles.stream()
.map(role -> new SimpleGrantedAuthority("ROLE_" + role))
.collect(Collectors.toSet());
});
return converter;
}
}
四、OAuth2集成:现代授权
4.1 OAuth2客户端与资源服务器
场景:微服务调用外部OAuth2授权服务器(如Keycloak、Auth0)
java
@Configuration
@EnableWebSecurity
public class OAuth2ClientConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.anyRequest().authenticated()
)
.oauth2Login(oauth2 -> oauth2
.loginPage("/login")
.defaultSuccessUrl("/home")
.failureUrl("/login?error=true")
)
.oauth2Client(withDefaults()); // OAuth2客户端
return http.build();
}
@Bean
public OAuth2AuthorizedClientService authorizedClientService(
ClientRegistrationRepository clientRegistrationRepository) {
return new InMemoryOAuth2AuthorizedClientService(clientRegistrationRepository);
}
@Bean
public ClientRegistrationRepository clientRegistrationRepository() {
ClientRegistration registration = ClientRegistration
.withRegistrationId("keycloak")
.clientId("my-client")
.clientSecret("{noop}my-secret")
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.redirectUri("{baseUrl}/login/oauth2/code/{registrationId}")
.scope("openid", "profile", "roles")
.authorizationUri("https://keycloak.example.com/realms/my-realm/protocol/openid-connect/auth")
.tokenUri("https://keycloak.example.com/realms/my-realm/protocol/openid-connect/token")
.userInfoUri("https://keycloak.example.com/realms/my-realm/protocol/openid-connect/userinfo")
.userNameAttributeName("preferred_username")
.clientName("Keycloak")
.build();
return new InMemoryClientRegistrationRepository(registration);
}
}
4.2 资源服务器保护API
java
@Configuration
@EnableWebSecurity
public class ResourceServerConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.securityMatcher("/api/** ") // 仅保护API端点
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/public").permitAll()
.requestMatchers("/api/admin").hasAuthority("ROLE_ADMIN")
.anyRequest().hasAuthority("ROLE_USER")
)
.oauth2ResourceServer(oauth2 -> oauth2
.jwt(jwt -> jwt
.jwkSetUri("https://keycloak.example.com/.well-known/jwks.json")
.jwtAuthenticationConverter(jwt -> {
// 自定义权限映射
List<String> roles = jwt.getClaimAsStringList("roles");
Collection<GrantedAuthority> authorities = roles.stream()
.map(role -> new SimpleGrantedAuthority("ROLE_" + role))
.toList();
return new JwtAuthenticationToken(jwt, authorities, jwt.getClaimAsString("sub"));
})
)
);
return http.build();
}
}
五、生产实践:高级场景
5.1 动态权限(RBAC模型)
java
// 数据库模型:User → Role → Permission
@Entity
public class Permission {
private Long id;
private String resource; // "order"
private String action; // "create", "read", "update", "delete"
}
// 自定义PermissionEvaluator
@Component
public class CustomPermissionEvaluator implements PermissionEvaluator {
@Autowired
private PermissionService permissionService;
@Override
public boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission) {
String username = authentication.getName();
String resource = targetDomainObject.getClass().getSimpleName().toLowerCase();
String action = permission.toString();
return permissionService.hasPermission(username, resource, action);
}
@Override
public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) {
// 基于ID的权限检查
return false;
}
}
// Security配置启用
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/orders").access(
"@customPermissionEvaluator.hasPermission(authentication, 'order', 'create')"
)
);
return http.build();
}
5.2 多租户安全隔离
java
// 自定义TenantContext
public class TenantContext {
private static final ThreadLocal<String> CURRENT_TENANT = new ThreadLocal<>();
public static void setTenantId(String tenantId) {
CURRENT_TENANT.set(tenantId);
}
public static String getTenantId() {
return CURRENT_TENANT.get();
}
public static void clear() {
CURRENT_TENANT.remove();
}
}
// 租户过滤器
@Component
public class TenantFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain) throws ServletException, IOException {
String tenantId = request.getHeader("X-Tenant-ID");
if (StringUtils.hasText(tenantId)) {
TenantContext.setTenantId(tenantId);
}
try {
chain.doFilter(request, response);
} finally {
TenantContext.clear();
}
}
}
// 自定义AuthenticationProvider
@Component
public class TenantAuthenticationProvider implements AuthenticationProvider {
@Autowired
private TenantUserDetailsService userDetailsService;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String username = authentication.getName();
String password = authentication.getCredentials().toString();
String tenantId = TenantContext.getTenantId();
if (tenantId == null) {
throw new BadCredentialsException("租户ID未提供");
}
UserDetails user = userDetailsService.loadUserByUsernameAndTenant(username, tenantId);
if (!passwordEncoder.matches(password, user.getPassword())) {
throw new BadCredentialsException("密码错误");
}
return new UsernamePasswordAuthenticationToken(user, password, user.getAuthorities());
}
}
5.3 API速率限制
java
// 结合Spring Security和Bucket4j
@Component
public class RateLimitFilter extends OncePerRequestFilter {
private final Map<String, Bucket> buckets = new ConcurrentHashMap<>();
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain) throws ServletException, IOException {
String clientId = getClientId(request);
Bucket bucket = buckets.computeIfAbsent(clientId, this::createBucket);
// 尝试消耗1个令牌
if (bucket.tryConsume(1)) {
chain.doFilter(request, response);
} else {
response.setStatus(HttpStatus.TOO_MANY_REQUESTS.value());
response.getWriter().write("Rate limit exceeded");
}
}
private Bucket createBucket(String clientId) {
Bandwidth bandwidth = Bandwidth.classic(100, Refill.intervally(100, Duration.ofMinutes(1)));
return Bucket.builder().addLimit(bandwidth).build();
}
private String getClientId(HttpServletRequest request) {
String apiKey = request.getHeader("X-API-Key");
if (StringUtils.hasText(apiKey)) {
return apiKey;
}
return request.getRemoteAddr();
}
}
// 配置到SecurityFilterChain
http.addFilterBefore(new RateLimitFilter(), UsernamePasswordAuthenticationFilter.class);
六、总结:Spring Security核心要点
| 核心概念 | 关键组件 | 配置方式 | 生产建议 |
|---|---|---|---|
| 过滤器链 | SecurityFilterChain |
HttpSecurity配置 |
理解顺序,自定义Filter |
| 认证 | AuthenticationManager, AuthenticationProvider |
AuthenticationManager Bean |
自定义Provider实现MFA |
| 授权 | FilterSecurityInterceptor |
.authorizeHttpRequests() |
使用RBAC,避免硬编码 |
| JWT | BearerTokenAuthenticationFilter |
.oauth2ResourceServer() |
使用成熟库,校验签名 |
| OAuth2 | ClientRegistrationRepository |
OAuth2 Client/Resource Server | 使用授权服务器 |
| 代理与缓存 | SecurityContextHolder |
SessionCreationPolicy |
STATELESS for API |
最佳实践:
- API优先 :REST API使用JWT +
BearerTokenAuthenticationFilter - 会话策略 :明确
SessionCreationPolicy.STATELESS或IF_REQUIRED - 密码编码 :始终使用
BCryptPasswordEncoder - 异常处理 :自定义
AuthenticationEntryPoint和AccessDeniedHandler - 测试覆盖 :使用
@WithMockUser和@WithAnonymousUser测试安全规则
理解过滤器链的顺序、认证架构的委托模式、JWT的无状态特性,是掌握现代Spring Security的关键。