Spring 安全架构深度解析¶
概述¶
Spring Security 是 Spring 生态中的安全框架,提供全面的认证、授权、攻击防护等功能。本文深度解析 Spring Security 的架构设计和高级用法。
graph TB
A[Spring Security] --> B[认证体系]
A --> C[授权体系]
A --> D[安全过滤器链]
A --> E[OAuth2 集成]
A --> F[攻击防护]
B --> B1[表单登录]
B --> B2[JWT 认证]
B --> B3[OAuth2 认证]
B --> B4[LDAP 认证]
C --> C1[方法级授权]
C --> C2[URL 级授权]
C --> C3[数据级授权]
D --> D1[过滤器顺序]
D --> D2[自定义过滤器]
D --> D3[CSRF 防护]
E --> E1[授权服务器]
E --> E2[资源服务器]
E --> E3[客户端配置]
F --> F1[XSS 防护]
F --> F2[CSRF 防护]
F --> F3[SQL 注入防护] Spring Security 核心架构¶
1. 安全过滤器链(Security Filter Chain)¶
过滤器链组成¶
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
// 1. 认证相关过滤器
.formLogin(form -> form
.loginPage("/login")
.permitAll()
)
// 2. 授权相关过滤器
.authorizeHttpRequests(auth -> auth
.requestMatchers("/admin/**").hasRole("ADMIN")
.requestMatchers("/user/**").hasAnyRole("USER", "ADMIN")
.requestMatchers("/public/**").permitAll()
.anyRequest().authenticated()
)
// 3. 会话管理
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
.maximumSessions(1)
.maxSessionsPreventsLogin(true)
)
// 4. 记住我功能
.rememberMe(remember -> remember
.tokenValiditySeconds(86400) // 24小时
.key("remember-me-key")
)
// 5. 退出登录
.logout(logout -> logout
.logoutUrl("/logout")
.logoutSuccessUrl("/login?logout")
.invalidateHttpSession(true)
.deleteCookies("JSESSIONID")
)
// 6. CSRF 防护
.csrf(csrf -> csrf
.ignoringRequestMatchers("/api/**") // API 接口禁用 CSRF
)
// 7. 异常处理
.exceptionHandling(exception -> exception
.authenticationEntryPoint(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED))
.accessDeniedHandler(new HttpStatusEntryPoint(HttpStatus.FORBIDDEN))
);
return http.build();
}
}
过滤器执行顺序¶
// Spring Security 过滤器链的典型顺序
public class SecurityFilterOrder {
// 1. ChannelProcessingFilter - 处理 HTTPS 重定向
// 2. WebAsyncManagerIntegrationFilter - 异步请求支持
// 3. SecurityContextPersistenceFilter - 安全上下文管理
// 4. HeaderWriterFilter - 安全响应头设置
// 5. CorsFilter - 跨域请求处理
// 6. CsrfFilter - CSRF 防护
// 7. LogoutFilter - 退出登录处理
// 8. OAuth2AuthorizationRequestRedirectFilter - OAuth2 授权请求重定向
// 9. Saml2WebSsoAuthenticationRequestFilter - SAML2 认证
// 10. X509AuthenticationFilter - X509 证书认证
// 11. AbstractPreAuthenticatedProcessingFilter - 预认证处理
// 12. CasAuthenticationFilter - CAS 认证
// 13. OAuth2LoginAuthenticationFilter - OAuth2 登录认证
// 14. Saml2WebSsoAuthenticationFilter - SAML2 Web SSO
// 15. UsernamePasswordAuthenticationFilter - 用户名密码认证
// 16. OpenIDAuthenticationFilter - OpenID 认证
// 17. DefaultLoginPageGeneratingFilter - 默认登录页生成
// 18. DefaultLogoutPageGeneratingFilter - 默认退出页生成
// 19. ConcurrentSessionFilter - 并发会话控制
// 20. DigestAuthenticationFilter - 摘要认证
// 21. BearerTokenAuthenticationFilter - Bearer Token 认证
// 22. BasicAuthenticationFilter - Basic 认证
// 23. RequestCacheAwareFilter - 请求缓存
// 24. SecurityContextHolderAwareRequestFilter - 安全上下文感知
// 25. JaasApiIntegrationFilter - JAAS 集成
// 26. RememberMeAuthenticationFilter - 记住我认证
// 27. AnonymousAuthenticationFilter - 匿名认证
// 28. OAuth2AuthorizationCodeGrantFilter - OAuth2 授权码
// 29. SessionManagementFilter - 会话管理
// 30. ExceptionTranslationFilter - 异常转换
// 31. FilterSecurityInterceptor - 安全拦截器(授权决策)
// 32. SwitchUserFilter - 用户切换
}
2. 认证体系深度解析¶
自定义认证提供者¶
@Component
public class CustomAuthenticationProvider implements AuthenticationProvider {
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String username = authentication.getName();
String password = authentication.getCredentials().toString();
// 1. 加载用户信息
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
// 2. 验证密码
if (!passwordEncoder.matches(password, userDetails.getPassword())) {
throw new BadCredentialsException("Invalid password");
}
// 3. 检查账户状态
if (!userDetails.isEnabled()) {
throw new DisabledException("Account is disabled");
}
if (!userDetails.isAccountNonLocked()) {
throw new LockedException("Account is locked");
}
if (!userDetails.isAccountNonExpired()) {
throw new AccountExpiredException("Account is expired");
}
if (!userDetails.isCredentialsNonExpired()) {
throw new CredentialsExpiredException("Credentials are expired");
}
// 4. 创建认证令牌
return new UsernamePasswordAuthenticationToken(
userDetails,
password,
userDetails.getAuthorities()
);
}
@Override
public boolean supports(Class<?> authentication) {
return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
}
}
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Autowired
private CustomAuthenticationProvider customAuthenticationProvider;
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authenticationProvider(customAuthenticationProvider)
.authorizeHttpRequests(auth -> auth
.anyRequest().authenticated()
)
.formLogin(Customizer.withDefaults());
return http.build();
}
}
多因素认证(MFA)实现¶
@Component
public class MultiFactorAuthenticationProvider implements AuthenticationProvider {
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private OtpService otpService;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
if (authentication instanceof UsernamePasswordAuthenticationToken) {
// 第一阶段:用户名密码认证
return authenticateFirstFactor(authentication);
} else if (authentication instanceof MultiFactorAuthenticationToken) {
// 第二阶段:多因素认证
return authenticateSecondFactor(authentication);
}
return null;
}
private Authentication authenticateFirstFactor(Authentication authentication) {
String username = authentication.getName();
String password = authentication.getCredentials().toString();
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
// 验证密码...
// 创建第一阶段认证令牌(未完全认证)
MultiFactorAuthenticationToken firstFactorToken = new MultiFactorAuthenticationToken(
userDetails,
null,
Collections.emptyList(),
false // 未完全认证
);
firstFactorToken.setDetails(authentication.getDetails());
// 发送 OTP 验证码
otpService.sendOtp(username);
return firstFactorToken;
}
private Authentication authenticateSecondFactor(Authentication authentication) {
MultiFactorAuthenticationToken mfaToken = (MultiFactorAuthenticationToken) authentication;
String otpCode = mfaToken.getOtpCode();
String username = mfaToken.getName();
// 验证 OTP 码
if (!otpService.validateOtp(username, otpCode)) {
throw new BadCredentialsException("Invalid OTP code");
}
// 加载用户信息
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
// 创建完全认证的令牌
MultiFactorAuthenticationToken authenticatedToken = new MultiFactorAuthenticationToken(
userDetails,
null,
userDetails.getAuthorities(),
true // 完全认证
);
authenticatedToken.setDetails(mfaToken.getDetails());
return authenticatedToken;
}
@Override
public boolean supports(Class<?> authentication) {
return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication) ||
MultiFactorAuthenticationToken.class.isAssignableFrom(authentication);
}
}
// 自定义多因素认证令牌
public class MultiFactorAuthenticationToken extends AbstractAuthenticationToken {
private final Object principal;
private Object credentials;
private String otpCode;
public MultiFactorAuthenticationToken(Object principal, Object credentials,
Collection<? extends GrantedAuthority> authorities,
boolean authenticated) {
super(authorities);
this.principal = principal;
this.credentials = credentials;
super.setAuthenticated(authenticated);
}
public String getOtpCode() {
return otpCode;
}
public void setOtpCode(String otpCode) {
this.otpCode = otpCode;
}
@Override
public Object getCredentials() {
return credentials;
}
@Override
public Object getPrincipal() {
return principal;
}
}
授权体系深度解析¶
1. 方法级安全控制¶
基于注解的细粒度授权¶
@Service
public class UserService {
@PreAuthorize("hasRole('ADMIN') or #userId == authentication.principal.id")
public User getUserById(Long userId) {
// 只有管理员或用户自己可以查看用户信息
return userRepository.findById(userId).orElseThrow();
}
@PreAuthorize("hasPermission(#user, 'WRITE')")
public User updateUser(User user) {
// 基于自定义权限的授权
return userRepository.save(user);
}
@PostAuthorize("returnObject.owner == authentication.principal.username")
public Document getDocument(Long docId) {
// 方法执行后检查返回值权限
return documentRepository.findById(docId).orElseThrow();
}
@PreFilter("filterObject.owner == authentication.principal.username")
public List<Document> updateDocuments(List<Document> documents) {
// 过滤输入参数,只处理用户拥有的文档
return documentRepository.saveAll(documents);
}
@PostFilter("filterObject.status == 'PUBLISHED' or filterObject.owner == authentication.principal.username")
public List<Document> getUserDocuments(Long userId) {
// 过滤返回结果,只返回已发布文档或用户自己的文档
return documentRepository.findByOwnerId(userId);
}
@Secured({"ROLE_ADMIN", "ROLE_MANAGER"})
public void deleteUser(Long userId) {
// 基于角色的授权(旧版注解)
userRepository.deleteById(userId);
}
@RolesAllowed({"ADMIN", "MANAGER"})
public void approveDocument(Long docId) {
// JSR-250 标准注解
documentRepository.approve(docId);
}
}
// 启用方法级安全
@Configuration
@EnableMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
public class MethodSecurityConfig {
@Bean
public MethodSecurityExpressionHandler methodSecurityExpressionHandler() {
DefaultMethodSecurityExpressionHandler handler = new DefaultMethodSecurityExpressionHandler();
handler.setPermissionEvaluator(customPermissionEvaluator());
return handler;
}
@Bean
public PermissionEvaluator customPermissionEvaluator() {
return new CustomPermissionEvaluator();
}
}
// 自定义权限评估器
@Component
public class CustomPermissionEvaluator implements PermissionEvaluator {
@Autowired
private PermissionService permissionService;
@Override
public boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission) {
if (authentication == null || targetDomainObject == null || !(permission instanceof String)) {
return false;
}
String username = authentication.getName();
String perm = (String) permission;
// 检查用户对目标对象是否有指定权限
return permissionService.hasPermission(username, targetDomainObject, perm);
}
@Override
public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) {
if (authentication == null || targetId == null || targetType == null || !(permission instanceof String)) {
return false;
}
String username = authentication.getName();
String perm = (String) permission;
// 根据类型和ID检查权限
return permissionService.hasPermission(username, targetType, targetId.toString(), perm);
}
}
2. 动态权限控制¶
基于数据库的权限管理¶
@Component
public class DynamicPermissionEvaluator {
@Autowired
private PermissionRepository permissionRepository;
@Autowired
private RoleRepository roleRepository;
public boolean hasPermission(String username, String resource, String action) {
// 1. 获取用户角色
List<Role> userRoles = roleRepository.findByUsername(username);
// 2. 检查角色权限
for (Role role : userRoles) {
if (permissionRepository.existsByRoleAndResourceAndAction(role.getName(), resource, action)) {
return true;
}
}
// 3. 检查用户特定权限
return permissionRepository.existsByUsernameAndResourceAndAction(username, resource, action);
}
public List<String> getUserPermissions(String username) {
List<String> permissions = new ArrayList<>();
// 获取角色权限
List<Role> userRoles = roleRepository.findByUsername(username);
for (Role role : userRoles) {
List<Permission> rolePermissions = permissionRepository.findByRole(role.getName());
for (Permission perm : rolePermissions) {
permissions.add(perm.getResource() + ":" + perm.getAction());
}
}
// 获取用户特定权限
List<Permission> userSpecificPermissions = permissionRepository.findByUsername(username);
for (Permission perm : userSpecificPermissions) {
permissions.add(perm.getResource() + ":" + perm.getAction());
}
return permissions;
}
}
// 权限实体类
@Entity
public class Permission {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String role; // 角色名(可为空)
private String username; // 用户名(可为空)
private String resource; // 资源(如:user, document)
private String action; // 操作(如:read, write, delete)
// getters and setters
}
// 角色实体类
@Entity
public class Role {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name; // 角色名
private String description; // 角色描述
@ManyToMany(mappedBy = "roles")
private Set<User> users; // 拥有该角色的用户
// getters and setters
}
OAuth2 深度集成¶
1. 授权服务器配置¶
基于 Spring Authorization Server¶
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig {
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
return http
.formLogin(Customizer.withDefaults())
.build();
}
@Bean
public RegisteredClientRepository registeredClientRepository() {
RegisteredClient client = RegisteredClient.withId(UUID.randomUUID().toString())
.clientId("web-client")
.clientSecret("{bcrypt}$2a$10$dXJ3SW6G7P.XBLBvanJYv.Ms6.2Z7.2Z7.2Z7.2Z7.2Z7.2Z7")
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
.redirectUri("http://localhost:8080/login/oauth2/code/web-client")
.scope(OidcScopes.OPENID)
.scope(OidcScopes.PROFILE)
.scope("read")
.scope("write")
.clientSettings(ClientSettings.builder()
.requireAuthorizationConsent(true)
.build())
.build();
return new InMemoryRegisteredClientRepository(client);
}
@Bean
public JWKSource<SecurityContext> jwkSource() {
RSAKey rsaKey = Jwks.generateRsa();
JWKSet jwkSet = new JWKSet(rsaKey);
return (jwkSelector, securityContext) -> jwkSelector.select(jwkSet);
}
@Bean
public ProviderSettings providerSettings() {
return ProviderSettings.builder()
.issuer("http://auth-server:9000")
.build();
}
}
// JWT 密钥生成工具类
final class Jwks {
private Jwks() {}
public static RSAKey generateRsa() {
KeyPair keyPair = KeyGeneratorUtils.generateRsaKey();
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
return new RSAKey.Builder(publicKey)
.privateKey(privateKey)
.keyID(UUID.randomUUID().toString())
.build();
}
}
final class KeyGeneratorUtils {
private KeyGeneratorUtils() {}
static KeyPair generateRsaKey() {
KeyPair keyPair;
try {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(2048);
keyPair = keyPairGenerator.generateKeyPair();
} catch (Exception ex) {
throw new IllegalStateException(ex);
}
return keyPair;
}
}
2. 资源服务器配置¶
JWT 令牌验证¶
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class ResourceServerConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/public/**").permitAll()
.anyRequest().authenticated()
)
.oauth2ResourceServer(oauth2 -> oauth2
.jwt(jwt -> jwt
.jwtAuthenticationConverter(jwtAuthenticationConverter())
)
)
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
);
return http.build();
}
@Bean
public JwtAuthenticationConverter jwtAuthenticationConverter() {
JwtGrantedAuthoritiesConverter grantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
grantedAuthoritiesConverter.setAuthorityPrefix("ROLE_");
grantedAuthoritiesConverter.setAuthoritiesClaimName("roles");
JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(grantedAuthoritiesConverter);
return jwtAuthenticationConverter;
}
@Bean
public JwtDecoder jwtDecoder() {
return NimbusJwtDecoder.withJwkSetUri("http://auth-server:9000/oauth2/jwks")
.build();
}
}
// 自定义 JWT 令牌增强器
@Component
public class CustomJwtTokenEnhancer implements OAuth2TokenCustomizer<JwtEncodingContext> {
@Override
public void customize(JwtEncodingContext context) {
if (context.getTokenType().getValue().equals(OAuth2TokenType.ACCESS_TOKEN.getValue())) {
// 添加自定义声明
context.getClaims().claim("custom_claim", "custom_value");
// 添加用户角色信息
Authentication principal = context.getPrincipal();
if (principal instanceof OAuth2AuthenticationToken) {
OAuth2AuthenticationToken oauthToken = (OAuth2AuthenticationToken) principal;
Set<String> authorities = oauthToken.getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.toSet());
context.getClaims().claim("roles", authorities);
}
}
}
}
安全最佳实践¶
1. 密码安全¶
密码编码器配置¶
@Configuration
public class PasswordConfig {
@Bean
public PasswordEncoder passwordEncoder() {
// 使用 BCrypt 密码编码器
return new BCryptPasswordEncoder(12); // 强度因子 12
}
@Bean
public DelegatingPasswordEncoder delegatingPasswordEncoder() {
String encodingId = "bcrypt";
Map<String, PasswordEncoder> encoders = new HashMap<>();
encoders.put(encodingId, new BCryptPasswordEncoder(12));
encoders.put("pbkdf2", new Pbkdf2PasswordEncoder());
encoders.put("scrypt", new SCryptPasswordEncoder());
encoders.put("argon2", new Argon2PasswordEncoder());
// 默认使用 bcrypt,但支持迁移其他算法
return new DelegatingPasswordEncoder(encodingId, encoders);
}
}
// 密码策略验证
@Component
public class PasswordPolicyValidator {
private static final int MIN_LENGTH = 8;
private static final int MAX_LENGTH = 128;
public void validatePassword(String password) {
if (password == null || password.length() < MIN_LENGTH) {
throw new IllegalArgumentException("Password must be at least " + MIN_LENGTH + " characters long");
}
if (password.length() > MAX_LENGTH) {
throw new IllegalArgumentException("Password must not exceed " + MAX_LENGTH + " characters");
}
// 检查密码复杂度
if (!containsUpperCase(password)) {
throw new IllegalArgumentException("Password must contain at least one uppercase letter");
}
if (!containsLowerCase(password)) {
throw new IllegalArgumentException("Password must contain at least one lowercase letter");
}
if (!containsDigit(password)) {
throw new IllegalArgumentException("Password must contain at least one digit");
}
if (!containsSpecialCharacter(password)) {
throw new IllegalArgumentException("Password must contain at least one special character");
}
// 检查常见弱密码
if (isCommonPassword(password)) {
throw new IllegalArgumentException("Password is too common, please choose a stronger one");
}
}
private boolean containsUpperCase(String password) {
return password.chars().anyMatch(Character::isUpperCase);
}
private boolean containsLowerCase(String password) {
return password.chars().anyMatch(Character::isLowerCase);
}
private boolean containsDigit(String password) {
return password.chars().anyMatch(Character::isDigit);
}
private boolean containsSpecialCharacter(String password) {
return password.chars().anyMatch(ch -> !Character.isLetterOrDigit(ch));
}
private boolean isCommonPassword(String password) {
Set<String> commonPasswords = Set.of(
"password", "123456", "qwerty", "admin", "welcome"
// 更多常见密码...
);
return commonPasswords.contains(password.toLowerCase());
}
}
2. 安全响应头配置¶
自定义安全头过滤器¶
@Component
public class SecurityHeadersFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
// 设置安全相关的 HTTP 头
response.setHeader("X-Content-Type-Options", "nosniff");
response.setHeader("X-Frame-Options", "DENY");
response.setHeader("X-XSS-Protection", "1; mode=block");
response.setHeader("Strict-Transport-Security", "max-age=31536000; includeSubDomains");
response.setHeader("Content-Security-Policy",
"default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'");
response.setHeader("Referrer-Policy", "strict-origin-when-cross-origin");
response.setHeader("Feature-Policy",
"geolocation 'none'; microphone 'none'; camera 'none'");
filterChain.doFilter(request, response);
}
}
// 配置安全头过滤器
@Configuration
public class SecurityHeadersConfig {
@Bean
public FilterRegistrationBean<SecurityHeadersFilter> securityHeadersFilter() {
FilterRegistrationBean<SecurityHeadersFilter> registrationBean = new FilterRegistrationBean<>();
registrationBean.setFilter(new SecurityHeadersFilter());
registrationBean.setOrder(Ordered.HIGHEST_PRECEDENCE);
return registrationBean;
}
}
总结¶
Spring Security 提供了全面的安全解决方案:
- 认证体系:支持多种认证方式,可自定义认证提供者
- 授权体系:细粒度的权限控制,支持动态权限管理
- OAuth2 集成:完整的 OAuth2 授权服务器和资源服务器支持
- 安全过滤器链:可定制的安全过滤器执行流程
- 安全最佳实践:密码安全、响应头安全、攻击防护
通过深度理解 Spring Security 架构,可以构建安全、可靠的企业级应用。