Spring Security provides authentication, authorization, exploit protection, and integrations for servlet and reactive applications. Correct configuration depends on the application's credential transport, clients, trust boundaries, and threat model.
This guide reflects current Spring Security 7.x and Spring Boot 4.1 starter names as of September 2026. It covers authentication, authorization, JWT access tokens, OAuth 2.0, OpenID Connect, browser protections, and testing.
Table of Contents
- Security Fundamentals Questions
- Authentication Questions
- Authorization Questions
- JWT Authentication Questions
- OAuth2 and OpenID Connect Questions
- Security Configuration Questions
- Security Best Practices Questions
Security Fundamentals Questions
Understanding the core concepts is essential before diving into Spring Security specifics.
What is the difference between authentication and authorization?
Authentication and authorization are the two fundamental pillars of application security, and confusing them is a common mistake. Authentication answers the question "Who are you?" by verifying identity through credentials like username/password, tokens, or certificates. Authorization comes after and answers "What can you do?" by checking whether the authenticated user has permission to perform a specific action.
In current Spring Security, an AuthenticationManager commonly delegates authentication to one or more AuthenticationProviders. Request and method authorization use AuthorizationManager implementations. An authorization rule may also permit an anonymous request, so do not model every request as a mandatory two-step pipeline.
| Concept | Question | Spring Security Component |
|---|---|---|
| Authentication | Who are you? | AuthenticationManager |
| Authorization | What can you do? | AuthorizationManager |
Authentication verifies identity through credentials:
- Username and password
- JWT tokens
- OAuth2 tokens
- Certificates
Authorization checks permissions after authentication:
- Role-based (ROLE_ADMIN, ROLE_USER)
- Permission-based (READ_PRIVILEGE, WRITE_PRIVILEGE)
- Resource-based (own resources only)
How does the Spring Security filter chain work?
Spring Security is built on servlet filters, which is fundamental to understanding how it processes requests. Every HTTP request passes through a chain of security filters before reaching your controller. Each filter has a specific responsibility—some handle authentication, others handle authorization, and some handle specific attack vectors like CSRF.
FilterChainProxy selects the first SecurityFilterChain whose matcher applies. The selected filters run in framework-defined order. Configuration changes the list, so inspect the logged chain and place a custom filter relative to a known filter only when a built-in authentication mechanism cannot solve the requirement.
flowchart LR
REQ["Request"] --> CHAIN["Filter Chain"] --> SERVLET["Servlet<br/>(Controller)"]flowchart TB
subgraph CHAIN["Security Filter Chain"]
F1["1. SecurityContextHolderFilter<br/><i>Loads deferred SecurityContext</i>"]
F2["2. Exploit-protection filters<br/><i>Headers, CORS, CSRF as configured</i>"]
F3["3. Authentication filters<br/><i>Only mechanisms you enable</i>"]
F4["4. AnonymousAuthenticationFilter<br/><i>Optional anonymous identity</i>"]
F5["5. ExceptionTranslationFilter<br/><i>Starts authentication or handles denied access</i>"]
F6["6. AuthorizationFilter<br/><i>Uses AuthorizationManager</i>"]
F1 --> F2 --> F3 --> F4 --> F5 --> F6
endHow do you access the current user in Spring Security?
Spring Security stores the current Authentication in a SecurityContext, accessed through SecurityContextHolder. The default servlet strategy uses a ThreadLocal, which does not automatically cross arbitrary executor or reactive boundaries; prefer controller argument resolution and supported context-propagation integrations over static access deep in domain code. Credentials are commonly erased after authentication and the principal is not always UserDetails.
In controllers, you can also use the @AuthenticationPrincipal annotation to inject the current user directly as a method parameter, which is cleaner than accessing SecurityContextHolder manually.
// Get the current authenticated user
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
// Get username
String username = authentication.getName();
// Get authorities (roles/permissions)
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
// Get principal (usually UserDetails)
Object principal = authentication.getPrincipal();
if (principal instanceof UserDetails) {
UserDetails user = (UserDetails) principal;
String username = user.getUsername();
}
// In a controller, inject directly
@GetMapping("/profile")
public ResponseEntity<User> getProfile(@AuthenticationPrincipal UserDetails user) {
return ResponseEntity.ok(userService.findByUsername(user.getUsername()));
}Authentication Questions
Spring Security supports multiple authentication mechanisms. Here's how to implement the most common ones.
How do you configure Spring Security in Spring Boot 4?
Modern Spring Security uses component-based configuration with SecurityFilterChain; the old WebSecurityConfigurerAdapter is gone. With multiple chains, give each a securityMatcher and intentional order, because FilterChainProxy uses the first matching chain.
The basic configuration defines which requests require authentication, how login works, and how logout is handled. Provide a PasswordEncoder only when the application authenticates passwords. A delegating encoder preserves an algorithm identifier and supports gradual upgrades.
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/public/**").permitAll()
.requestMatchers(HttpMethod.POST, "/auth/login").permitAll()
.requestMatchers("/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
)
.formLogin(form -> form
.loginPage("/login")
.defaultSuccessUrl("/dashboard")
.permitAll()
)
.logout(logout -> logout
.logoutSuccessUrl("/login?logout")
.permitAll()
);
return http.build();
}
@Bean
public PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
}How do you implement UserDetailsService?
UserDetailsService is the core interface for loading user-specific data during authentication. Spring Security calls its loadUserByUsername method when a user attempts to log in, and you're responsible for fetching the user from your database and returning a UserDetails object.
The implementation should throw UsernameNotFoundException if the user doesn't exist. The returned UserDetails must include the encoded password and the user's roles/authorities.
@Service
public class CustomUserDetailsService implements UserDetailsService {
private final UserRepository userRepository;
public CustomUserDetailsService(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("User not found"));
return org.springframework.security.core.userdetails.User.builder()
.username(user.getUsername())
.password(user.getPassword()) // Already encoded
.roles(user.getRoles().toArray(new String[0]))
.build();
}
}How do you create a custom UserDetails implementation?
The UserDetails interface describes password-authentication state and authorities. A custom implementation can carry a stable local subject identifier, but keep the principal small and immutable: do not retain an attached JPA entity, password-reset data, provider profile, or unrelated personal data in a session-backed security context.
Normalize authority names once. hasRole("ADMIN") checks ROLE_ADMIN, while hasAuthority checks the exact value. Do not automatically convert untrusted user/profile input into an authority.
public class CustomUserDetails implements UserDetails {
private final Long id;
private final String username;
private final String encodedPassword;
private final boolean locked;
private final boolean enabled;
private final List<GrantedAuthority> authorities;
public CustomUserDetails(User user) {
this.id = user.getId();
this.username = user.getUsername();
this.encodedPassword = user.getPassword();
this.locked = user.isLocked();
this.enabled = user.isEnabled();
this.authorities = user.getRoles().stream()
.map(role -> new SimpleGrantedAuthority("ROLE_" + role.getName()))
.map(GrantedAuthority.class::cast)
.toList();
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorities;
}
@Override
public String getPassword() {
return encodedPassword;
}
@Override
public String getUsername() {
return username;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return !locked;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return enabled;
}
public Long getId() {
return id;
}
}How should you encode passwords in Spring Security?
Store passwords with a salted adaptive one-way function such as bcrypt, PBKDF2, scrypt, or Argon2, tuned on the deployed hardware and protected by login throttling. No algorithm is "future-proof". DelegatingPasswordEncoder records an algorithm id and verifies configured legacy formats; persistence of a stronger encoding after successful login requires an update path such as UserDetailsPasswordService.
When registering users, encode the password before saving. When authenticating, Spring Security automatically uses the same encoder to verify the password matches. For applications with legacy password formats, DelegatingPasswordEncoder can handle migration.
@Bean
public PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
// Usage in registration
public User registerUser(RegisterRequest request) {
User user = new User();
user.setUsername(request.getUsername());
user.setPassword(passwordEncoder.encode(request.getPassword()));
return userRepository.save(user);
}
// Password encoders available
BCryptPasswordEncoder // Adaptive, widely supported
Argon2PasswordEncoder // Memory-hard; requires Bouncy Castle here
SCryptPasswordEncoder // Memory-hard alternative
Pbkdf2PasswordEncoder // Iteration-based alternativeWhen should you create a custom AuthenticationProvider?
AuthenticationProvider is the interface responsible for the actual authentication logic. Spring Security provides default implementations for common scenarios, but you need a custom provider when you have special authentication requirements—like checking additional conditions beyond username and password, integrating with external authentication systems, or implementing custom authentication schemes.
The provider's authenticate method receives the authentication request and returns a fully authenticated token on success, or throws an exception on failure.
@Component
@RequiredArgsConstructor
public class CustomAuthenticationProvider implements AuthenticationProvider {
private final UserDetailsService userDetailsService;
private final PasswordEncoder passwordEncoder;
@Override
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
String username = authentication.getName();
String password = authentication.getCredentials().toString();
UserDetails user = userDetailsService.loadUserByUsername(username);
if (!passwordEncoder.matches(password, user.getPassword())) {
throw new BadCredentialsException("Invalid password");
}
// Additional checks
if (!user.isEnabled()) {
throw new DisabledException("Account is disabled");
}
return UsernamePasswordAuthenticationToken.authenticated(
user, null, user.getAuthorities());
}
@Override
public boolean supports(Class<?> authentication) {
return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
}
}Authorization Questions
Authorization controls what authenticated users can access.
How do you configure URL-based authorization?
URL-based authorization is configured in the SecurityFilterChain and determines which endpoints require authentication and what roles or authorities are needed. The order of matchers matters—more specific patterns should come before general ones. Spring Security evaluates them in order and uses the first match.
You can use role checks (hasRole), authority checks (hasAuthority), or even SpEL expressions for complex authorization logic. The anyRequest().authenticated() at the end is a catch-all that ensures no endpoint is accidentally left unprotected.
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests(auth -> auth
// Public endpoints
.requestMatchers("/", "/public/**", "/auth/**").permitAll()
// Static resources
.requestMatchers("/css/**", "/js/**", "/images/**").permitAll()
// Role-based
.requestMatchers("/admin/**").hasRole("ADMIN")
.requestMatchers("/user/**").hasAnyRole("USER", "ADMIN")
// Authority-based (more granular)
.requestMatchers(HttpMethod.DELETE, "/api/**").hasAuthority("DELETE_PRIVILEGE")
// SpEL expressions
.requestMatchers("/api/users/{id}/**")
.access((authentication, context) -> {
Long userId = Long.parseLong(context.getVariables().get("id"));
// Custom logic
return new AuthorizationDecision(
hasAccess(authentication.get(), userId)
);
})
// Fail closed for routes that were not reviewed above
.anyRequest().denyAll()
);
return http.build();
}How do you enable method-level security?
Method-level security lets you protect individual methods rather than URLs, which is useful when authorization logic depends on method parameters or return values. Enable it with @EnableMethodSecurity, then annotate methods with security rules.
There are three annotation styles: @PreAuthorize (Spring's most powerful, uses SpEL), @Secured (Spring's simple role check), and @RolesAllowed (JSR-250 standard). @PreAuthorize is recommended for its flexibility.
@Configuration
@EnableMethodSecurity(
prePostEnabled = true, // @PreAuthorize, @PostAuthorize
securedEnabled = true, // @Secured
jsr250Enabled = true // @RolesAllowed
)
public class MethodSecurityConfig {
}@PreAuthorize - Before method execution:
@Service
public class UserService {
// Simple role check
@PreAuthorize("hasRole('ADMIN')")
public List<User> getAllUsers() {
return userRepository.findAll();
}
// Multiple roles
@PreAuthorize("hasAnyRole('ADMIN', 'MANAGER')")
public void updateUser(User user) {
userRepository.save(user);
}
// SpEL with method parameters
@PreAuthorize("#userId == principal.id or hasRole('ADMIN')")
public User getUser(Long userId) {
return userRepository.findById(userId).orElseThrow();
}
// Complex expressions
@PreAuthorize("hasRole('ADMIN') and #user.department == principal.department")
public void promoteUser(User user) {
// ...
}
// Custom method
@PreAuthorize("@securityService.canAccessResource(#resourceId)")
public Resource getResource(Long resourceId) {
return resourceRepository.findById(resourceId).orElseThrow();
}
}@PostAuthorize - After method execution:
// Check result after method executes
@PostAuthorize("returnObject.owner == principal.username or hasRole('ADMIN')")
public Document getDocument(Long id) {
return documentRepository.findById(id).orElseThrow();
}@Secured and @RolesAllowed:
// Spring's @Secured
@Secured("ROLE_ADMIN")
public void adminOnly() { }
@Secured({"ROLE_ADMIN", "ROLE_MANAGER"})
public void adminOrManager() { }
// JSR-250 @RolesAllowed
@RolesAllowed("ADMIN")
public void adminOnly() { }What is the difference between role and authority?
This distinction confuses many developers. In Spring Security, roles are just authorities with a special ROLE_ prefix. When you use hasRole("ADMIN"), Spring Security actually checks for the authority "ROLE_ADMIN". This is purely a naming convention—roles represent high-level user categories, while authorities can represent any permission.
When creating authorities, add the ROLE_ prefix for roles and use descriptive names for fine-grained permissions. In configuration, hasRole() adds the prefix automatically, while hasAuthority() requires the exact authority name.
// Roles are authorities with ROLE_ prefix
// hasRole("ADMIN") checks for ROLE_ADMIN authority
// When creating authorities:
new SimpleGrantedAuthority("ROLE_ADMIN") // For roles
new SimpleGrantedAuthority("READ_PRIVILEGE") // For permissions
// In configuration:
.hasRole("ADMIN") // Checks ROLE_ADMIN
.hasAuthority("ROLE_ADMIN") // Checks ROLE_ADMIN (explicit)
.hasAuthority("READ_PRIVILEGE") // Checks exact authorityJWT Authentication Questions
JWT is one possible token format. Many systems correctly use opaque access tokens and introspection instead, and a browser session can be safer and simpler for a same-origin web application.
What is the structure of a JWT token?
A signed JWT (JWS compact serialization) has Base64url-encoded header and payload segments plus a signature segment. Base64url is not encryption: anyone holding the token can normally read its claims. The signature lets a verifier detect modification only after it validates with a trusted key and an allowed algorithm. Encrypted JWTs use JWE and have a different five-part structure.
Validate the token type/use, algorithm, signature, issuer, audience, expiry, not-before, and any application-specific constraints. A JWT can be locally verifiable, but logout, revocation, key rotation, replay detection, and current account state may still require server-side state or short lifetimes.
Header.Payload.Signature
eyJhbGciOiJSUzI1NiIsInR5cCI6ImF0K2p3dCJ9. // protected header
eyJpc3MiOiJodHRwczovL2lkcC5leGFtcGxlIiwic3ViIjoiMTIzIn0. // claims
signature-bytes-as-base64url
How do you create a JWT utility class?
Do not start with a generic utility that both issues and validates tokens. First decide whether the application is an OAuth client, resource server, or authorization server. A resource server should normally consume a framework-managed JwtDecoder. A service that legitimately issues tokens should use Spring Authorization Server or another reviewed authorization server, with managed asymmetric keys, rotation, metadata, client policy, consent, scopes, and audited lifecycle.
For a narrowly scoped non-OAuth signed object, use Spring Security's JwtEncoder/JwtDecoder or another maintained JOSE implementation and centralize policy. Separate signing from verification, pin algorithms, use purpose-specific keys, protect private material outside source control, and test key rotation and malformed inputs. Claim extraction must happen only after successful validation.
How do you implement a JWT authentication filter?
For OAuth 2.0 Bearer tokens, do not write one. Resource Server's BearerTokenAuthenticationFilter extracts the token, delegates to the configured authentication manager, establishes the security context, and returns standards-based WWW-Authenticate errors. JwtDecoder validates JWT access tokens; opaque tokens use introspection.
Write a custom filter only for a genuinely custom credential mechanism, with a documented threat model, exact placement, bounded parsing, consistent failures, and tests for context clearing. Do not silently treat a malformed credential as anonymous on an endpoint that requires authentication.
How do you configure Spring Security for JWT?
Configure the application as an OAuth 2.0 Resource Server, trust an issuer, validate the intended audience, and map only documented claims to authorities. Spring Boot 4's focused starter is spring-boot-starter-security-oauth2-resource-server.
STATELESS prevents Spring Security from using the HTTP session for the security context; it does not make every dependency stateless. Disabling CSRF is appropriate for an API that accepts Bearer tokens only from an explicit Authorization header. If a browser automatically attaches the credential in a cookie or via HTTP Basic, keep CSRF protection.
@Configuration
@EnableWebSecurity
public class JwtSecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http
// This API uses Authorization: Bearer, never cookie authentication.
.csrf(csrf -> csrf.disable())
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(auth -> auth
.requestMatchers("/public/**").permitAll()
.requestMatchers("/api/admin/**").hasRole("ADMIN")
.anyRequest().authenticated())
.oauth2ResourceServer(oauth2 -> oauth2
.jwt(Customizer.withDefaults()))
.build();
}
}spring:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: https://idp.example.com
audiences: https://orders-api.example.comHow do you implement JWT login and refresh endpoints?
Login and token refresh belong to an OAuth 2.0/OpenID Connect authorization server, not a resource-server controller that turns any still-valid access token into a new one. An access token is not a refresh token. Use Authorization Code with PKCE for user-facing clients and the authorization server's token endpoint.
If the authorization server issues refresh tokens, bind them to the client, restrict their scope and resources, protect them as high-value credentials, and define expiry and revocation. RFC 9700 requires public clients to use sender-constrained refresh tokens or refresh-token rotation with reuse detection. Rotate or revoke on relevant security events and never expose tokens in URLs or logs.
OAuth2 and OpenID Connect Questions
Modern authentication for APIs and single sign-on.
What are the OAuth2 roles?
OAuth2 defines four roles that interact during the authorization process. Understanding these roles helps you architect secure integrations and debug authorization flows. The Resource Owner is typically the end user who owns the data. The Client is your application requesting access. The Authorization Server (like Keycloak, Auth0, or Okta) handles authentication and issues tokens. The Resource Server is your API that validates tokens and serves protected data.
In many setups, your Spring Boot application acts as the Resource Server, validating tokens issued by an external Authorization Server.
| Role | Description |
|---|---|
| Resource Owner | The user who owns the data |
| Client | Application requesting access |
| Authorization Server | Issues tokens (Keycloak, Auth0, Okta) |
| Resource Server | API that validates tokens |
How do you configure a Spring Boot OAuth2 resource server?
With Spring Boot 4, add spring-boot-starter-security-oauth2-resource-server. Configure the trusted issuer and expected audience. Boot discovers authorization-server metadata and keys, while Spring Security validates the signature and standard claims. If you configure only jwk-set-uri, add an issuer validator explicitly; key retrieval alone does not establish who issued the token.
By default, scopes map to SCOPE_ authorities. If the issuer contract uses a custom claim, validate its type and semantics before mapping it; do not grant application roles from an undocumented claim merely because the token is signed.
# application.yml
spring:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: https://auth.example.com/realms/myrealm
audiences: https://orders-api.example.com
# Or specify JWK Set URI directly
# jwk-set-uri: https://auth.example.com/.well-known/jwks.json@Configuration
@EnableWebSecurity
public class OAuth2ResourceServerConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/public/**").permitAll()
.anyRequest().authenticated()
)
.oauth2ResourceServer(oauth2 -> oauth2
.jwt(jwt -> jwt
.jwtAuthenticationConverter(jwtAuthenticationConverter())
)
);
return http.build();
}
@Bean
public JwtAuthenticationConverter jwtAuthenticationConverter() {
JwtGrantedAuthoritiesConverter authoritiesConverter = new JwtGrantedAuthoritiesConverter();
authoritiesConverter.setAuthoritiesClaimName("roles");
authoritiesConverter.setAuthorityPrefix("ROLE_");
JwtAuthenticationConverter converter = new JwtAuthenticationConverter();
converter.setJwtGrantedAuthoritiesConverter(authoritiesConverter);
return converter;
}
}How do you implement social login with Google or GitHub?
Spring Security's OAuth 2.0 Login client can integrate with OAuth 2.0 providers and OpenID Connect providers. The openid scope selects the OIDC path and produces an OidcUser; without it, Spring uses the OAuth-specific user-service path. Register exact redirect URIs with the provider, keep client secrets outside source control, and retain the framework's state, nonce, and authorization-code protections.
Configure multiple providers in application.yml, each with their client ID, secret, and requested scopes.
# application.yml
spring:
security:
oauth2:
client:
registration:
google:
client-id: ${GOOGLE_CLIENT_ID}
client-secret: ${GOOGLE_CLIENT_SECRET}
scope: openid, profile, email
github:
client-id: ${GITHUB_CLIENT_ID}
client-secret: ${GITHUB_CLIENT_SECRET}
scope: read:user, user:email@Configuration
@EnableWebSecurity
public class OAuth2LoginConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/", "/login/**", "/error").permitAll()
.anyRequest().authenticated()
)
.oauth2Login(oauth2 -> oauth2
.loginPage("/login")
.defaultSuccessUrl("/dashboard")
.userInfoEndpoint(userInfo -> userInfo
.userService(customOAuth2UserService())
.oidcUserService(customOidcUserService())
)
);
return http.build();
}
@Bean
public OAuth2UserService<OAuth2UserRequest, OAuth2User> customOAuth2UserService() {
return new CustomOAuth2UserService();
}
@Bean
public OAuth2UserService<OidcUserRequest, OidcUser> customOidcUserService() {
return new CustomOidcUserService();
}
}How do you customize OAuth2 user loading?
Use a delegated DefaultOAuth2UserService for OAuth providers and OidcUserService for OpenID Connect providers. Link an external identity by the stable pair (issuer or provider registration, subject), not by display name. Email can be missing, mutable, or unverified; do not merge accounts solely by matching email without an explicit verified-linking policy.
The custom service should delegate protocol validation and UserInfo retrieval to Spring, then map the validated external identity to a local account in a transaction. Apply tenant and allow/deny policy before returning a principal. Avoid retaining every provider attribute in the session or logs.
@Service
public class CustomOidcUserService
implements OAuth2UserService<OidcUserRequest, OidcUser> {
private final OidcUserService delegate = new OidcUserService();
private final ExternalIdentityService identities;
@Override
public OidcUser loadUser(OidcUserRequest request) {
OidcUser external = delegate.loadUser(request);
String issuer = external.getIssuer().toString();
String subject = external.getSubject();
LocalUser local = identities.resolveOrProvision(issuer, subject, external);
return new ApplicationOidcUser(local, external);
}
}Security Configuration Questions
Common configurations that appear in most Spring Security applications.
How do you configure CORS in Spring Security?
For a browser client on another origin, process CORS before authentication because a preflight request normally has no credentials. Spring Security can reuse Spring MVC CORS configuration or a CorsConfigurationSource. CORS is a browser response-sharing policy, not authentication and not protection against non-browser clients.
Specify origins, methods, and headers from actual clients. Enable credentialed CORS only when required and never combine it with an unrestricted origin. Keep development origins out of the production profile.
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.cors(cors -> cors.configurationSource(corsConfigurationSource()))
// ... other config
;
return http.build();
}
@Bean
public 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", "X-CSRF-TOKEN"));
configuration.setAllowCredentials(true);
configuration.setMaxAge(3600L);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/api/**", configuration);
return source;
}How do you configure CSRF protection?
Spring Security enables CSRF protection by default for unsafe methods and stores the expected token in the HTTP session by default. A legitimate form or JavaScript client returns the token in a request parameter or header that a cross-site attacker cannot simply cause the browser to reproduce.
"Stateless" does not imply CSRF-safe: browsers automatically attach cookies and HTTP Basic credentials. You may disable CSRF for an API that accepts only an explicitly supplied Bearer token in the Authorization header. Use CookieCsrfTokenRepository.withHttpOnlyFalse() only when JavaScript genuinely must read the token; XSS can read that cookie too. Ignored webhook endpoints need independent signature, timestamp, and replay validation.
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
// For traditional web apps - CSRF enabled by default
.csrf(csrf -> csrf
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
// Only if another filter verifies this webhook's signature,
// timestamp, and replay identifier.
.ignoringRequestMatchers("/api/webhooks/payment")
)
// For stateless APIs - disable CSRF
// .csrf(csrf -> csrf.disable())
;
return http.build();
}How do you configure security headers?
Security headers are defense in depth. Spring Security supplies useful defaults, but Content Security Policy, framing policy, referrer policy, permissions policy, and HSTS must match how the application is served and embedded.
A strict Content Security Policy can limit the impact of some injection flaws; it does not replace contextual output encoding, safe DOM APIs, and sanitization. HSTS is emitted only on HTTPS responses and should be enabled after every covered host is ready for HTTPS. Prefer CSP frame-ancestors when you need more precise framing control than X-Frame-Options.
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.headers(headers -> headers
.contentSecurityPolicy(csp -> csp
.policyDirectives("default-src 'self'; script-src 'self'")
)
.frameOptions(frame -> frame.deny())
.xssProtection(xss -> xss.disable()) // Modern browsers have built-in protection
.contentTypeOptions(Customizer.withDefaults()) // X-Content-Type-Options: nosniff
.httpStrictTransportSecurity(hsts -> hsts
.includeSubDomains(true)
.maxAgeInSeconds(31536000)
)
);
return http.build();
}How do you configure session management?
Session management controls how Spring Security handles HTTP sessions. Options include when to create sessions, how many concurrent sessions a user can have, and how to protect against session fixation attacks.
For stateless APIs, set the policy to STATELESS. For traditional web applications, configure concurrent session limits and fixation protection.
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.sessionManagement(session -> {
session.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED);
session.sessionFixation(fixation -> fixation.migrateSession());
session.invalidSessionUrl("/login?invalid");
session.maximumSessions(1)
.maxSessionsPreventsLogin(false); // Expire the older session
});
return http.build();
}Security Best Practices Questions
Following security best practices is essential for production applications.
What are the best practices for password storage?
Use a salted adaptive one-way password function and tune its CPU/memory cost on production-class hardware against both attack resistance and authentication capacity. Fast general-purpose hashes such as MD5, SHA-1, and plain SHA-256 are not password-storage functions. Protect the database, rate-limit verification, and monitor resource exhaustion as well as credential stuffing.
DelegatingPasswordEncoder identifies stored formats and can report that an encoding needs upgrade. Actual re-encoding on successful authentication requires an update path such as UserDetailsPasswordService; keep legacy verification only for the migration window.
@Bean
public PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}How do you implement rate limiting?
Rate limiting can reduce credential stuffing, abuse, and accidental overload, but it is not complete denial-of-service protection. Define separate policies for login identifiers, accounts, trusted client IPs, authenticated principals, expensive operations, and global capacity. Avoid locking a victim's account solely because an attacker generated failures for that username.
Enforce coarse limits at an edge you control and application-specific limits near the operation. request.getRemoteAddr() is only a trustworthy client signal when the proxy chain is trusted and forwarded headers are normalized. An in-memory counter is per instance; multi-replica enforcement needs a shared/edge design with explicit failure behavior, atomicity, bounded key cardinality, 429, and Retry-After.
@Component
@RequiredArgsConstructor
public class RateLimitingFilter extends OncePerRequestFilter {
private final RateLimiter rateLimiter;
private final TrustedClientIdentity trustedClientIdentity;
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String clientKey = trustedClientIdentity.resolve(request);
if (!rateLimiter.tryAcquire(clientKey)) {
response.setStatus(HttpStatus.TOO_MANY_REQUESTS.value());
response.setHeader(HttpHeaders.RETRY_AFTER, "60");
response.getWriter().write("Rate limit exceeded");
return;
}
filterChain.doFilter(request, response);
}
}How does Spring Security prevent common attacks?
Spring Security supplies authentication/authorization infrastructure, CSRF support, session-fixation defenses, and security headers. It does not automatically prevent every vulnerability in application code.
Prevent SQL injection with parameterized queries and strict handling of dynamic identifiers; JPA can still execute unsafe concatenated native queries. Prevent XSS with contextual encoding, safe templating/DOM APIs, sanitization where HTML is allowed, and a restrictive CSP as defense in depth. Configure CORS, CSRF, cookies, TLS, redirects, headers, object-level authorization, file handling, and dependency updates for the actual application. Test configuration instead of assuming defaults cover the threat model.
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
// XSS Protection - use Content-Security-Policy
.headers(headers -> headers
.contentSecurityPolicy(csp -> csp
.policyDirectives("default-src 'self'; script-src 'self'")
)
)
// SQL injection is addressed in data-access code, not this chain.
// CSRF - enabled by default for stateful apps
// Clickjacking - frame options
.headers(headers -> headers
.frameOptions(frame -> frame.deny())
)
// Secure cookies
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
);
return http.build();
}
}How do you log security events?
Spring Security publishes authentication events that can feed audit, detection, and incident-response pipelines. Define event schemas, retention, access, redaction, clock synchronization, correlation, and alert thresholds before logging every success or failure.
Never log passwords, session IDs, tokens, authorization codes, secrets, or raw provider profiles. Usernames and IP addresses may be personal data and attacker-controlled; prefer a stable internal subject identifier or protected hash where appropriate, structured outcome/reason codes, and server-generated timestamps. Avoid raw exception messages.
@Component
public class AuthenticationEventListener {
private static final Logger logger = LoggerFactory.getLogger(AuthenticationEventListener.class);
@EventListener
public void onAuthenticationSuccess(AuthenticationSuccessEvent event) {
String subjectRef = auditSubject.reference(event.getAuthentication());
logger.atInfo()
.addKeyValue("event", "authentication_success")
.addKeyValue("subjectRef", subjectRef)
.log("Authentication succeeded");
}
@EventListener
public void onAuthenticationFailure(AbstractAuthenticationFailureEvent event) {
String subjectRef = auditSubject.reference(event.getAuthentication());
logger.atWarn()
.addKeyValue("event", "authentication_failure")
.addKeyValue("subjectRef", subjectRef)
.addKeyValue("reason", failureCode(event))
.log("Authentication failed");
}
}How do you test Spring Security configurations?
Test the authorization matrix, authentication integration, browser protections, and error semantics. @WithMockUser is useful for authorization tests but bypasses real token/password decoding. Resource-server tests can use jwt() or opaqueToken() request processors; add separate integration tests for issuer, audience, algorithms, expired tokens, and actual authorization-server metadata where those boundaries matter.
For unsafe methods with CSRF enabled, test missing, valid, and invalid tokens. Distinguish 401 Unauthorized (authentication required or invalid) from 403 Forbidden (authenticated but denied), and include unlisted-route tests so fail-closed behavior cannot regress.
@SpringBootTest
@AutoConfigureMockMvc
class SecurityTest {
@Autowired
private MockMvc mockMvc;
@Test
void publicEndpoint_shouldBeAccessible() throws Exception {
mockMvc.perform(get("/public/health"))
.andExpect(status().isOk());
}
@Test
void protectedEndpoint_shouldReturn401_whenNotAuthenticated() throws Exception {
mockMvc.perform(get("/api/users"))
.andExpect(status().isUnauthorized());
}
@Test
@WithMockUser(username = "user", roles = "USER")
void protectedEndpoint_shouldBeAccessible_whenAuthenticated() throws Exception {
mockMvc.perform(get("/api/users"))
.andExpect(status().isOk());
}
@Test
@WithMockUser(username = "user", roles = "USER")
void adminEndpoint_shouldReturn403_forRegularUser() throws Exception {
mockMvc.perform(get("/api/admin/users"))
.andExpect(status().isForbidden());
}
@Test
@WithMockUser(username = "admin", roles = "ADMIN")
void adminEndpoint_shouldBeAccessible_forAdmin() throws Exception {
mockMvc.perform(get("/api/admin/users"))
.andExpect(status().isOk());
}
@Test
@WithMockUser(roles = "ADMIN")
void stateChangingRequest_shouldRequireCsrf() throws Exception {
mockMvc.perform(post("/admin/reindex"))
.andExpect(status().isForbidden());
mockMvc.perform(post("/admin/reindex").with(csrf()))
.andExpect(status().isAccepted());
}
@Test
void resourceServer_shouldMapJwtScope() throws Exception {
mockMvc.perform(get("/api/reports")
.with(jwt().authorities(
new SimpleGrantedAuthority("SCOPE_reports.read"))))
.andExpect(status().isOk());
}
}Quick Reference
What are the key Spring Security concepts?
Authentication vs Authorization:
- Authentication = Who are you?
- Authorization = What can you do?
Filter chain order:
SecurityContextHolderFilter- Exploit-protection filters enabled for the chain
- Mechanism-specific authentication filters
ExceptionTranslationFilterAuthorizationFilter
Method security annotations:
@PreAuthorize- SpEL expressions, most flexible@Secured- Simple role checks@RolesAllowed- JSR-250 standard
JWT resource-server setup:
- Add the focused Resource Server starter
- Configure trusted issuer and audience
- Map documented scopes/claims to authorities
- Choose session and CSRF policy from credential transport
OAuth2 Resource Server:
- Add
spring-boot-starter-security-oauth2-resource-server - Configure
issuer-uriandaudiences - Customize authority conversion only for a documented issuer contract
Related Articles
- Complete Java Backend Developer Interview Guide - Full Java backend interview guide
- Spring Boot Interview Guide - Spring Boot fundamentals
- Authentication & JWT Interview Guide - JWT concepts in depth
- Web Security & OWASP Interview Guide - Security fundamentals
Sources
- Spring Security servlet architecture -
FilterChainProxy,SecurityFilterChain, current filters, and ordering guidance - Spring Security authentication architecture - security context, authentication managers, and providers
- Spring Security password storage - adaptive encoders, tuning, and
DelegatingPasswordEncoder - Spring Security OAuth 2.0 Resource Server JWT - decoder, issuer, audience, and authority mapping
- Spring Security OAuth 2.0 Login - OAuth user and OIDC user-service customization
- Spring Security CSRF protection - stateful and stateless browser threat model
- Spring Security MockMvc testing - authentication, authorization, OAuth, and CSRF test support
- Spring Boot 4.1 starters - focused Security OAuth starter names
- RFC 9700: OAuth 2.0 Security Best Current Practice - redirect, audience, token, and refresh-token security
- RFC 10017: OAuth 2.0 for Browser-Based Applications - current browser-based OAuth architecture and refresh-token guidance
Frequently Asked Questions
What is the difference between authentication and authorization in Spring Security?
Authentication establishes an identity and produces an Authentication, often through an AuthenticationManager and AuthenticationProvider. Authorization decides whether that principal may perform an action, using AuthorizationManager-based request or method rules in current Spring Security. Some endpoints also authorize anonymous requests, so authorization is not simply a second step that always requires authentication.
How does the Spring Security filter chain work?
The servlet container delegates to Spring Security's FilterChainProxy, which selects the first matching SecurityFilterChain. A modern chain commonly includes SecurityContextHolderFilter, exploit-protection filters, mechanism-specific authentication filters, ExceptionTranslationFilter, and AuthorizationFilter. The exact filters depend on configuration; inspect the startup log rather than memorizing one universal order.
How do you implement JWT authentication in Spring Boot?
For OAuth 2.0 Bearer access tokens, use Spring Security Resource Server instead of a handwritten JWT filter. Configure a trusted issuer and audience so JwtDecoder validates the signature, allowed algorithm, issuer, audience, and time claims, then map only trusted claims to authorities. Statelessness alone does not remove CSRF risk; disable CSRF only when browsers do not automatically attach the credential.
What is the difference between @Secured, @PreAuthorize, and @RolesAllowed?
@Secured is Spring-specific and checks listed authorities. @RolesAllowed is the Jakarta standard role-oriented annotation. @PreAuthorize uses an expression before invocation and can reference arguments or an authorization bean. Prefer the simplest rule that expresses the policy, keep domain authorization testable, and enable only the annotation families the application actually uses.
How do you configure OAuth2 resource server in Spring Boot?
With Spring Boot 4, add spring-boot-starter-security-oauth2-resource-server. Configure issuer-uri and audiences for JWT access tokens, or introspection for opaque tokens. Define request authorization in SecurityFilterChain and explicitly map trusted scopes or claims to authorities. A raw jwk-set-uri alone does not validate issuer unless an issuer validator is also configured.
How does Spring Security protect against CSRF attacks?
Spring Security enables CSRF protection by default for unsafe HTTP methods and stores the expected token in the session by default; forms or JavaScript clients return the token separately. A stateless browser application can still be vulnerable when cookies or HTTP Basic are attached automatically. Disable CSRF only for endpoints whose credentials arrive explicitly, such as a Bearer token in an Authorization header, and protect ignored webhook routes with their own signature and replay controls.
