SpringBoot安全最佳实践Skill springboot-security

本技能是关于Java Spring Boot应用安全防护的综合指南,涵盖认证授权、输入验证、SQL注入防护、密码编码、CSRF防护、密钥管理、安全头配置、CORS、速率限制、依赖安全等核心安全实践。适用于后端开发人员、安全工程师和架构师,用于构建安全、合规的企业级Web服务和API。关键词:Spring Security, 认证授权, 输入验证, SQL注入防护, CSRF防护, 密钥管理, 安全头, CORS, 速率限制, 依赖安全, Java后端安全, Web应用安全。

后端开发 0 次安装 12 次浏览 更新于 2/27/2026

名称: springboot-security 描述: 适用于Java Spring Boot服务的Spring Security最佳实践,涵盖认证授权、输入验证、CSRF防护、密钥管理、安全头配置、速率限制和依赖安全。

Spring Boot 安全审查

在添加认证、处理输入、创建端点或处理密钥时使用。

认证

  • 优先使用无状态JWT或带撤销列表的不透明令牌
  • 对于会话,使用httpOnlySecureSameSite=Strict的Cookie
  • 使用OncePerRequestFilter或资源服务器验证令牌
@Component
public class JwtAuthFilter extends OncePerRequestFilter {
  private final JwtService jwtService;

  public JwtAuthFilter(JwtService jwtService) {
    this.jwtService = jwtService;
  }

  @Override
  protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
      FilterChain chain) throws ServletException, IOException {
    String header = request.getHeader(HttpHeaders.AUTHORIZATION);
    if (header != null && header.startsWith("Bearer ")) {
      String token = header.substring(7);
      Authentication auth = jwtService.authenticate(token);
      SecurityContextHolder.getContext().setAuthentication(auth);
    }
    chain.doFilter(request, response);
  }
}

授权

  • 启用方法级安全:@EnableMethodSecurity
  • 使用@PreAuthorize("hasRole('ADMIN')")@PreAuthorize("@authz.canEdit(#id)")
  • 默认拒绝;仅公开必需的权限范围
@RestController
@RequestMapping("/api/admin")
public class AdminController {

  @PreAuthorize("hasRole('ADMIN')")
  @GetMapping("/users")
  public List<UserDto> listUsers() {
    return userService.findAll();
  }

  @PreAuthorize("@authz.isOwner(#id, authentication)")
  @DeleteMapping("/users/{id}")
  public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
    userService.delete(id);
    return ResponseEntity.noContent().build();
  }
}

输入验证

  • 在控制器上使用Bean Validation和@Valid注解
  • 在DTO上应用约束:@NotBlank@Email@Size、自定义验证器
  • 在渲染前使用白名单清理任何HTML内容
// 错误示例:无验证
@PostMapping("/users")
public User createUser(@RequestBody UserDto dto) {
  return userService.create(dto);
}

// 正确示例:已验证的DTO
public record CreateUserDto(
    @NotBlank @Size(max = 100) String name,
    @NotBlank @Email String email,
    @NotNull @Min(0) @Max(150) Integer age
) {}

@PostMapping("/users")
public ResponseEntity<UserDto> createUser(@Valid @RequestBody CreateUserDto dto) {
  return ResponseEntity.status(HttpStatus.CREATED)
      .body(userService.create(dto));
}

SQL注入防护

  • 使用Spring Data仓库或参数化查询
  • 对于原生查询,使用:param绑定;切勿拼接字符串
// 错误示例:原生查询中的字符串拼接
@Query(value = "SELECT * FROM users WHERE name = '" + name + "'", nativeQuery = true)

// 正确示例:参数化原生查询
@Query(value = "SELECT * FROM users WHERE name = :name", nativeQuery = true)
List<User> findByName(@Param("name") String name);

// 正确示例:Spring Data派生查询(自动参数化)
List<User> findByEmailAndActiveTrue(String email);

密码编码

  • 始终使用BCrypt或Argon2哈希密码——切勿存储明文
  • 使用PasswordEncoder Bean,而非手动哈希
@Bean
public PasswordEncoder passwordEncoder() {
  return new BCryptPasswordEncoder(12); // 成本因子12
}

// 在服务中
public User register(CreateUserDto dto) {
  String hashedPassword = passwordEncoder.encode(dto.password());
  return userRepository.save(new User(dto.email(), hashedPassword));
}

CSRF防护

  • 对于浏览器会话应用,保持CSRF启用;在表单/请求头中包含令牌
  • 对于使用Bearer令牌的纯API,禁用CSRF并依赖无状态认证
http
  .csrf(csrf -> csrf.disable())
  .sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS));

密钥管理

  • 源代码中不存放密钥;从环境变量或Vault加载
  • 保持application.yml无凭据;使用占位符
  • 定期轮换令牌和数据库凭据
# 错误示例:硬编码在application.yml中
spring:
  datasource:
    password: mySecretPassword123

# 正确示例:环境变量占位符
spring:
  datasource:
    password: ${DB_PASSWORD}

# 正确示例:Spring Cloud Vault集成
spring:
  cloud:
    vault:
      uri: https://vault.example.com
      token: ${VAULT_TOKEN}

安全头配置

http
  .headers(headers -> headers
    .contentSecurityPolicy(csp -> csp
      .policyDirectives("default-src 'self'"))
    .frameOptions(HeadersConfigurer.FrameOptionsConfig::sameOrigin)
    .xssProtection(Customizer.withDefaults())
    .referrerPolicy(rp -> rp.policy(ReferrerPolicyHeaderWriter.ReferrerPolicy.NO_REFERRER)));

CORS配置

  • 在安全过滤器级别配置CORS,而非每个控制器
  • 限制允许的来源——生产环境中切勿使用*
@Bean
public CorsConfigurationSource corsConfigurationSource() {
  CorsConfiguration config = new CorsConfiguration();
  config.setAllowedOrigins(List.of("https://app.example.com"));
  config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE"));
  config.setAllowedHeaders(List.of("Authorization", "Content-Type"));
  config.setAllowCredentials(true);
  config.setMaxAge(3600L);

  UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
  source.registerCorsConfiguration("/api/**", config);
  return source;
}

// 在SecurityFilterChain中:
http.cors(cors -> cors.configurationSource(corsConfigurationSource()));

速率限制

  • 在昂贵端点上应用Bucket4j或网关级限制
  • 记录并告警突发流量;返回429状态码并提示重试
// 使用Bucket4j进行端点级速率限制
@Component
public class RateLimitFilter extends OncePerRequestFilter {
  private final Map<String, Bucket> buckets = new ConcurrentHashMap<>();

  private Bucket createBucket() {
    return Bucket.builder()
        .addLimit(Bandwidth.classic(100, Refill.intervally(100, Duration.ofMinutes(1))))
        .build();
  }

  @Override
  protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
      FilterChain chain) throws ServletException, IOException {
    String clientIp = request.getRemoteAddr();
    Bucket bucket = buckets.computeIfAbsent(clientIp, k -> createBucket());

    if (bucket.tryConsume(1)) {
      chain.doFilter(request, response);
    } else {
      response.setStatus(HttpStatus.TOO_MANY_REQUESTS.value());
      response.getWriter().write("{\"error\": \"Rate limit exceeded\"}");
    }
  }
}

依赖安全

  • 在CI中运行OWASP Dependency Check / Snyk
  • 保持Spring Boot和Spring Security使用受支持的版本
  • 对已知CVE导致构建失败

日志记录与PII

  • 切勿记录密钥、令牌、密码或完整的主账号数据
  • 脱敏敏感字段;使用结构化JSON日志

文件上传

  • 验证大小、内容类型和扩展名
  • 存储在Web根目录之外;必要时进行扫描

发布前检查清单

  • [ ] 认证令牌已验证且正确过期
  • [ ] 每个敏感路径都有授权防护
  • [ ] 所有输入都已验证和清理
  • [ ] 无字符串拼接的SQL
  • [ ] CSRF策略与应用类型匹配
  • [ ] 密钥已外部化;未提交任何密钥
  • [ ] 安全头已配置
  • [ ] API已实施速率限制
  • [ ] 依赖项已扫描且为最新版本
  • [ ] 日志无敏感数据

记住:默认拒绝、验证输入、最小权限、配置先行安全。