From spring
Secure Spring applications with `SecurityFilterChain`, authentication and authorization rules, bearer-token resource servers, method security, session and CSRF policy, and Spring Security tests. Use this skill when securing Spring applications with `SecurityFilterChain`, authentication and authorization rules, password storage, bearer-token resource servers using JWT verification or opaque-token introspection, method security, session and CSRF policy, and Spring Security tests.
npx claudepluginhub ririnto/sinon --plugin springThis skill uses the workspace's default tool permissions.
Use this skill when securing Spring applications with `SecurityFilterChain`, authentication and authorization rules, password storage, bearer-token resource servers using JWT verification or opaque-token introspection, method security, session and CSRF policy, and Spring Security tests.
references/delegated-login-and-oauth2-client.mdreferences/jwt-claim-mapping.mdreferences/ldap-authentication.mdreferences/multiple-security-filter-chains.mdreferences/reactive-webflux-security.mdreferences/saml2-login.mdreferences/security-exception-handling.mdreferences/security-headers.mdreferences/servlet-opaque-token-resource-server.mdreferences/session-management-and-logout.mdMandates invoking relevant skills via tools before any response in coding sessions. Covers access, priorities, and adaptations for Claude Code, Copilot CLI, Gemini CLI.
Share bugs, ideas, or general feedback.
Use this skill when securing Spring applications with SecurityFilterChain, authentication and authorization rules, password storage, bearer-token resource servers using JWT verification or opaque-token introspection, method security, session and CSRF policy, and Spring Security tests.
Use spring-security for application-side authentication, authorization, filter-chain design, method security, CORS and CSRF policy, session behavior, logout behavior, security headers, exception handling, and security-focused tests.
The ordinary Spring Security job is:
SecurityFilterChain instead of relying on hidden defaults.SKILL.md for the ordinary servlet path: one main SecurityFilterChain, explicit authorization rules, password encoding, basic CORS and CSRF policy, session policy, logout behavior, JWT resource-server validation, method security, and allow or deny tests.SecurityWebFilterChain.scope to authority conversion is not enough or issuer, audience, algorithm, or principal-claim rules must be customized.The standalone examples in this section pin the current stable Spring Security BOM, 7.0.5.
Use this path when the application intentionally stays on the Spring Boot-managed Spring Security line for that Boot release.
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
If the application stays on a Spring Boot line that still manages Spring Security 6.x, either keep that Boot-managed 6.x path or move to a platform line that is compatible with Spring Security 7 and Spring Framework 7 before copying the Spring Security 7 APIs shown here.
Use this path when the build must target the standalone Spring Security BOM instead of the Boot-managed line.
<dependencyManagement>
<dependencies>
<dependency><groupId>org.springframework.security</groupId><artifactId>spring-security-bom</artifactId><version>7.0.5</version><type>pom</type><scope>import</scope></dependency>
</dependencies>
</dependencyManagement>
Keep Spring Security modules versionless because the imported BOM manages their versions. This is standard Maven dependency management, not a Spring Security feature gate.
For the ordinary servlet path shown in this skill, add the core modules explicitly under that BOM:
<dependencies>
<dependency><groupId>org.springframework.security</groupId><artifactId>spring-security-config</artifactId></dependency>
<dependency><groupId>org.springframework.security</groupId><artifactId>spring-security-web</artifactId></dependency>
<dependency><groupId>org.springframework.security</groupId><artifactId>spring-security-test</artifactId><scope>test</scope></dependency>
</dependencies>
Add the Boot starter only when the Boot-managed application validates incoming bearer tokens.
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-oauth2-resource-server</artifactId></dependency>
Use the direct Spring Security modules when the build follows the standalone BOM path instead of Boot starters.
<dependency><groupId>org.springframework.security</groupId><artifactId>spring-security-oauth2-resource-server</artifactId></dependency>
Add JOSE support as well when the resource server validates JWTs locally.
<dependency><groupId>org.springframework.security</groupId><artifactId>spring-security-oauth2-jose</artifactId></dependency>
./mvnw test
./gradlew test
Spring Security 7 provides PathPatternRequestMatcher for direct matcher construction. Use it when a matcher instance must be explicit, for example in custom securityMatcher(...) logic or filter-chain composition.
PathPatternRequestMatcher apiMatcher = PathPatternRequestMatcher.pathPattern("/api/**");
For an HTTP-method-specific matcher:
PathPatternRequestMatcher itemMatcher = PathPatternRequestMatcher.pathPattern(HttpMethod.GET, "/api/items/{id}");
Use the requestMatchers("/path/**") DSL for ordinary rules. Reach for PathPatternRequestMatcher only when you need an explicit matcher object.
Use this shape for token-based APIs that should not create login sessions.
@Configuration
@EnableMethodSecurity
class SecurityConfig {
@Bean
SecurityFilterChain api(HttpSecurity http) throws Exception {
return http
.authorizeHttpRequests(auth -> auth.requestMatchers("/actuator/health").permitAll().requestMatchers(HttpMethod.GET, "/api/public/**").permitAll().anyRequest().authenticated())
.cors(Customizer.withDefaults())
.csrf(csrf -> csrf.ignoringRequestMatchers("/api/**", "/actuator/health"))
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.oauth2ResourceServer(oauth2 -> oauth2.jwt(jwt -> jwt.jwtAuthenticationConverter(jwtAuthenticationConverter())))
.build();
}
@Bean
Converter<Jwt, ? extends AbstractAuthenticationToken> jwtAuthenticationConverter() {
JwtGrantedAuthoritiesConverter authorities = new JwtGrantedAuthoritiesConverter();
authorities.setAuthorityPrefix("SCOPE_");
authorities.setAuthoritiesClaimName("scope");
JwtAuthenticationConverter converter = new JwtAuthenticationConverter();
converter.setJwtGrantedAuthoritiesConverter(authorities);
converter.setPrincipalClaimName("sub");
return converter;
}
}
Keep securityMatcher(...) out of a single-chain baseline unless the application also defines an explicit fallback chain, because unmatched requests are left unprotected.
For browser SPAs that use session cookies, use Spring Security's SPA CSRF support instead of disabling CSRF entirely.
@Bean
SecurityFilterChain spa(HttpSecurity http) throws Exception {
return http
.authorizeHttpRequests(auth -> auth.requestMatchers("/login", "/assets/**").permitAll().anyRequest().authenticated())
.formLogin(Customizer.withDefaults())
.csrf(csrf -> csrf.spa())
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED))
.build();
}
Spring Security's SPA support uses CookieCsrfTokenRepository under the hood. In the Spring Security 7 line, the default cookie name is XSRF-TOKEN and the default request header is X-XSRF-TOKEN.
Keep pure bearer-token API endpoints in a separate stateless chain if they must ignore CSRF entirely.
Spring Security 6 and 7 tighten session behavior. Key changes to keep explicit:
SessionCreationPolicy.STATELESS tells Spring Security not to create an HttpSession and not to use one to obtain the SecurityContext.SessionCreationPolicy.IF_REQUIRED creates a session only when authentication is needed.SessionCreationPolicy.NEVER never creates a session but uses an existing one if present.SessionCreationPolicy.ALWAYS always creates a session if one does not exist.SessionManagementFilter no longer reads the session on every request just to detect a newly authenticated user.SecurityContext explicitly through the chosen SecurityContextRepository.// Bearer-token API
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
// Browser login
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED))
Use an explicit CorsConfigurationSource when a browser client calls your API from another origin.
@Bean
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(List.of("https://app.example.com"));
configuration.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS"));
configuration.setAllowedHeaders(List.of("Authorization", "Content-Type"));
configuration.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
Use this shape for server-rendered or browser-session applications.
@Bean
SecurityFilterChain browser(HttpSecurity http) throws Exception {
return http
.authorizeHttpRequests(auth -> auth.requestMatchers("/login", "/assets/**").permitAll().anyRequest().authenticated())
.formLogin(Customizer.withDefaults())
.logout(logout -> logout.logoutUrl("/logout").logoutSuccessUrl("/login?logout"))
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED))
.build();
}
@Bean
PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
PasswordEncoderFactories.createDelegatingPasswordEncoder() creates a delegating encoder that stores the algorithm prefix inside the stored hash, supporting migration from older encoders.
For applications that need a specific hash without the delegating wrapper, use the current documented Spring Security defaults directly:
@Bean
PasswordEncoder passwordEncoder() {
return Argon2PasswordEncoder.defaultsForSpringSecurity_v5_8();
// return new BCryptPasswordEncoder();
// return Pbkdf2PasswordEncoder.defaultsForSpringSecurity_v5_8();
}
Use the documented defaults when selecting a concrete encoder directly. When migrating from weaker hashes, store the old encoder prefix alongside hashes and delegate to the new encoder.
Keep the OAuth2 and OIDC branch explicit. Open references/delegated-login-and-oauth2-client.md when the application delegates browser login to an external IdP or manages outbound OAuth2 client tokens for downstream calls.
PasswordEncoder and never store raw passwords.@PostAuthorize, @PreFilter, or @PostFilter only when access depends on returned objects or filtered collections.@Service
class InvoiceService {
@PreAuthorize("hasAuthority('SCOPE_invoices:write')")
void approve(long invoiceId) {
}
@PostAuthorize("returnObject.owner().equals(authentication.name)")
InvoiceView load(long invoiceId) {
return new InvoiceView(invoiceId, "alice");
}
}
@PreFilter(filterTarget = "invoiceIds", value = "filterObject > 0")
void reprocess(List<Long> invoiceIds) {
}
@WebMvcTest(AdminController.class)
@Import(SecurityConfig.class)
class AdminControllerTests {
@Autowired
MockMvc mvc;
@MockBean
JwtDecoder jwtDecoder;
@Test
void requiresScope() throws Exception {
mvc.perform(get("/admin")
.with(jwt().authorities(new SimpleGrantedAuthority("SCOPE_admin"))))
.andExpect(status().isOk());
}
}
@WebMvcTest(AdminController.class)
@Import(SecurityConfig.class)
class AdminControllerTests {
@Autowired
MockMvc mvc;
@Test
@WithMockUser(username = "alice@example.com", authorities = "ROLE_ADMIN")
void adminAccessForAuthenticatedUser() throws Exception {
mvc.perform(get("/admin"))
.andExpect(status().isOk());
}
@Test
void rejectsAnonymousRequest() throws Exception {
mvc.perform(get("/admin"))
.andExpect(status().is3xxRedirection());
}
}
Use @WithUserDetails only when the test slice also provides the matching UserDetailsService entry for that username.
@Test
void rejectsAnonymousRequest() throws Exception {
mvc.perform(get("/admin"))
.andExpect(status().isUnauthorized());
}
Use this 401 expectation only for API/basic/bearer-token style security chains that do not redirect to a login page.
@Test
void rejectsInsufficientAuthority() throws Exception {
mvc.perform(get("/admin")
.with(jwt().authorities(new SimpleGrantedAuthority("SCOPE_read"))))
.andExpect(status().isForbidden());
}
SCOPE_invoices:write
.requestMatchers(HttpMethod.GET, "/api/public/**").permitAll()
.oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults()))
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.logout(logout -> logout.logoutSuccessUrl("/login?logout"))
Return:
/actuator paths unless explicitly intended. Permitting /actuator/health alone does not imply actuator endpoints are publicly accessible; secure any additional actuator endpoints explicitly.SecurityWebFilterChain and reactive security behavior.