08 - Spring Security Notes
A beginner-to-advanced guide to Spring Security for Spring Professional Certification candidates. Covers authentication, authorization, the security filter chain, JWT, OAuth2, method security, CSRF, CORS, interview questions, and a quick cheat sheet for Spring Security 6 and Spring Boot 3.
Table of Contents
- What Is Spring Security?
- Authentication
- Authorization
- Security Filter Chain
- JWT
- OAuth2
- Method Security
- CSRF
- CORS
- Common Spring Boot Configuration
- Common Mistakes
- Certification Traps
- Interview Questions
- Cheat Sheet
1. What Is Spring Security?
Spring Security is the Spring framework module responsible for authentication, authorization, protection against common web attacks, and integration with standards such as OAuth2, OpenID Connect, LDAP, and SAML.
It protects applications at multiple levels:
| Level | Example |
|---|---|
| Web request | Protect /admin/** URLs |
| Method invocation | Protect @PreAuthorize("hasRole('ADMIN')") methods |
| Object access | Check whether the current user owns a resource |
| Protocol integration | OAuth2 login, JWT resource server, OIDC |
| Web attack defense | CSRF, security headers, session protection |
Spring Security is based on servlet filters in Spring MVC applications and web filters in Spring WebFlux applications.
2. Authentication
Authentication answers: Who is the current user?
It verifies identity using credentials such as:
- username and password
- HTTP Basic credentials
- form login credentials
- JWT bearer token
- OAuth2 or OpenID Connect login
- LDAP credentials
- client certificate
Core Authentication Types
| Type | Description |
|---|---|
Authentication | Represents the current principal, credentials, and authorities |
AuthenticationManager | Main strategy interface for authenticating requests |
AuthenticationProvider | Performs a specific authentication mechanism |
UserDetailsService | Loads user-specific data by username |
UserDetails | Represents application user information |
PasswordEncoder | Encodes and verifies passwords |
SecurityContext | Stores the current Authentication |
SecurityContextHolder | Holds the SecurityContext, usually in a thread-local |
Authentication Flow
Request enters application
|
v
Authentication filter extracts credentials
|
v
AuthenticationManager delegates to AuthenticationProvider
|
v
AuthenticationProvider validates credentials
|
v
Authenticated Authentication object is created
|
v
Authentication is stored in SecurityContext
Example: In-Memory Users
@Bean
UserDetailsService users(PasswordEncoder passwordEncoder) {
UserDetails user = User.builder()
.username("user")
.password(passwordEncoder.encode("password"))
.roles("USER")
.build();
UserDetails admin = User.builder()
.username("admin")
.password(passwordEncoder.encode("password"))
.roles("ADMIN")
.build();
return new InMemoryUserDetailsManager(user, admin);
}
@Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
Password Encoding
Never store plain-text passwords. Spring Security commonly uses BCryptPasswordEncoder.
@Bean
PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
DelegatingPasswordEncoder stores the encoding id with the password:
{bcrypt}$2a$10$...
This allows applications to support password encoding upgrades over time.
Authentication vs Principal
| Term | Meaning |
|---|---|
| Principal | The identity of the user, often username or user object |
| Credentials | Proof of identity, such as password or token |
| Authorities | Permissions or roles granted to the user |
| Authentication | Full security object containing principal, credentials, and authorities |
3. Authorization
Authorization answers: What is the current user allowed to do?
Authorization happens after authentication.
Authorities and Roles
Spring Security uses authorities internally.
new SimpleGrantedAuthority("READ_PRIVILEGE");
new SimpleGrantedAuthority("ROLE_ADMIN");
A role is a special authority with the ROLE_ prefix.
| Expression | Checks For |
|---|---|
hasAuthority("READ_PRIVILEGE") | Exact authority READ_PRIVILEGE |
hasRole("ADMIN") | Authority ROLE_ADMIN |
hasAnyRole("ADMIN", "USER") | Either ROLE_ADMIN or ROLE_USER |
authenticated() | Any authenticated user |
permitAll() | Everyone, including anonymous users |
denyAll() | Nobody |
URL-Based Authorization
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/", "/login", "/public/**").permitAll()
.requestMatchers("/admin/**").hasRole("ADMIN")
.requestMatchers("/api/**").authenticated()
.anyRequest().denyAll()
)
.formLogin(Customizer.withDefaults())
.build();
}
Rule Ordering Matters
Spring evaluates authorization rules in the order they are declared.
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/admin/**").hasRole("ADMIN")
.requestMatchers("/api/**").authenticated()
.anyRequest().denyAll()
)
Specific matchers should usually appear before broad matchers.
4. Security Filter Chain
Spring Security works through a chain of filters.
In servlet applications, requests pass through DelegatingFilterProxy, which delegates to Spring Security's FilterChainProxy.
High-Level Flow
HTTP request
|
v
Servlet container filter chain
|
v
DelegatingFilterProxy
|
v
FilterChainProxy
|
v
SecurityFilterChain
|
v
Application controller
Important Filters
| Filter | Responsibility |
|---|---|
SecurityContextHolderFilter | Loads and clears the security context |
UsernamePasswordAuthenticationFilter | Handles form login authentication |
BasicAuthenticationFilter | Handles HTTP Basic authentication |
BearerTokenAuthenticationFilter | Handles bearer token authentication |
AuthorizationFilter | Performs authorization checks |
CsrfFilter | Applies CSRF protection |
CorsFilter | Handles CORS preflight and headers |
LogoutFilter | Handles logout requests |
Defining a SecurityFilterChain
Spring Security 6 no longer recommends extending WebSecurityConfigurerAdapter.
@Configuration
@EnableWebSecurity
class SecurityConfig {
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/public/**").permitAll()
.anyRequest().authenticated()
)
.httpBasic(Customizer.withDefaults())
.build();
}
}
Multiple Filter Chains
Applications can define multiple chains with different matchers.
@Bean
@Order(1)
SecurityFilterChain apiSecurity(HttpSecurity http) throws Exception {
return http
.securityMatcher("/api/**")
.authorizeHttpRequests(auth -> auth.anyRequest().authenticated())
.oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults()))
.build();
}
@Bean
SecurityFilterChain webSecurity(HttpSecurity http) throws Exception {
return http
.authorizeHttpRequests(auth -> auth.anyRequest().permitAll())
.formLogin(Customizer.withDefaults())
.build();
}
The first matching SecurityFilterChain handles the request.
5. JWT
JWT stands for JSON Web Token. It is a compact token format commonly used for stateless API authentication.
JWT Structure
header.payload.signature
| Part | Purpose |
|---|---|
| Header | Token type and signing algorithm |
| Payload | Claims such as subject, issuer, expiry, and scopes |
| Signature | Verifies token integrity |
Common Claims
| Claim | Meaning |
|---|---|
sub | Subject, usually user id or username |
iss | Issuer |
aud | Audience |
exp | Expiration time |
iat | Issued-at time |
scope or scp | Permissions or scopes |
JWT Resource Server
Spring Security can validate JWTs for protected APIs.
@Bean
SecurityFilterChain apiSecurity(HttpSecurity http) throws Exception {
return http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/public/**").permitAll()
.requestMatchers("/api/admin/**").hasAuthority("SCOPE_admin")
.anyRequest().authenticated()
)
.oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults()))
.build();
}
Example configuration:
spring.security.oauth2.resourceserver.jwt.issuer-uri=https://issuer.example.com
or:
spring.security.oauth2.resourceserver.jwt.jwk-set-uri=https://issuer.example.com/.well-known/jwks.json
JWT Authentication Flow
Client sends Authorization: Bearer <token>
|
v
BearerTokenAuthenticationFilter extracts token
|
v
JwtDecoder validates signature, issuer, audience, and expiry
|
v
JwtAuthenticationConverter maps claims to authorities
|
v
SecurityContext stores authenticated principal
JWT Best Practices
| Practice | Reason |
|---|---|
| Use HTTPS | Prevent token interception |
| Keep access tokens short-lived | Limit damage if leaked |
| Validate issuer and audience | Prevent accepting tokens from wrong systems |
| Do not store secrets in JWT claims | JWT payload is readable even when signed |
| Prefer asymmetric signing for distributed systems | Resource servers can verify without private signing key |
| Use refresh tokens carefully | Refresh tokens require stronger storage and revocation strategy |
6. OAuth2
OAuth2 is an authorization framework. It allows a client application to access protected resources on behalf of a user or itself.
OpenID Connect builds on OAuth2 and adds authentication and identity information.
OAuth2 Roles
| Role | Description |
|---|---|
| Resource Owner | User who owns the data |
| Client | Application requesting access |
| Authorization Server | Issues tokens |
| Resource Server | Hosts protected APIs |
Common Grant Types
| Grant Type | Use Case |
|---|---|
| Authorization Code | Web apps and mobile apps |
| Authorization Code with PKCE | Public clients such as SPAs and mobile apps |
| Client Credentials | Machine-to-machine communication |
| Refresh Token | Obtain new access token without user login |
Password grant is deprecated and should generally not be used.
OAuth2 Login
Used when the application lets users sign in through an external provider.
@Bean
SecurityFilterChain webSecurity(HttpSecurity http) throws Exception {
return http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/", "/error").permitAll()
.anyRequest().authenticated()
)
.oauth2Login(Customizer.withDefaults())
.build();
}
Example client registration:
spring.security.oauth2.client.registration.github.client-id=your-client-id
spring.security.oauth2.client.registration.github.client-secret=your-client-secret
OAuth2 Resource Server
Used when the application exposes APIs protected by access tokens.
@Bean
SecurityFilterChain resourceServer(HttpSecurity http) throws Exception {
return http
.authorizeHttpRequests(auth -> auth.anyRequest().authenticated())
.oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults()))
.build();
}
OAuth2 Login vs Resource Server
| Feature | OAuth2 Login | OAuth2 Resource Server |
|---|---|---|
| Main purpose | Log users into the application | Protect APIs with access tokens |
| Input | Authorization code flow | Bearer token |
| Session | Usually uses server-side session | Usually stateless |
| Common client | Browser web app | API client |
7. Method Security
Method security protects service methods, repository methods, or controller methods using annotations.
Enable it with:
@Configuration
@EnableMethodSecurity
class MethodSecurityConfig {
}
Common Annotations
| Annotation | Description |
|---|---|
@PreAuthorize | Checks authorization before method execution |
@PostAuthorize | Checks authorization after method execution |
@PreFilter | Filters method arguments before execution |
@PostFilter | Filters return collections after execution |
@Secured | Role-based method security |
@RolesAllowed | JSR-250 role-based method security |
Examples
@Service
class AccountService {
@PreAuthorize("hasRole('ADMIN')")
List<Account> findAllAccounts() {
return List.of();
}
@PreAuthorize("#accountId == authentication.name")
Account findOwnAccount(String accountId) {
return new Account(accountId);
}
@PostAuthorize("returnObject.owner == authentication.name")
Account findAccount(String accountId) {
return new Account(accountId);
}
}
Method Security Notes
| Point | Explanation |
|---|---|
| It uses Spring AOP | Calls must usually go through a Spring proxy |
| Self-invocation can bypass checks | A method calling another method on the same object may not pass through proxy |
| Prefer service-layer checks | Business authorization belongs close to business logic |
| URL security is still needed | Method security complements web security |
8. CSRF
CSRF stands for Cross-Site Request Forgery.
It tricks an authenticated user's browser into sending unwanted state-changing requests to a trusted site.
Why CSRF Works
Browsers automatically attach cookies to requests for the cookie's domain. If an application uses cookie-based authentication, a malicious site may trigger requests that include the user's session cookie.
Spring Security Default
CSRF protection is enabled by default for browser-based applications.
It usually applies to unsafe HTTP methods:
POSTPUTPATCHDELETE
It does not normally apply to safe methods:
GETHEADOPTIONSTRACE
CSRF Token Flow
Server generates CSRF token
|
v
Client receives token
|
v
Client sends token with state-changing request
|
v
Server validates token against expected value
When CSRF Is Important
| Application Type | CSRF Needed? |
|---|---|
| Server-rendered web app using sessions and cookies | Yes |
| Form login application | Yes |
Stateless REST API using bearer tokens in Authorization header | Usually no |
| API authenticated only by cookies | Yes |
Disabling CSRF for Stateless APIs
@Bean
SecurityFilterChain apiSecurity(HttpSecurity http) throws Exception {
return http
.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(auth -> auth.anyRequest().authenticated())
.oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults()))
.build();
}
Do not disable CSRF for browser session applications without understanding the risk.
9. CORS
CORS stands for Cross-Origin Resource Sharing.
It controls whether browsers allow JavaScript running on one origin to call APIs on another origin.
Origin
An origin is the combination of:
scheme + host + port
Examples:
| URL | Origin |
|---|---|
https://example.com/app | https://example.com |
https://example.com:8443/app | https://example.com:8443 |
http://example.com/app | http://example.com |
CORS vs CSRF
| Topic | CORS | CSRF |
|---|---|---|
| Main concern | Browser cross-origin reads and calls | Forged authenticated state changes |
| Enforced by | Browser | Server |
| Protects against | Unauthorized cross-origin access from JavaScript | Malicious requests using existing credentials |
| Applies to | Cross-origin browser requests | Cookie/session-based authenticated requests |
CORS Configuration
@Bean
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration config = new CorsConfiguration();
config.setAllowedOrigins(List.of("https://app.example.com"));
config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE"));
config.setAllowedHeaders(List.of("Authorization", "Content-Type"));
config.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/api/**", config);
return source;
}
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http
.cors(Customizer.withDefaults())
.authorizeHttpRequests(auth -> auth.anyRequest().authenticated())
.build();
}
Preflight Request
For non-simple cross-origin requests, the browser sends an OPTIONS request first.
OPTIONS /api/orders
Origin: https://app.example.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: Authorization, Content-Type
The server must respond with allowed origins, methods, and headers.
10. Common Spring Boot Configuration
Basic Web Application
@Bean
SecurityFilterChain webSecurity(HttpSecurity http) throws Exception {
return http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/", "/css/**", "/js/**").permitAll()
.anyRequest().authenticated()
)
.formLogin(Customizer.withDefaults())
.logout(Customizer.withDefaults())
.build();
}
Stateless API
@Bean
SecurityFilterChain apiSecurity(HttpSecurity http) throws Exception {
return http
.csrf(csrf -> csrf.disable())
.sessionManagement(session ->
session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/public/**").permitAll()
.anyRequest().authenticated()
)
.oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults()))
.build();
}
Public Static Resources
.authorizeHttpRequests(auth -> auth
.requestMatchers("/css/**", "/js/**", "/images/**", "/webjars/**").permitAll()
.anyRequest().authenticated()
)
Prefer permitAll() for public endpoints instead of ignoring Spring Security entirely, unless the resource truly should bypass all security filters.
11. Common Mistakes
| Mistake | Why It Is a Problem |
|---|---|
| Storing plain-text passwords | Passwords are exposed if database leaks |
| Confusing roles and authorities | hasRole("ADMIN") checks ROLE_ADMIN, not ADMIN |
| Disabling CSRF everywhere | Breaks protection for browser session apps |
Allowing * origin with credentials | Invalid and unsafe CORS configuration |
| Trusting JWT claims without signature validation | Attackers can forge unsigned or invalid tokens |
| Using long-lived access tokens | Token theft has longer impact |
| Putting broad matchers before specific matchers | Specific rules may never run |
| Relying only on frontend checks | Client-side authorization can be bypassed |
| Forgetting method security proxies | Self-invocation can bypass method annotations |
12. Certification Traps
| Trap | Correct Understanding |
|---|---|
| Authentication and authorization are the same | Authentication identifies the user; authorization checks permissions |
| Roles are stored without prefix | Spring stores roles as authorities with ROLE_ prefix |
hasRole("ADMIN") means authority ADMIN | It checks authority ROLE_ADMIN |
| JWT is encrypted by default | JWT is usually signed, not encrypted |
| CORS protects APIs from all non-browser clients | CORS is enforced by browsers |
| CSRF is only a frontend concern | CSRF must be validated server-side |
WebSecurityConfigurerAdapter is current style | Spring Security 6 uses SecurityFilterChain beans |
| Method security replaces URL security | They complement each other |
| OAuth2 is only for login | OAuth2 also protects APIs and machine-to-machine calls |
13. Interview Questions
Authentication
Q1. What is authentication in Spring Security?
Authentication is the process of verifying a user's identity. Spring Security represents the result using an Authentication object stored in the SecurityContext.
Q2. What is the role of AuthenticationManager?
AuthenticationManager is the main interface that attempts to authenticate an Authentication request. It commonly delegates to one or more AuthenticationProvider implementations.
Q3. What is UserDetailsService used for?
It loads user-specific data, usually by username, during authentication.
Q4. Why should passwords be encoded?
Encoded passwords reduce the risk of credential exposure if the database is compromised. BCrypt is a common adaptive hashing algorithm.
Authorization
Q5. What is authorization?
Authorization determines whether an authenticated principal has permission to access a resource or perform an action.
Q6. What is the difference between role and authority?
An authority is a granted permission. A role is a convention-based authority prefixed with ROLE_.
Q7. What does hasRole("ADMIN") check?
It checks for the authority ROLE_ADMIN.
Security Filter Chain
Q8. What is SecurityFilterChain?
It is the set of Spring Security filters applied to matching HTTP requests.
Q9. What replaced WebSecurityConfigurerAdapter?
Spring Security 6 uses component-based configuration with one or more SecurityFilterChain beans.
Q10. Why does filter order matter?
Security filters perform different responsibilities in sequence. Authentication must happen before authorization can make decisions based on the authenticated user.
JWT
Q11. What is JWT used for?
JWT is commonly used as a stateless bearer token for API authentication and authorization.
Q12. Is JWT encrypted by default?
No. A standard JWT is usually signed, not encrypted. Its payload can be read by anyone who has the token.
Q13. How does Spring Security validate JWTs?
It uses a JwtDecoder to validate the token signature, expiration, issuer, and other configured claims.
OAuth2
Q14. What is the difference between OAuth2 and OpenID Connect?
OAuth2 is for delegated authorization. OpenID Connect adds authentication and identity information on top of OAuth2.
Q15. What is the client credentials grant used for?
It is used for machine-to-machine communication where no end user is involved.
Q16. What is PKCE?
PKCE is an extension to the authorization code flow that protects public clients from authorization code interception attacks.
Method Security
Q17. How do you enable method security?
Use @EnableMethodSecurity in a configuration class.
Q18. What does @PreAuthorize do?
It evaluates an authorization expression before the method runs.
Q19. Why can self-invocation bypass method security?
Method security is typically implemented using Spring AOP proxies. A method call inside the same object may not go through the proxy.
CSRF and CORS
Q20. What is CSRF?
CSRF is an attack where a malicious site causes a user's browser to send an unwanted authenticated request to another site.
Q21. When can CSRF often be disabled?
It is often disabled for stateless APIs that authenticate using bearer tokens in the Authorization header and do not rely on cookies.
Q22. What is CORS?
CORS is a browser security mechanism that controls whether JavaScript from one origin can access resources from another origin.
Q23. Does CORS replace authentication?
No. CORS is not an authentication or authorization mechanism.
14. Cheat Sheet
Core Concepts
| Concept | Quick Meaning |
|---|---|
| Authentication | Verifies who the user is |
| Authorization | Checks what the user can access |
| Principal | Current user's identity |
| Credentials | Secret or proof of identity |
| Authority | Permission granted to a user |
| Role | Authority prefixed with ROLE_ |
| Security context | Stores current authentication |
| Filter chain | Security filters applied to HTTP requests |
Essential Classes and Interfaces
| Type | Purpose |
|---|---|
SecurityFilterChain | Defines HTTP security configuration |
HttpSecurity | Fluent API for configuring web security |
Authentication | Represents authentication state |
AuthenticationManager | Authenticates authentication requests |
AuthenticationProvider | Implements a specific authentication strategy |
UserDetailsService | Loads users |
PasswordEncoder | Encodes and verifies passwords |
GrantedAuthority | Represents permission or role |
Common Configuration Snippets
.authorizeHttpRequests(auth -> auth
.requestMatchers("/public/**").permitAll()
.requestMatchers("/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
)
.formLogin(Customizer.withDefaults())
.httpBasic(Customizer.withDefaults())
.oauth2Login(Customizer.withDefaults())
.oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults()))
.csrf(csrf -> csrf.disable())
.sessionManagement(session ->
session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
Authorization Expressions
| Expression | Meaning |
|---|---|
permitAll() | Allow everyone |
denyAll() | Deny everyone |
authenticated() | Require authenticated user |
anonymous() | Require anonymous user |
hasRole("ADMIN") | Require ROLE_ADMIN |
hasAuthority("SCOPE_read") | Require exact authority |
hasAnyRole("ADMIN", "USER") | Require any listed role |
Security Defaults
| Default | Meaning |
|---|---|
| CSRF enabled | Protects browser session apps |
| Secure headers enabled | Adds common security headers |
| Form login generated | Boot may generate a default login form |
| Default user generated | Boot creates a user when no user config exists |
| Password logged at startup | Boot logs generated password for development |
Quick Decision Guide
| Requirement | Use |
|---|---|
| Browser login with sessions | Form login or OAuth2 login |
| API protected by bearer tokens | OAuth2 resource server with JWT |
| Machine-to-machine access | OAuth2 client credentials |
| Service-layer authorization | @EnableMethodSecurity and @PreAuthorize |
| Cookie-based authentication | Keep CSRF enabled |
| Stateless bearer-token API | Usually disable CSRF and use stateless sessions |
| Cross-origin frontend calls API | Configure CORS explicitly |
Must Remember
- Authentication happens before authorization.
hasRole("ADMIN")checks forROLE_ADMIN.- JWTs are usually signed, not encrypted.
- CORS is enforced by browsers, not by API clients such as
curl. - CSRF matters most for cookie/session-based browser applications.
- Spring Security 6 uses
SecurityFilterChainbeans instead ofWebSecurityConfigurerAdapter. - Method security depends on Spring proxies, so self-invocation can bypass checks.
- Do not rely on frontend checks for security decisions.