mcpField
S
spring-security

spring-security

github.com/claude-dev-suiteSecurity4638 installsUpdated 2d ago
Repository

About spring-security

A quick-reference skill for Spring Security developers configuring authentication and authorization. It provides JWT, role and method security patterns alongside production checks and troubleshooting guidance.

Key features

  1. 01JWT authentication patterns
  2. 02Role-based access control
  3. 03Method-level authorization
  4. 04CORS and security headers
  5. 05Security testing guidance

Use cases

  1. 01Configure JWT authentication filters
  2. 02Restrict controller methods by role
  3. 03Diagnose 401, 403, and CORS issues

MCP support

The README references `mcp__documentation__fetch_docs` as a way to retrieve deeper Spring Security documentation.

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/claude-dev-suite/claude-dev-suite --skill spring-security

After installing, type/spring-securityin Claude Code to confirm it loaded.

Details

From the project README

Spring Security - Quick Reference

When to Use This Skill

  • Configure JWT authentication
  • Role-based access control
  • Method-level security

Deep Knowledge: Use mcp__documentation__fetch_docs with technology: spring-security for comprehensive documentation.

Essential Patterns

Security Config
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
@RequiredArgsConstructor
public class SecurityConfig {

    private final JwtAuthenticationFilter jwtAuthFilter;

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        return http
            .csrf(AbstractHttpConfigurer::disable)
            .cors(Customizer.withDefaults())
            .sessionManagement(s -> s.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/api/v1/auth/**").permitAll()
                .requestMatchers("/swagger-ui/**", "/v3/api-docs/**").permitAll()
                .requestMatchers("/api/v1/admin/**").hasRole("ADMIN")
                .anyRequest().authenticated())
            .addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class)
            .build();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}
JWT Filter
@Component
@RequiredArgsConstructor
public class JwtAuthenticationFilter extends OncePerRequestFilter {

    private final JwtService jwtService;
    private final UserDetailsService userDetailsService;

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

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

        String jwt = authHeader.substring(7);
        String email = jwtService.extractUsername(jwt);

        if (email != null && SecurityContextHolder.getContext().getAuthentication() == null) {
            UserDetails user = userDetailsService.loadUserByUsername(email);
            if (jwtService.isTokenValid(jwt, user)) {
                var auth = new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities());
                SecurityContextHolder.getContext().setAuthentication(auth);
            }
        }
        chain.doFilter(request, response);
    }
}
Method Security
@PreAuthorize("hasRole('ADMIN')")
@DeleteMapping("/{id}")
public void delete(@PathVariable Long id) { ... }

@PreAuthorize("#id == authentication.principal.id or hasRole('ADMIN')")
@GetMapping("/{id}/profile")
public UserProfile getProfile(@PathVariable Long id) { ... }
UserDetails
public class UserPrincipal implements UserDetails {
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return List.of(new SimpleGrantedAuthority("ROLE_" + role.name()));
    }
}

Common Annotations

AnnotationUsage
@PreAuthorizeCheck before method execution
hasRole('X')Requires role
hasAnyRoleAny of the roles

Anti-Patterns to Avoid

  • Do not disable CSRF without stateless
  • Do not forget CORS for frontend
  • Do not hardcode JWT secret

Production Readiness

JWT Service
@Service
@RequiredArgsConstructor
public class JwtService {

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

    @Value("${security.jwt.expiration}")
    private long jwtExpiration;

    @Value("${security.jwt.refresh-expiration}")
    private long refreshExpiration;

    public String generateToken(UserDetails userDetails) {
        return buildToken(userDetails, jwtExpiration);
    }

    public String generateRefreshToken(UserDetails userDetails) {
        return buildToken(userDetails, refreshExpiration);
    }

    private String buildToken(UserDetails userDetails, long expiration) {
        return Jwts.builder()
            .setSubject(userDetails.getUsername())
            .claim("roles", userDetails.getAuthorities().stream()
                .map(GrantedAuthority::getAuthority)
                .toList())
            .setIssuedAt(new Date())
            .setExpiration(new Date(System.currentTimeMillis() + expiration))
            .signWith(getSigningKey(), SignatureAlgorithm.HS256)
            .compact();
    }

    public boolean isTokenValid(String token, UserDetails userDetails) {
        try {
            final String username = extractUsername(token);
            return username.equals(userDetails.getUsername()) && !isTokenExpired(token);
        } catch (ExpiredJwtException | MalformedJwtException e) {
            return false;
        }
    }

    private SecretKey getSigningKey() {
        byte[] keyBytes = Decoders.BASE64.decode(secretKey);
        return Keys.hmacShaKeyFor(keyBytes);
    }
}
CORS Configuration
@Configuration
public class CorsConfig {

    @Bean
    public CorsFilter corsFilter() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        CorsConfiguration config = new CorsConfiguration();

        config.setAllowCredentials(true);
        config.setAllowedOrigins(List.of(
            "https://yourdomain.com",
            "https://app.yourdomain.com"
        ));
        config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS"));
        config.setAllowedHeaders(List.of("*"));
        config.setExposedHeaders(List.of("Authorization"));
        config.setMaxAge(3600L);

        source.registerCorsConfiguration("/**", config);
        return new CorsFilter(source);
    }
}
Rate Limiting
@Component
public class RateLimitingFilter extends OncePerRequestFilter {

    private final Cache<String, AtomicInteger> requestCounts = Caffeine.newBuilder()
        .expireAfterWrite(1, TimeUnit.MINUTES)
        .build();

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

        String clientIP = getClientIP(request);
        AtomicInteger count = requestCounts.get(clientIP, k -> new AtomicInteger(0));

        if (count.incrementAndGet() > 100) {
            response.setStatus(HttpStatus.TOO_MANY_REQUESTS.value());
            response.getWriter().write("{\"error\": \"Rate limit exceeded\"}");
            return;
        }

        chain.doFilter(request, response);
    }

    private String getClientIP(HttpServletRequest request) {
        String xForwardedFor = request.getHeader("X-Forwarded-For");
        if (xForwardedFor != null && !xForwardedFor.isEmpty()) {
            return xForwardedFor.split(",")[0].trim();
        }
        return request.getRemoteAddr();
    }
}
Security Headers
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    return http
        // ... existing config
        .headers(headers -> headers
            .contentSecurityPolicy(csp -> csp
                .policyDirectives("default-src 'self'; frame-ancestors 'none'"))
            .frameOptions(frame -> frame.deny())
            .xssProtection(xss -> xss.block(true))
            .contentTypeOptions(Customizer.withDefaults())
            .referrerPolicy(referrer -> referrer
                .policy(ReferrerPolicyHeaderWriter.ReferrerPolicy.STRICT_ORIGIN_WHEN_CROSS_ORIGIN)))
        .build();
}
Security Testing
@WebMvcTest(UserController.class)
@Import(SecurityConfig.class)
class UserControllerSecurityTest {

    @Autowired
    private MockMvc mockMvc;

    @MockBean
    private UserService userService;

    @Test
    void unauthenticated_shouldReturn401() throws Exception {
        mockMvc.perform(get("/api/v1/users"))
            .andExpect(status().isUnauthorized());
    }

    @Test
    @WithMockUser(roles = "ADMIN")
    void adminEndpoint_withAdminRole_shouldSucceed() throws Exception {
        mockMvc.perform(delete("/api/v1/admin/users/1"))
            .andExpect(status().isOk());
    }

    @Test
    @WithMockUser(roles = "USER")
    void adminEndpoint_withUserRole_shouldReturn403() throws Exception {
        mockMvc.perform(delete("/api/v1/admin/users/1"))
            .andExpect(status().isForbidden());
    }
}
Monitoring Metrics
MetricTarget
Failed auth attemptsMonitor spikes
Token validation time< 5ms
Rate limit hits< 1%
Security exceptions0 unhandled
Checklist
  • JWT secret from environment variable
  • Token expiration configured
  • Refresh token mechanism
  • CORS whitelist (no wildcards in prod)
  • Rate limiting enabled
  • Security headers configured
  • Method-level security (@PreAuthorize)
  • Password encoding (BCrypt)
  • HTTPS enforced
  • Security integration tests

When NOT to Use This Skill

  • Spring Boot application setup → Use spring-boot skill
  • OAuth2 server implementation → Use OAuth2-specific skill
  • General cryptography → Use security-expert skill
  • Database access → Use spring-data-jpa skill
  • REST API patterns → Use spring-web skill

Anti-Patterns

Anti-PatternWhy It's BadCorrect Approach
Hardcoded JWT secretSecurity riskUse environment variables
Disable CSRF without statelessVulnerable to attacksOnly disable with JWT (stateless)
Permit all in productionNo securityConfigure proper authorization
Store passwords in plain textCritical security flawUse BCryptPasswordEncoder
No token expirationTokens never invalidateSet proper expiration time
Allow all CORS originsCSRF vulnerabilityWhitelist specific origins

Quick Troubleshooting

ProblemLikely CauseSolution
401 UnauthorizedToken missing/invalidCheck Authorization header format
403 ForbiddenInsufficient permissionsCheck user roles and @PreAuthorize
CORS errorsOrigin not allowedAdd origin to CORS configuration
Filter not executingWrong filter orderCheck addFilterBefore placement
JWT parsing failsWrong secret keyVerify JWT_SECRET matches
Authentication nullFilter not appliedEnsure SecurityContextHolder is set

Further Reading

For advanced configurations: mcp__documentation__fetch_docs

Comments

Markdown supported