springboot-security

springboot-security

熱門

Spring Security 最佳實務,涵蓋 Java Spring Boot 服務中的認證/授權、驗證、CSRF、機密管理、安全標頭、速率限制及相依性安全性。

23萬星標
3.5萬分支
更新於 2026/7/14
SKILL.md
readonlyread-only
name
springboot-security
description

Spring Security 最佳實務,涵蓋 Java Spring Boot 服務中的認證/授權、驗證、CSRF、機密管理、安全標頭、速率限制及相依性安全性。

Spring Boot 安全性審查

在新增驗證、處理輸入、建立端點或處理機密時使用。

啟用時機

  • 新增認證(JWT、OAuth2、基於 Session)
  • 實作授權(@PreAuthorize、角色權限控制)
  • 驗證使用者輸入(Bean Validation、自訂驗證器)
  • 設定 CORS、CSRF 或安全標頭
  • 管理機密(Vault、環境變數)
  • 新增速率限制或暴力破解防護
  • 掃描相依性中的 CVE

認證

  • 偏好無狀態 JWT 或附有撤銷清單的不透明 Token
  • 使用 httpOnlySecureSameSite=Strict Cookie 管理 Session
  • 透過 OncePerRequestFilter 或資源伺服器驗證 Token
@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();
  }
}

輸入驗證

  • 在 Controller 上使用 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 Repository 或參數化查詢
  • 對於原生查詢,使用 :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
}

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

CSRF 防護

  • 對於瀏覽器 Session 應用程式,保持 CSRF 啟用;在表單/標頭中包含 Token
  • 對於使用 Bearer Token 的純 API,停用 CSRF 並依賴無狀態認證
http
  .csrf(csrf -> csrf.disable())
  .sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS));

機密管理

  • 原始碼中不含機密;從環境變數或 Vault 載入
  • 保持 application.yml 不含憑證;使用佔位符
  • 定期輪換 Token 和資料庫憑證
# 錯誤:在 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,而非每個 Controller
  • 限制允許的來源——生產環境絕不使用 *
@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

  • 絕不記錄機密、Token、密碼或完整 PAN 資料
  • 遮罩敏感欄位;使用結構化 JSON 記錄

檔案上傳

  • 驗證大小、內容類型和副檔名
  • 儲存在 Web 根目錄之外;必要時掃描

發布前檢查清單

  • [ ] 認證 Token 已正確驗證且未過期
  • [ ] 每個敏感路徑都有授權防護
  • [ ] 所有輸入皆已驗證並清理
  • [ ] 無字串拼接的 SQL
  • [ ] CSRF 設定符合應用程式類型
  • [ ] 機密已外部化;無提交至版本控制
  • [ ] 安全標頭已設定
  • [ ] API 已啟用速率限制
  • [ ] 相依性已掃描且為最新版本
  • [ ] 記錄中無敏感資料

切記:預設拒絕、驗證輸入、最小權限、優先透過設定確保安全。