Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Spring Security best practices for authn/authz, validation, CSRF, secrets, headers, rate limiting, and dependency security in Java Spring Boot services.
.claude/skills/affaan-m-springboot-security/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-15 | ✗→✓ | ▲ Improved | 75% | 0% |
| case-12 | ✓→✗ | ▼ Worse | 95% | 0% |
| case-01 | ✓→✓ | = Same ✓ | 162% | 0% |
| case-02 | ✓→✓ | = Same ✓ | 335% | 0% |
| case-03 | ✓→✓ | = Same ✓ | 128% | 0% |
在添加身份验证、处理输入、创建端点或处理密钥时使用。
httpOnly、Secure、SameSite=Strict cookieOncePerRequestFilter 或资源服务器验证令牌java@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)")java@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(); } }
@Valid 的 Bean 验证@NotBlank、@Email、@Size、自定义验证器java// BAD: No validation @PostMapping("/users") public User createUser(@RequestBody UserDto dto) { return userService.create(dto); } // GOOD: Validated 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)); }
:param 绑定;切勿拼接字符串java// BAD: String concatenation in native query @Query(value = "SELECT * FROM users WHERE name = '" + name + "'", nativeQuery = true) // GOOD: Parameterized native query @Query(value = "SELECT * FROM users WHERE name = :name", nativeQuery = true) List<User> findByName(@Param("name") String name); // GOOD: Spring Data derived query (auto-parameterized) List<User> findByEmailAndActiveTrue(String email);
PasswordEncoder Bean,而非手动哈希java@Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(12); // cost factor 12 } // In service public User register(CreateUserDto dto) { String hashedPassword = passwordEncoder.encode(dto.password()); return userRepository.save(new User(dto.email(), hashedPassword)); }
javahttp .csrf(csrf -> csrf.disable()) .sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
application.yml 不包含凭据;使用占位符yaml# BAD: Hardcoded in application.yml spring: datasource: password: mySecretPassword123 # GOOD: Environment variable placeholder spring: datasource: password: ${DB_PASSWORD} # GOOD: Spring Cloud Vault integration spring: cloud: vault: uri: https://vault.example.com token: ${VAULT_TOKEN}
javahttp .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)));
*java@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; } // In SecurityFilterChain: http.cors(cors -> cors.configurationSource(corsConfigurationSource()));
java// Using Bucket4j for per-endpoint rate limiting @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\"}"); } } }
记住:默认拒绝、验证输入、最小权限、优先采用安全配置。
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | pass→pass | 6,395 | 6,652 | +4% | 1 | 1 | 0% | 1,326 | 3,479 | +162% | 0 | 0 | — |
case-02 | pass→pass | 3,339 | 3,117 | -7% | 1 | 1 | 0% | 602 | 2,619 | +335% | 0 | 0 | — |
case-03 | pass→pass | 6,440 | 4,423 | -31% | 1 | 1 | 0% | 1,213 | 2,765 | +128% | 0 | 0 | — |
case-04 | pass→pass | 10,114 | 9,398 | -7% | 1 | 1 | 0% | 2,084 | 3,924 | +88% | 0 | 0 | — |
case-05 | pass→pass | 6,776 | 6,580 | -3% | 1 | 1 | 0% | 1,332 | 3,484 | +162% | 0 | 0 | — |
case-06 | pass→pass | 13,005 | 11,334 | -13% | 1 | 1 | 0% | 2,732 | 4,350 | +59% | 0 | 0 | — |
case-07 | pass→pass | 10,199 | 21,006 | +106% | 1 | 1 | 0% | 1,970 | 3,987 | +102% | 0 | 0 | — |
case-08 | pass→pass | 5,550 | 3,464 | -38% | 1 | 1 | 0% | 1,070 | 2,713 | +154% | 0 | 0 | — |
case-09 | pass→pass | 8,628 | 5,184 | -40% | 1 | 1 | 0% | 1,790 | 3,085 | +72% | 0 | 0 | — |
case-10 | pass→pass | 6,122 | 5,110 | -17% | 1 | 1 | 0% | 1,213 | 3,180 | +162% | 0 | 0 | — |
case-11 | pass→pass | 8,146 | 6,025 | -26% | 1 | 1 | 0% | 1,647 | 3,211 | +95% | 0 | 0 | — |
case-12 | pass→fail | 9,359 | 13,668 | +46% | 1 | 1 | 0% | 1,872 | 3,648 | +95% | 0 | 0 | — |
case-13 | fail→fail | 8,929 | 7,458 | -16% | 1 | 1 | 0% | 2,076 | 3,644 | +76% | 0 | 0 | — |
case-14 | pass→pass | 9,066 | 7,564 | -17% | 1 | 1 | 0% | 1,823 | 3,733 | +105% | 0 | 0 | — |
case-15 | fail→pass | 9,545 | 6,872 | -28% | 1 | 1 | 0% | 1,939 | 3,401 | +75% | 0 | 0 | — |
case-16 | pass→pass | 15,232 | 15,057 | -1% | 1 | 1 | 0% | 2,697 | 4,965 | +84% | 0 | 0 | — |
case-17 | pass→pass | 17,401 | 17,261 | -1% | 1 | 1 | 0% | 3,164 | 5,535 | +75% | 0 | 0 | — |
case-18 | pass→pass | 16,479 | 12,836 | -22% | 1 | 1 | 0% | 2,816 | 4,448 | +58% | 0 | 0 | — |
case-19 | pass→pass | 7,657 | 7,126 | -7% | 1 | 1 | 0% | 1,613 | 3,536 | +119% | 0 | 0 | — |
case-20 | pass→pass | 15,676 | 29,124 | +86% | 1 | 1 | 0% | 3,163 | 5,163 | +63% | 0 | 0 | — |
case-21 | pass→pass | 13,107 | 12,099 | -8% | 1 | 1 | 0% | 2,589 | 4,324 | +67% | 0 | 0 | — |
case-22 | pass→pass | 6,754 | 6,114 | -9% | 1 | 1 | 0% | 1,368 | 3,268 | +139% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of 0 percentage points is the difference between those two pass rates over the 22 comparable cases. 1 case got worse with the skill loaded, and it is included in that figure.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.