Skip to content
Back to skills

Springboot Security

ASecurity

Spring Security 6.x best practices. Covers SecurityFilterChain, JWT auth, OAuth2, CORS, CSRF, method-level security, password encoding, and rate limiting for Spring Boot 3.x applications.

  • 3 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added October 14, 2026
ai-agentsjavaspringgitapidatabasesecurity

Works with

  • claude code
  • cli
  • api

Security analysis

A100/100

Scanned October 14, 2026

npx -y skills add peopleforrester/claude-dotfiles --skill springboot-security --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Springboot Security?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Springboot Security
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/peopleforrester-springboot-security/badge)](https://www.skillsdirectory.com/skills/peopleforrester-springboot-security)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: springboot-security
description: |
  Spring Security 6.x best practices. Covers SecurityFilterChain, JWT auth,
  OAuth2, CORS, CSRF, method-level security, password encoding, and
  rate limiting for Spring Boot 3.x applications.
license: MIT
compatibility: Claude Code 2.1+
metadata:
  author: peopleforrester
  version: "1.0.0"
  tags:
    - security
    - spring-boot
    - framework
    - java
---

# Spring Boot Security Patterns

Security configuration for Spring Boot 3.x with Spring Security 6.x.

## SecurityFilterChain Configuration

```java
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
@RequiredArgsConstructor
public class SecurityConfig {

    private final JwtAuthFilter jwtAuthFilter;

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        return http
            .csrf(csrf -> csrf.disable())  // Disable for stateless API
            .cors(cors -> cors.configurationSource(corsConfig()))
            .sessionManagement(session ->
                session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/api/auth/**").permitAll()
                .requestMatchers("/actuator/health").permitAll()
                .requestMatchers("/api/admin/**").hasRole("ADMIN")
                .anyRequest().authenticated())
            .addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class)
            .build();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder(12);
    }

    private CorsConfigurationSource corsConfig() {
        CorsConfiguration config = new CorsConfiguration();
        config.setAllowedOrigins(List.of("https://example.com"));
        config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE"));
        config.setAllowedHeaders(List.of("Authorization", "Content-Type"));
        config.setMaxAge(3600L);
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/api/**", config);
        return source;
    }
}
```

## JWT Authentication Filter

```java
@Component
@RequiredArgsConstructor
public class JwtAuthFilter extends OncePerRequestFilter {

    private final JwtService jwtService;
    private final UserDetailsService userDetailsService;

    @Override
    protected void doFilterInternal(
            HttpServletRequest request,
            HttpServletResponse response,
            FilterChain chain) throws ServletException, IOException {

        String header = request.getHeader("Authorization");
        if (header == null || !header.startsWith("Bearer ")) {
            chain.doFilter(request, response);
            return;
        }

        String token = header.substring(7);
        String username = jwtService.extractUsername(token);

        if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
            UserDetails userDetails = userDetailsService.loadUserByUsername(username);
            if (jwtService.isValid(token, userDetails)) {
                UsernamePasswordAuthenticationToken auth =
                    new UsernamePasswordAuthenticationToken(
                        userDetails, null, userDetails.getAuthorities());
                auth.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                SecurityContextHolder.getContext().setAuthentication(auth);
            }
        }

        chain.doFilter(request, response);
    }
}
```

## JWT Service

```java
@Service
public class JwtService {

    @Value("${jwt.secret}")
    private String secretKey;

    @Value("${jwt.expiration:3600000}")  // 1 hour default
    private long expiration;

    public String generateToken(UserDetails userDetails) {
        return Jwts.builder()
            .subject(userDetails.getUsername())
            .issuedAt(new Date())
            .expiration(new Date(System.currentTimeMillis() + expiration))
            .signWith(getSigningKey())
            .compact();
    }

    public String extractUsername(String token) {
        return extractClaim(token, Claims::getSubject);
    }

    public boolean isValid(String token, UserDetails userDetails) {
        String username = extractUsername(token);
        return username.equals(userDetails.getUsername()) && !isExpired(token);
    }

    private boolean isExpired(String token) {
        return extractClaim(token, Claims::getExpiration).before(new Date());
    }

    private <T> T extractClaim(String token, Function<Claims, T> resolver) {
        Claims claims = Jwts.parser()
            .verifyWith(getSigningKey())
            .build()
            .parseSignedClaims(token)
            .getPayload();
        return resolver.apply(claims);
    }

    private SecretKey getSigningKey() {
        return Keys.hmacShaKeyFor(Decoders.BASE64.decode(secretKey));
    }
}
```

## Method-Level Security

```java
@Service
@RequiredArgsConstructor
public class UserServiceImpl implements UserService {

    @PreAuthorize("hasRole('ADMIN')")
    public void deleteUser(UUID id) {
        userRepository.deleteById(id);
    }

    @PreAuthorize("#userId == authentication.principal.id or hasRole('ADMIN')")
    public UserResponse getUser(UUID userId) {
        return userRepository.findById(userId)
            .map(UserResponse::from)
            .orElseThrow(() -> new ResourceNotFoundException("User", userId));
    }

    @PostAuthorize("returnObject.email == authentication.principal.username")
    public UserResponse getCurrentUser() {
        // Only return if result matches authenticated user
        return findCurrentUser();
    }
}
```

## Input Validation

```java
// Request DTOs with validation constraints
public record LoginRequest(
    @NotBlank @Email String email,
    @NotBlank @Size(min = 8, max = 128) String password
) {}

public record RegisterRequest(
    @NotBlank @Size(min = 2, max = 50) String name,
    @NotBlank @Email String email,
    @NotBlank @Size(min = 12, max = 128)
    @Pattern(regexp = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d).*$",
             message = "Must contain uppercase, lowercase, and digit")
    String password
) {}
```

## Rate Limiting

```java
// Using Bucket4j for rate limiting
@Component
@RequiredArgsConstructor
public class RateLimitFilter extends OncePerRequestFilter {

    private final Map<String, Bucket> buckets = new ConcurrentHashMap<>();

    @Override
    protected void doFilterInternal(
            HttpServletRequest request,
            HttpServletResponse response,
            FilterChain chain) throws ServletException, IOException {

        String clientIp = request.getRemoteAddr();
        Bucket bucket = buckets.computeIfAbsent(clientIp, this::createBucket);

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

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

## Security Headers

```java
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    return http
        .headers(headers -> headers
            .contentTypeOptions(Customizer.withDefaults())
            .frameOptions(frame -> frame.deny())
            .httpStrictTransportSecurity(hsts -> hsts
                .includeSubDomains(true)
                .maxAgeInSeconds(31536000))
            .contentSecurityPolicy(csp -> csp
                .policyDirectives("default-src 'self'; frame-ancestors 'none'")))
        // ... rest of config
        .build();
}
```

## Secrets Configuration

```yaml
# application.yml - reference environment variables
spring:
  datasource:
    url: ${DATABASE_URL}
    username: ${DB_USERNAME}
    password: ${DB_PASSWORD}

jwt:
  secret: ${JWT_SECRET_KEY}
  expiration: ${JWT_EXPIRATION:3600000}
```

## Checklist

- [ ] SecurityFilterChain configured (not deprecated WebSecurityConfigurerAdapter)
- [ ] Stateless session management for API applications
- [ ] BCrypt password encoder with cost factor >= 12
- [ ] JWT secrets loaded from environment, never hardcoded
- [ ] Method-level security with `@PreAuthorize` for fine-grained access
- [ ] CORS restricted to specific origins
- [ ] Rate limiting on authentication endpoints
- [ ] Input validation on all request DTOs
- [ ] Security headers configured (HSTS, CSP, X-Frame-Options)
- [ ] Actuator endpoints secured (health only public)

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.