1. Spring Security 核心价值与应用场景
Spring Security 作为 Java 生态中最成熟的安全框架,其核心价值在于为应用系统提供全方位的安全防护能力。在前后端分离架构成为主流的今天,如何正确集成 Spring Security 实现认证授权、防御常见攻击,是每个 Java 开发者必须掌握的技能。
我在多个企业级项目中实践发现,许多团队在集成 Spring Security 时容易陷入两个极端:要么简单拷贝配置不求甚解,要么过度定制导致复杂度失控。本文将基于最新 Spring Boot 3.x 和 Spring Security 6.x 版本,分享经过生产验证的最佳实践方案。
2. 基础环境搭建与基础配置
2.1 项目初始化与依赖管理
使用 Spring Initializr 创建项目时,除了选择 Spring Web 和 Spring Security 基础依赖外,建议额外添加以下依赖:
xml复制<!-- 生产环境必备 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
<!-- 开发辅助 -->
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.1.0</version>
</dependency>
关键配置解析:
- Redis 用于会话管理和令牌存储
- JJWT 实现 JWT 令牌生成/验证
- SpringDoc 自动生成 API 文档(含安全Scheme)
2.2 安全配置类骨架代码
创建基础安全配置类时,推荐以下结构:
java复制@Configuration
@EnableWebSecurity
@EnableMethodSecurity(prePostEnabled = true)
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable()) // 前后端分离场景可禁用
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/auth/**").permitAll()
.anyRequest().authenticated())
.addFilterBefore(jwtAuthFilter(), UsernamePasswordAuthenticationFilter.class);
return http.build();
}
@Bean
public JwtAuthFilter jwtAuthFilter() {
return new JwtAuthFilter();
}
}
重要提示:在 Spring Security 6.x 中,Lambda DSL 成为推荐配置方式,相比传统链式调用更易维护
3. JWT 认证完整实现
3.1 令牌生成与验证逻辑
JWT 工具类应包含以下核心方法:
java复制public class JwtUtils {
private static final String SECRET_KEY = "your-256-bit-secret";
private static final long EXPIRATION_MS = 86400000; // 24小时
public static String generateToken(UserDetails userDetails) {
return Jwts.builder()
.setSubject(userDetails.getUsername())
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_MS))
.signWith(Keys.hmacShaKeyFor(SECRET_KEY.getBytes()), SignatureAlgorithm.HS256)
.compact();
}
public static boolean validateToken(String token) {
try {
Jwts.parserBuilder()
.setSigningKey(SECRET_KEY.getBytes())
.build()
.parseClaimsJws(token);
return true;
} catch (JwtException e) {
log.error("JWT validation error: {}", e.getMessage());
return false;
}
}
}
3.2 认证过滤器实现
自定义过滤器需要继承 OncePerRequestFilter:
java复制public class JwtAuthFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
String authHeader = request.getHeader("Authorization");
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
filterChain.doFilter(request, response);
return;
}
String token = authHeader.substring(7);
if (!JwtUtils.validateToken(token)) {
response.setStatus(HttpStatus.UNAUTHORIZED.value());
return;
}
String username = JwtUtils.extractUsername(token);
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
filterChain.doFilter(request, response);
}
}
4. 前后端分离特殊处理
4.1 跨域配置方案
推荐在安全配置中集成 CORS 设置:
java复制@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(List.of("https://your-frontend.com"));
configuration.setAllowedMethods(List.of("GET","POST","PUT","DELETE"));
configuration.setAllowedHeaders(List.of("*"));
configuration.setExposedHeaders(List.of("Authorization"));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
// 在 securityFilterChain 方法中添加:
http.cors(cors -> cors.configurationSource(corsConfigurationSource()));
4.2 认证异常统一处理
创建全局异常处理器:
java复制@RestControllerAdvice
public class SecurityExceptionHandler {
@ExceptionHandler(AccessDeniedException.class)
public ResponseEntity<ErrorResponse> handleAccessDenied(AccessDeniedException ex) {
return ResponseEntity.status(HttpStatus.FORBIDDEN)
.body(new ErrorResponse("权限不足", 403));
}
@ExceptionHandler(AuthenticationException.class)
public ResponseEntity<ErrorResponse> handleAuthentication(AuthenticationException ex) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
.body(new ErrorResponse("认证失败", 401));
}
}
5. 生产级增强功能
5.1 接口权限精细化控制
结合 @PreAuthorize 注解实现方法级控制:
java复制@GetMapping("/admin/users")
@PreAuthorize("hasRole('ADMIN')")
public List<User> listAllUsers() {
return userService.findAll();
}
@PostMapping("/projects")
@PreAuthorize("#project.owner == authentication.name")
public Project createProject(@RequestBody Project project) {
return projectService.save(project);
}
5.2 动态权限加载方案
实现 PermissionEvaluator 接口:
java复制public class CustomPermissionEvaluator implements PermissionEvaluator {
@Override
public boolean hasPermission(Authentication auth, Object target, Object permission) {
String username = auth.getName();
// 从数据库查询用户对目标对象的权限
return permissionService.checkPermission(username, target, permission.toString());
}
// 注册评估器
@Bean
public MethodSecurityExpressionHandler expressionHandler() {
DefaultMethodSecurityExpressionHandler handler = new DefaultMethodSecurityExpressionHandler();
handler.setPermissionEvaluator(new CustomPermissionEvaluator());
return handler;
}
}
6. 安全防护进阶配置
6.1 CSRF 防御策略
虽然前后端分离通常禁用 CSRF 防护,但对于敏感操作建议启用:
java复制http.csrf(csrf -> csrf
.ignoringRequestMatchers("/api/public/**")
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
);
6.2 请求限流保护
集成 Resilience4j 实现接口限流:
java复制@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.addFilterBefore(new RateLimitFilter(), BasicAuthenticationFilter.class);
// ...
}
public class RateLimitFilter extends OncePerRequestFilter {
private final RateLimiter limiter = RateLimiter.of("api-limiter",
RateLimiterConfig.custom()
.limitForPeriod(100)
.limitRefreshPeriod(Duration.ofMinutes(1))
.build());
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain) throws IOException, ServletException {
if (!limiter.acquirePermission()) {
response.sendError(429, "Too many requests");
return;
}
chain.doFilter(request, response);
}
}
7. 实战问题排查指南
7.1 常见配置错误
-
过滤器顺序问题:
- 自定义过滤器必须正确设置顺序,特别是涉及认证的过滤器
- 推荐使用
http.addFilterBefore/After()明确指定位置
-
权限注解失效:
- 检查是否添加
@EnableMethodSecurity - 方法必须是 Spring 代理对象调用的(同类调用不生效)
- 检查是否添加
7.2 性能优化建议
-
JWT 密钥管理:
- 生产环境必须使用非对称加密(RS256)
- 密钥应当定期轮换
-
用户信息缓存:
java复制@Cacheable(value = "userDetails", key = "#username") public UserDetails loadUserByUsername(String username) { // ... }
8. 测试策略与工具
8.1 安全测试用例示例
java复制@Test
@WithMockUser(roles = "USER")
void whenUserAccessAdminEndpoint_thenForbidden() throws Exception {
mockMvc.perform(get("/admin"))
.andExpect(status().isForbidden());
}
@Test
void whenUnauthenticated_thenRedirectToLogin() throws Exception {
mockMvc.perform(get("/private"))
.andExpect(status().isUnauthorized());
}
8.2 Postman 测试集合
-
认证测试流程:
POST /api/auth/login获取令牌- 将令牌放入
Authorization: Bearer <token>头 - 测试受保护端点
-
自动化测试脚本:
javascript复制pm.test("JWT Auth Success", function() {
pm.response.to.have.status(200);
var jsonData = pm.response.json();
pm.expect(jsonData.token).to.exist;
pm.environment.set("jwt_token", jsonData.token);
});
9. 部署注意事项
9.1 安全头配置
增强 HTTP 安全头:
java复制http.headers(headers -> headers
.contentSecurityPolicy(csp -> csp
.policyDirectives("default-src 'self'"))
.httpStrictTransportSecurity(hsts -> hsts
.includeSubDomains(true)
.maxAgeInSeconds(31536000))
);
9.2 密钥管理方案
推荐使用 Kubernetes Secrets 或 AWS KMS:
yaml复制# application-prod.yaml
jwt:
secret: ${JWT_SECRET}
expiration-ms: 3600000
启动命令:
bash复制JWT_SECRET=$(aws kms decrypt --ciphertext-blob fileb://encrypted.key --output text --query Plaintext) \
java -jar app.jar
10. 扩展架构建议
10.1 分布式会话方案
集成 Spring Session Redis:
java复制@EnableRedisHttpSession
public class SessionConfig {
@Bean
public RedisConnectionFactory redisConnectionFactory() {
return new LettuceConnectionFactory();
}
}
10.2 多因素认证集成
实现 TOTP 认证:
java复制public class TotpService {
public boolean verifyCode(String secret, int code) {
long time = System.currentTimeMillis() / 1000 / 30;
return generateCode(secret, time) == code;
}
private int generateCode(String secret, long time) {
byte[] key = Base32.decode(secret);
byte[] data = new byte[8];
for (int i = 8; i-- > 0; time >>>= 8) {
data[i] = (byte) time;
}
// HMAC-SHA1 计算...
}
}
在实际项目部署时,建议将安全配置分为多个 Profile(开发/测试/生产),使用环境变量管理敏感信息。对于关键业务系统,应当定期进行安全审计和渗透测试,确保防护措施持续有效。