Spring Security: A Comprehensive Guide
Table of Contents
• Introduction to Spring Security
• Core Security Concepts
• Security Filters and the Filter Chain
• UserDetailsService and Custom Authentication
• Password Encoding with BCrypt
• Configuring Security (Java Config)
• Method-Level Security
• Web Application Security: Login and Logout
• CSRF Protection and Session Management
• Securing REST APIs (Stateless)
• JWT and Spring Security
• OAuth2 and OpenID Connect Support
Introduction to Spring Security
Spring Security is a comprehensive framework for authentication and authorization in Java applications. It
integrates seamlessly with Spring (and Spring Boot), providing ready-made filters, security contexts, and
authentication providers for securing web and service layers 1 2 . By default it secures both MVC
(servlet) and WebFlux (reactive) applications, and protects against common exploits (CSRF, session fixation,
etc.) 1 2 . In a Spring Boot app, adding the spring-boot-starter-security dependency
automatically applies sensible defaults (HTTP Basic auth on all routes, a generated login page, etc.) and
integrates with the Spring container for easy configuration.
Spring Security is feature-rich. For example, it supports form-based login, HTTP Basic, and OAuth2/OIDC
(social logins or custom providers) out of the box 2 . It also offers fine-grained access control and works
with declarative annotations or access rules. Its integration points include filters, authentication managers,
and user detail services that plug into Spring’s dependency injection model. The bottom line: Spring
Security is the de-facto standard security framework for Spring applications 1 , making it easy to add login,
logout, encryption, and authorization rules while leveraging familiar Spring concepts (beans, context,
configuration).
Core Security Concepts
Spring Security is built on a few fundamental concepts. Authentication is the process of verifying who a
user is. Authorization (or access control) decides what the authenticated user is allowed to do. Both
concepts are distinct: you first authenticate (e.g. via username/password) and then authorize (e.g. check
roles or permissions) when accessing protected resources. A Principal represents the user’s identity and is
contained in an Authentication object. The Authentication object holds the principal (often a
1
UserDetails ), credentials (usually omitted after login), and authorities (granted roles/permissions).
Spring Security stores the Authentication object in a SecurityContext, which is thread-local. You can
obtain it via:
Object principal =
SecurityContextHolder.getContext().getAuthentication().getPrincipal();
By default, after login, principal is an instance of UserDetails , which adapts your application’s user
to Spring’s security model 3 4 . Spring provides a UserDetailsService interface (see below) to load
user information and construct that UserDetails .
Spring Security also uses the SecurityContextHolder to store the SecurityContext of the current
thread or session 5 . The context holds the Authentication object for the current user 5 . For
example, SecurityContextHolder.getContext().getAuthentication() returns the current
Authentication (if any). A GrantedAuthority represents a permission or role (e.g. ROLE_ADMIN )
granted to the user 6 . During authorization, Spring Security checks these authorities against access rules
(e.g. URL or method security).
In summary, Spring Security maintains a SecurityContext per request, populated with an Authentication
(containing the Principal and Authorities) once the user logs in. This context flows through the filter chain
(see next section), and is used to make authorization decisions and record user details. Together, these core
concepts (authentication, authorization, principal, security context, granted authorities) form the basis of
how Spring Security works 7 3 .
Security Filters and the Filter Chain
Spring Security works primarily through Servlet Filters. Every HTTP request is intercepted by a chain of
filters before it reaches your application’s servlets or controllers. The servlet container constructs a
FilterChain for a request, containing all active Filter instances (including Spring Security’s) and the
target DispatcherServlet 8 . Each filter can examine or modify the request/response, or even block
further processing. For example, a security filter might check if a request has valid credentials and, if not,
redirect to a login page or return a 401 response.
Spring Boot auto-configures a Spring Security filter chain consisting of many filters (such as
SecurityContextPersistenceFilter , UsernamePasswordAuthenticationFilter , etc.). These are
invoked in a defined order to handle authentication and authorization. The high-level flow is: the security
filters retrieve the SecurityContext (if any), check if the request needs authentication, delegate to
authentication managers, set the authenticated context, and finally enforce authorization rules.
For example, one filter in the chain is UsernamePasswordAuthenticationFilter , which processes
form login submissions (typically at /login ). Other filters handle HTTP Basic auth, logout
( LogoutFilter ), CSRF tokens, and more. The SecurityFilterChain (in Java config) lets you specify which
filters apply to which URL patterns. You configure it by defining a @Bean of type SecurityFilterChain
that builds an HttpSecurity object. The framework then registers all necessary filters for you based on
2
that config. In short, every request in a Spring app flows through the Spring Security filter chain, which
applies authentication and authorization logic before reaching your controllers 8 .
UserDetailsService and Custom Authentication
The UserDetailsService interface is central to custom username/password authentication in Spring
Security. Its single method loadUserByUsername(String username) should locate the user (e.g. from
a database) and return a UserDetails object containing the username, password (usually hashed), and
granted authorities. Spring’s DaoAuthenticationProvider uses this service to fetch user credentials
during login 9 . For example, you might write:
@Service
public class MyUserDetailsService implements UserDetailsService {
@Autowired
private UserRepository userRepo; // JPA repository for your User entity
@Override
public UserDetails loadUserByUsername(String username) {
User user = userRepo.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("User not found"));
// Convert your User entity to Spring Security’s UserDetails
return org.springframework.security.core.userdetails.User
.withUsername(user.getUsername())
.password(user.getPassword()) // (already encoded)
.authorities(user.getRoles().toArray(new String[0]))
.build();
}
}
In this code, userRepo.findByUsername(...) retrieves a user record. If found, we create a
UserDetails (in this case using Spring Security’s built-in User.withUsername()... ) with the stored
password and roles. When authentication runs, Spring Security will take the credentials supplied by the user
(e.g. from a login form), hash them and compare to user.getPassword() . If they match, authentication
succeeds and the returned UserDetails (the principal) is stored in the security context 3 9 .
Spring Security provides default implementations (in-memory, JDBC, etc.), but a custom
UserDetailsService lets you integrate any user store. To use it, simply expose it as a @Bean (or
annotate with @Service ); Spring Boot will pick it up. For example, the documentation shows registering a
custom service:
@Bean
public UserDetailsService myUserDetailsService() {
return new MyUserDetailsService();
}
3
Once registered, this service is automatically used by the authentication provider. The
UserDetailsService acts as the bridge between your application’s user database and Spring Security’s
Authentication system 9 3 .
Password Encoding with BCrypt
Secure password storage is essential. Spring Security’s PasswordEncoder interface provides one-way
hashing for passwords. The BCrypt algorithm is the default and recommended encoder. For example, you
can create a BCryptPasswordEncoder and use it to hash new passwords:
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
String raw = "mySecretPassword";
String hashed = encoder.encode(raw);
// Store 'hashed' in the database
assertTrue(encoder.matches(raw, hashed)); // verifies password
The BCryptPasswordEncoder uses a computationally-intensive (configurable strength) hashing
algorithm. By default it uses 10 rounds, but you can increase this for more security (it will just be slower)
10 . For instance, new BCryptPasswordEncoder(12) uses strength 12. When a user logs in, Spring
Security will automatically use the same PasswordEncoder (that you configure) to compare the
submitted password to the stored hash.
Since Spring Security 5, password hashes in storage must be prefixed with an id to indicate the encoding
(e.g. {bcrypt} ), or you define a default encoder. The common practice is to define a bean:
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
With this, when creating new users you should call passwordEncoder().encode(rawPassword) before
saving. Spring will then call passwordEncoder().matches(input, storedHash) during login. Using
BCrypt means even if your password database leaks, attackers cannot easily recover raw passwords,
because BCrypt is one-way and slow 10 . (Spring Security also includes other encoders like Argon2, PBKDF2,
but BCrypt is simplest and widely used.)
Configuring Security (Java Config)
Modern Spring Security is configured using Java-based configuration. Instead of the older
WebSecurityConfigurerAdapter , you define one or more SecurityFilterChain beans. For
example, a basic configuration enabling HTTP Basic and requiring authentication for all requests looks like:
4
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authz -> authz
.anyRequest().authenticated()
)
.httpBasic(Customizer.withDefaults());
return http.build();
}
}
This replaces the old configure(HttpSecurity) method. In this example, all requests require
authentication ( anyRequest().authenticated() ), and HTTP Basic is enabled. You could instead call
http.formLogin() for form-based login. The lambda DSL ( authorizeHttpRequests ) lets you specify
URL patterns. For instance, to permit public access to / and /home but secure other paths:
http
.authorizeHttpRequests(authz -> authz
.requestMatchers("/", "/home").permitAll()
.anyRequest().authenticated()
)
.formLogin(form -> form.loginPage("/login").permitAll())
.logout(logout -> logout.permitAll());
Here, we allow / and /home without login, require auth for everything else, and configure a custom
login page at /login 11 . The @EnableWebSecurity annotation (or Spring Boot auto-config) ensures
Spring Security is active.
If you need multiple security chains (for different URL spaces), you can define multiple
SecurityFilterChain beans with @Order and .securityMatcher(...) . Each chain defines its
own rules. In each bean, calling http.build() finalizes that chain. Overall, the Java config style is
component-based: you declare filters and rules using the HttpSecurity DSL, and Spring assembles the
filter chain automatically 12 13 .
Method-Level Security
Spring Security can enforce access control at the method level using annotations. This is especially useful
for service-layer security (beyond HTTP URLs). To use it, first enable method security by adding
@EnableMethodSecurity to a configuration class (Spring Security 6+, previously
@EnableGlobalMethodSecurity ) 14 . This activates Spring’s method security support.
5
You can then annotate your beans’ methods. Common annotations are:
• @Secured("ROLE_ADMIN") : only allows users with the given role. (Legacy, requires
securedEnabled = true in config if still supported.)
• @RolesAllowed("ADMIN") : JSR-250 style (requires jsr250Enabled = true ).
• @PreAuthorize("hasRole('ADMIN')") : SpEL-based, very flexible. It checks the expression
before the method invocation.
• @PostAuthorize("returnObject.owner == authentication.name") : checks after method
execution using the return value.
• @PreFilter and @PostFilter : to filter collection or array arguments/results based on
expressions.
For example:
@Service
public class AccountService {
@PreAuthorize("hasRole('USER')")
public void viewAccount(Long accountId) {
// ...
}
@PreAuthorize("hasRole('ADMIN')")
public void createAccount(...) {
// only ADMIN can execute
}
}
In the above, only authenticated users with role USER can call viewAccount , and only ADMIN can call
createAccount . SpEL expressions in @PreAuthorize (e.g. authentication.name or principal )
allow complex checks, like comparing a method argument to the current user ID. Internally, Spring Security
creates proxies or AOP interceptors around these methods to enforce the rules.
Enabling: In Spring Boot or Java config, annotate a config class with @EnableMethodSecurity . By
default this activates @PreAuthorize , @PostAuthorize , @PreFilter , and @PostFilter support
14 . You can customize it to enable @Secured or JSR-250 as well. Once enabled, annotate your
@Service or @Component methods as needed. If a user without the required authority invokes a
secured method, an AccessDeniedException is thrown. Method security is a powerful way to ensure
even internal service APIs are protected, not just web endpoints.
Web Application Security: Login and Logout
Spring Security easily secures web (MVC) applications. By default, Spring Boot provides a basic login form at
/login and secures all pages. You can customize this with the DSL. For example, to configure form-based
login and logout:
6
http
.authorizeHttpRequests(authz -> authz
.requestMatchers("/public/**").permitAll()
.anyRequest().authenticated()
)
.formLogin(form -> form
.loginPage("/login")
.defaultSuccessUrl("/dashboard", true)
.permitAll()
)
.logout(logout -> logout
.logoutUrl("/logout")
.logoutSuccessUrl("/login?logout")
.permitAll()
);
This configuration permits access to /public/** without login, requires auth elsewhere, and sets up a
custom login page at /login . Upon logout (POST to /logout ), the user is sent to /login?logout .
Spring Security automatically creates the necessary endpoints: by default, a GET to /login returns a
default login form (or your custom one), and POST /login processes credentials. Similarly, Spring adds
GET/POST /logout which invalidates the session and clears the security context 15 .
Default behavior: If you include Spring Security (or use @EnableWebSecurity ), it auto-configures a login
page and logout handler. A POST to /logout will do the following: invalidate the HTTP session, clear the
SecurityContextHolder , and clear any remember-me tokens 16 . Note that if CSRF protection is
enabled (which it is by default), you must include the CSRF token in the logout request (this is why Spring’s
default login form includes a hidden CSRF field).
Handling login errors/success: By default, a failed login redirects to /login?error and a successful
logout to /login?logout 17 . You can customize these URLs (with failureUrl() and
logoutSuccessUrl() ) and display messages accordingly. In Thymeleaf or JSP, you can check $
{param.error} or ${param.logout} to show feedback 17 .
Example of config and view: In the Spring guide, a SecurityFilterChain bean is defined that permits
“/” and “/home”, then calls formLogin().loginPage("/login") and logout().permitAll() 11 .
The corresponding login.html form posts to /login . On submit, Spring Security’s filter intercepts /
login and attempts authentication. After logging out, Spring invalidates the session and redirects (usually
back to /login?logout ) 17 16 .
CSRF Protection and Session Management
Spring Security protects web apps against CSRF (Cross-Site Request Forgery) by default. Any “unsafe”
HTTP method (POST, PUT, DELETE, etc.) requires a CSRF token. If a form is rendered via Spring’s <form:>
tag or Thymeleaf, the CSRF token is automatically inserted. For AJAX/REST calls, the standard pattern is to
send the token in a header (the default header name is X-CSRF-TOKEN ). The documentation notes:
7
“Spring Security protects against CSRF attacks by default for unsafe HTTP methods” 18 . In code, CSRF is enabled
by default (no need to explicitly call .csrf() ), but you can disable it with http.csrf().disable() if
your API will be completely stateless (since CSRF is primarily a threat when using cookies for auth).
If you disable CSRF (common for stateless REST APIs), keep in mind that logout may behave differently: by
default, GET /logout shows a confirmation page only if CSRF is enabled 19 . With CSRF disabled, a GET to
/logout will immediately log out the user without confirmation.
Session management: By default, Spring Security creates an HTTP session for authenticated users and
stores the SecurityContext there. You can configure session behavior via
http.sessionManagement() . For example, to prevent creating any session (useful for stateless APIs),
do:
http
.sessionManagement(sess -> sess
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
);
Using SessionCreationPolicy.STATELESS tells Spring Security not to create or use an HTTP session
for storing security context 20 . Internally, this sets the context repository to a
NullSecurityContextRepository , so nothing is kept in the session 20 . In a stateful web app, you
might not change this (default is IF_REQUIRED ), but for an API using JWTs or Basic auth, stateless is usual.
Spring Security also handles session fixation protection by default: it changes the session ID when a user
logs in to prevent an existing session ID from being used by an attacker. You can configure this via
sessionManagement().sessionFixation() (e.g. .newSession() or .changeSessionId() ).
Disabling session fixation protection ( .sessionFixation().none() ) is not recommended, as it leaves the
app vulnerable 21 . Finally, Spring can also limit concurrent sessions (max sessions per user) if configured,
but that is more advanced use.
Securing REST APIs (Stateless)
When securing RESTful microservices, you typically want a stateless approach: no HTTP sessions, and
authentication on each request. Common methods include HTTP Basic auth, or token-based schemes
(OAuth2/JWT, API keys, etc.). In Spring Security, you can configure an API like:
http
.csrf(csrf -> csrf.disable()) // often disable CSRF for APIs
.sessionManagement(sess -> sess
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
)
.authorizeHttpRequests(authz -> authz
.requestMatchers("/api/public/**").permitAll()
.anyRequest().authenticated()
8
)
.httpBasic(); // or .oauth2ResourceServer(...), or a custom JWT filter
By setting .sessionCreationPolicy(SessionCreationPolicy.STATELESS) , Spring Security will not
use the HTTP session. Every request must contain full credentials (Basic auth header, or a JWT token) 20 . It
is common to disable CSRF ( .csrf().disable() ) since CSRF tokens are only relevant for cookie-based
sessions 18 . For example, you might secure a JSON API so that all /api/** endpoints require a Bearer
token, and none rely on sessions.
When stateless, you may use Spring’s OAuth2 Resource Server support for JWT: the next section explains
JWT. Alternatively, you can add a custom filter at the front of the chain that reads a token from
Authorization header, validates it, and populates the SecurityContext . But Spring’s built-in
resource server configuration ( http.oauth2ResourceServer().jwt() ) simplifies this. Regardless of
method, the principle is: authenticate each request independently, do not create sessions, and typically do
not require CSRF. As the docs note, using SessionCreationPolicy.STATELESS prevents any session
from being created or used for saving requests 20 . This is the common pattern for microservices and
mobile clients.
JWT and Spring Security
JSON Web Tokens (JWT) are a popular stateless authentication mechanism for APIs. A JWT is a signed JSON
object containing user claims (e.g. username, roles, expiration). On login, the server issues a JWT to the
client, which the client then includes in the Authorization: Bearer <token> header on future
requests. The server verifies the token’s signature and reads the claims (without needing a database
lookup).
Spring Security’s OAuth2 Resource Server support provides built-in JWT validation. With Spring Boot, you
typically include the spring-security-oauth2-resource-server and spring-security-oauth2-
jose dependencies, then configure properties like:
spring:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: https://idp.example.com/issuer
Setting the issuer-uri (the OpenID Provider Issuer URI) allows Spring to fetch the OIDC discovery
document and public keys (JWKS) to validate tokens. With that, Spring auto-configures and “will
automatically configure itself to validate JWT-encoded Bearer Tokens” 22 23 . Internally it queries the
issuer’s metadata, finds the jwks_uri , retrieves signing keys, and configures a JwtDecoder that checks
the token’s signature and claims (issuer, expiration, etc.) 24 .
In Java config without Boot properties, you could write:
9
http
.authorizeHttpRequests(authz -> authz.anyRequest().authenticated())
.oauth2ResourceServer(oauth2 -> oauth2
.jwt(Customizer.withDefaults())
);
This enables the JWT Bearer token support. Spring Security then looks for an Authorization:
Bearer ... header on each request. If present, it attempts to decode and validate the JWT (using
configured keys or issuer). Upon successful validation, it creates an Authentication (typically a
JwtAuthenticationToken or JwtAuthenticationProvider ) whose principal is a Jwt or
OidcUser containing the token’s claims, including authorities from a claim like scope or roles .
If not using OAuth2 resource server support, one could manually implement JWT validation by writing a
filter: extract the header, decode/verify with a library (e.g. Nimbus or JJWT), then build a Spring
UsernamePasswordAuthenticationToken or JwtAuthenticationToken and set it in the context.
But the Spring-provided way is easier and recommended for most cases.
In summary, JWT integration in Spring Security means using Spring’s OAuth2 Resource Server support with
JWT. By declaring it as a resource server and pointing to an issuer or signing key, Spring will validate
incoming tokens automatically 22 24 . This turns your API into a stateless service that trusts tokens issued
by your Auth server.
OAuth2 and OpenID Connect Support
Spring Security has robust support for OAuth 2.0 and OpenID Connect (OIDC). This includes acting as an
OAuth2 client (e.g. social login or third-party login) and as an OAuth2 resource server.
• OAuth2 Login (Client): If you want users to log in via an external provider (like Google, GitHub, etc.),
you use Spring Security’s OAuth2 Client support. In code, you simply enable
http.oauth2Login() . Spring Boot makes this very easy by letting you configure clients in
application.properties , such as
spring.security.oauth2.client.registration.google.client-id=... and so on. At
runtime, Spring Security will handle the OAuth2 Authorization Code flow: redirecting to the provider,
handling the callback, exchanging the code for a token, and then loading the user’s details (name,
email) from the userinfo endpoint. For example:
http
.authorizeHttpRequests(authz -> authz
.anyRequest().authenticated()
)
.oauth2Login(); // enables OAuth2/OIDC login
This configuration (in a WebSecurityConfigurerAdapter in older versions or a
SecurityFilterChain bean now) protects all endpoints and initiates OAuth2 login. The guide
10
example shows .oauth2Login() after setting anyRequest.authenticated 25 . After successful
login, you get an OidcUser (an Authentication ) in your security context with details like
getName() , getEmail() , and access to the ID token and claims.
• OpenID Connect: OIDC is an identity layer on top of OAuth2. Spring Security supports it out of the
box via the same oauth2Login() mechanism when you configure an OpenID provider. For
instance, setting client.oidc: client-id and issuer-uri causes Spring to treat the
provider as OIDC. The principal object becomes OidcUser , and you can access claims like
principal.getClaim("email") . Spring’s docs note that legacy OpenID (1.0/2.0) is deprecated
and developers should use OpenID Connect through Spring Security’s OAuth2 support 26 . In
practice, using .oauth2Login() with an OIDC issuer URI lets Spring fetch the provider’s OIDC
metadata (including the authorization endpoint, token endpoint, and jwks keys) and handle login
seamlessly.
• OAuth2 Resource Server: As discussed in the JWT section, Spring can also act as a resource server
validating access tokens (opaque or JWT) via http.oauth2ResourceServer() . This is often used
in a microservice that sits behind an authorization server.
In summary, Spring Security abstracts away much of the boilerplate for OAuth2/OIDC. You configure one of
its modules ( oauth2-client or oauth2-resource-server ), set up client or issuer properties, and use
http.oauth2Login() or http.oauth2ResourceServer() in your security config. Spring then
handles the protocol details. As one source puts it, Spring encourages migrating to OpenID Connect
(“supported by spring-security-oauth2”) rather than older OpenID, reflecting its modern built-in support 26 .
For practical use, once configured, your app can offer social login, or protect APIs with bearer tokens, with
just a few lines of configuration.
References: The official docs and guides show examples of oauth2Login() and OIDC integration 25
26 , illustrating how Spring Security has first-class support for these modern authentication flows.
11
1 Spring Security :: Spring Security
https://docs.spring.io/spring-security/reference/index.html
2 Introduction to Spring Security and its Features | GeeksforGeeks
https://www.geeksforgeeks.org/introduction-to-spring-security-and-its-features/
3 4 9. Technical Overview
https://docs.spring.io/spring-security/site/docs/5.0.x/reference/html/technical-overview.html
5 6 7 Servlet Authentication Architecture :: Spring Security
https://docs.spring.io/spring-security/reference/servlet/authentication/architecture.html
8 Architecture :: Spring Security
https://docs.spring.io/spring-security/reference/servlet/architecture.html
9 UserDetailsService :: Spring Security
https://docs.spring.io/spring-security/reference/servlet/authentication/passwords/user-details-service.html
10 Password Storage :: Spring Security
https://docs.spring.io/spring-security/reference/features/authentication/password-storage.html
11 17 Getting Started | Securing a Web Application
https://spring.io/guides/gs/securing-web/
12 Spring Security without the WebSecurityConfigurerAdapter
https://spring.io/blog/2022/02/21/spring-security-without-the-websecurityconfigureradapter/
13 Java Configuration :: Spring Security
https://docs.spring.io/spring-security/reference/servlet/configuration/java.html
14 Method Security :: Spring Security
https://docs.spring.io/spring-security/reference/servlet/authorization/method-security.html
15 16 19 Handling Logouts :: Spring Security
https://docs.spring.io/spring-security/reference/servlet/authentication/logout.html
18 Cross Site Request Forgery (CSRF) :: Spring Security
https://docs.spring.io/spring-security/reference/servlet/exploits/csrf.html
20 21 Authentication Persistence and Session Management :: Spring Security
https://docs.spring.io/spring-security/reference/servlet/authentication/session-management.html
22 23 24 OAuth 2.0 Resource Server JWT :: Spring Security
https://docs.spring.io/spring-security/reference/servlet/oauth2/resource-server/jwt.html
25 Getting Started | Spring Boot and OAuth2
https://spring.io/guides/tutorials/spring-boot-oauth2/
26 OpenID Support :: Spring Security
https://docs.spring.io/spring-security/reference/5.8/servlet/authentication/openid.html
12