What Is Spring Security
Spring Security is the de-facto standard for authentication (who are you?) and authorization (what are you allowed to do?) in Spring applications. It is a separate project from Spring Framework, but Spring Boot integrates it seamlessly through a single starter dependency.
On the 2V0-72.22 exam, Spring Security is a dedicated topic area carrying approximately 6% of all questions — small in absolute terms, but historically a domain where unprepared candidates lose easy points. The exam tests core concepts: the security filter chain, basic configuration with SecurityFilterChain, authentication providers, method security, and password encoding. It does not require deep OAuth2, JWT, or SAML knowledge.
This guide walks through every Security topic on the 2V0-72.22 syllabus using Spring Security 6.x with Spring Boot 3.x and the Jakarta namespace — the current stack. Where APIs changed between Spring Security 5 and 6 (and there are several big ones), you will see explicit Old vs New callouts so older study material does not trip you up.
Exam tip: The exam blueprint groups Security as a single section, but Security concepts also bleed into other sections — actuator endpoints, method security on services, and
@WithMockUserin tests. Knowing Security well actually pays off in more than 6% of questions.
If you are still planning your study path, the complete 8-week preparation guide maps every section to a week — Security gets paired with Testing in Week 6.
Getting Started
Adding Spring Security to a Spring Boot application requires a single dependency:
Maven:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>Gradle:
implementation 'org.springframework.boot:spring-boot-starter-security'The moment this starter is on the classpath, Spring Boot's auto-configuration activates a strict security baseline:
- Every HTTP endpoint is secured — anonymous requests get redirected to a login page (or
401 Unauthorizedfor non-browser clients). - A default user named
useris created in memory with a randomly generated password printed to the console at startup, e.g.Using generated security password: 8e557245-.... - A default login form is rendered at
/loginand a logout endpoint is registered at/logout. - CSRF protection is enabled for stateful, browser-facing requests.
You can customise the default user via properties without writing any Java:
spring.security.user.name=admin
spring.security.user.password=secret
spring.security.user.roles=ADMINExam tip: The default user/password mechanism is intended for getting started only — it is not how you authenticate users in production. You will configure a real
UserDetailsService(covered below). Expect at least one exam question about the default behaviour (random password, single user, console log).
The SecurityFilterChain (Spring Security 6)
Before Spring Security 6, you configured security by extending WebSecurityConfigurerAdapter and overriding configure(HttpSecurity). In Spring Security 6 (and therefore Spring Boot 3.x), that base class is gone. Configuration now happens through a @Bean of type SecurityFilterChain using a lambda-based DSL.
A minimal Spring Security 6 configuration looks like this:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/", "/public/**").permitAll()
.requestMatchers("/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
)
.formLogin(form -> form.loginPage("/login").permitAll())
.logout(logout -> logout.permitAll());
return http.build();
}
}Every call on HttpSecurity returns the same HttpSecurity instance, so you can chain freely. The trailing http.build() produces the SecurityFilterChain bean.
Old (Spring Security 5, deprecated and removed in SS 6):
// DO NOT use — removed in Spring Security 6
@Configuration
@EnableWebSecurity
public class OldSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
.and()
.formLogin();
}
}New (Spring Security 6, current): the @Bean SecurityFilterChain shown above. Note also:
authorizeRequests()→authorizeHttpRequests(...)antMatchers(...)→requestMatchers(...).and()chaining → lambda DSL
Exam tip: If you see
WebSecurityConfigurerAdapter,antMatchers, orauthorizeRequestsin an answer for a Spring Boot 3 / Spring Security 6 question, it is almost certainly the wrong answer. The exam targets the current stack.
The most commonly chained HttpSecurity methods you need to recognise:
| Method | Purpose |
|---|---|
authorizeHttpRequests(...) | URL-level authorization rules |
formLogin(...) | Enables HTML form login |
httpBasic(...) | Enables HTTP Basic authentication |
csrf(...) | Configures or disables CSRF protection |
sessionManagement(...) | Controls session creation policy |
logout(...) | Configures the logout endpoint |
exceptionHandling(...) | Custom 401 / 403 entry points |
Authentication — Who Are You?
Authentication answers the question who is making this request? In Spring Security, authentication is a pipeline composed of small, replaceable pieces. At a high level:
HTTP request
│
▼
┌─────────────────────────┐
│ Security Filter Chain │ (a chain of servlet filters)
└─────────┬───────────────┘
│ (the relevant auth filter, e.g.
│ UsernamePasswordAuthenticationFilter
│ or BasicAuthenticationFilter, extracts
│ credentials from the request)
▼
┌─────────────────────────┐
│ AuthenticationManager │ (orchestrator — usually
│ (ProviderManager) │ a ProviderManager)
└─────────┬───────────────┘
│ delegates to one or more
▼
┌─────────────────────────┐
│ AuthenticationProvider │ (e.g. DaoAuthenticationProvider)
└─────────┬───────────────┘
│
┌──────┴──────┐
▼ ▼
UserDetails PasswordEncoder
Service (verifies the
(loads submitted
the user) password)The key abstractions:
AuthenticationManager— the entry point. The default implementation isProviderManager, which iterates over a list ofAuthenticationProviders until one succeeds.AuthenticationProvider— knows how to authenticate one kind of credential (username/password, LDAP, OAuth2 token, …).DaoAuthenticationProviderhandles username/password by combining aUserDetailsServicewith aPasswordEncoder.UserDetailsService— a lookup interface: given a username, return aUserDetails(or throwUsernameNotFoundException).UserDetails— represents the authenticated principal: username, password (encoded), authorities, account-status flags.PasswordEncoder— verifies the raw password submitted by the user against the encoded password stored inUserDetails.
On successful authentication, the provider returns a fully-populated Authentication object, which is stored in the SecurityContext (per thread, via SecurityContextHolder).
Exam tip: Memorise the chain
AuthenticationManager → AuthenticationProvider → UserDetailsService → PasswordEncoder. The exam loves to ask which component is responsible for which step. The manager orchestrates; the provider authenticates; the UserDetailsService loads; the PasswordEncoder compares.
UserDetailsService & UserDetails
For anything beyond the auto-configured default user, you provide your own UserDetailsService. The interface has a single method:
public interface UserDetailsService {
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}Two ready-made implementations cover the common cases:
1. In-memory (good for demos, tests, the exam):
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
@Bean
public InMemoryUserDetailsManager users(PasswordEncoder encoder) {
UserDetails alice = User.builder()
.username("alice")
.password(encoder.encode("password"))
.roles("USER")
.build();
UserDetails admin = User.builder()
.username("admin")
.password(encoder.encode("admin"))
.roles("USER", "ADMIN")
.build();
return new InMemoryUserDetailsManager(alice, admin);
}2. JDBC-backed:
@Bean
public JdbcUserDetailsManager users(DataSource dataSource) {
return new JdbcUserDetailsManager(dataSource);
}JdbcUserDetailsManager expects a specific schema (users and authorities tables) — its default DDL is shipped as users.ddl inside Spring Security. You can override the SQL queries via setter methods (setUsersByUsernameQuery(...), etc.).
Custom implementations (database, REST, LDAP, anywhere) — implement the interface yourself and expose a @Bean:
@Service
public class MyUserDetailsService implements UserDetailsService {
private final UserRepository repo;
public MyUserDetailsService(UserRepository repo) { this.repo = repo; }
@Override
public UserDetails loadUserByUsername(String username) {
return repo.findByUsername(username)
.map(u -> User.builder()
.username(u.getUsername())
.password(u.getEncodedPassword())
.roles(u.getRoles().toArray(String[]::new))
.build())
.orElseThrow(() -> new UsernameNotFoundException(username));
}
}When you expose a UserDetailsService bean and a PasswordEncoder bean, Spring Boot auto-wires a DaoAuthenticationProvider for you — no manual AuthenticationManager configuration is required for the basic case.
Exam tip:
User.withDefaultPasswordEncoder()exists and is convenient in samples, but it is annotated as deprecated and intended for tests/demos only. The expected production answer is always aPasswordEncoderbean (typicallyBCryptPasswordEncoder) plusencoder.encode(...). Watch for trick questions on this.
Password Encoding
Passwords must never be stored in plain text. Spring Security enforces this by requiring a PasswordEncoder bean any time you authenticate against username/password. If a DaoAuthenticationProvider is wired without an explicit encoder, Spring Security 6 uses a DelegatingPasswordEncoder by default.
DelegatingPasswordEncoder is the recommended encoder for new applications. It stores the algorithm used as a prefix:
{bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG
{noop}plain-text-password ← only for tests; never use in production
{pbkdf2}…
{scrypt}…
{argon2}…The prefix tells the encoder which algorithm verified the password. This lets you migrate hashes over time: new passwords are written with the strongest algorithm; old passwords keep verifying until each user logs in next and re-hashes.
The standard production bean:
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}To opt into the delegating encoder explicitly:
@Bean
public PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}Exam tip: Three facts the exam tests repeatedly: (1)
BCryptPasswordEncoderis the default modern choice, (2)NoOpPasswordEncoderexists but is deprecated and for tests only, (3)DelegatingPasswordEncoderreads the{id}prefix to pick which algorithm to verify with.
Authorization — What Can You Do?
Authorization rules decide whether an already authenticated request can access a resource. URL-based rules live inside authorizeHttpRequests:
http.authorizeHttpRequests(auth -> auth
.requestMatchers("/public/**", "/").permitAll()
.requestMatchers("/api/admin/**").hasRole("ADMIN")
.requestMatchers(HttpMethod.GET, "/api/items/**").hasAuthority("SCOPE_read")
.requestMatchers("/api/**").authenticated()
.anyRequest().denyAll()
);The matchers are evaluated top-to-bottom — first match wins. Always place more specific patterns before more general ones, and end the chain with anyRequest().authenticated() or anyRequest().denyAll() so nothing slips through unmatched.
Available rule terminators:
| Rule | Meaning |
|---|---|
permitAll() | Anyone, including anonymous users, may access |
denyAll() | No one may access |
authenticated() | Any logged-in user |
anonymous() | Only anonymous (not logged-in) users |
hasRole("ADMIN") | Logged-in user has authority ROLE_ADMIN |
hasAnyRole("ADMIN","OPS") | Has any of the listed roles |
hasAuthority("SCOPE_read") | Logged-in user has the exact authority string |
hasAnyAuthority(...) | Has any of the listed authorities |
access(authorizationManager) | Custom programmatic decision |
hasRole vs hasAuthority — the ROLE_ prefix trap
This is one of the most-asked Spring Security exam questions. The two methods look interchangeable but treat the input differently:
| Call | What is checked against the user's GrantedAuthority list |
|---|---|
hasRole("ADMIN") | ROLE_ADMIN (Spring Security prepends ROLE_ automatically) |
hasAuthority("ADMIN") | ADMIN (no prefix added) |
hasAuthority("ROLE_ADMIN") | ROLE_ADMIN (prefix only because you wrote it) |
User.builder().roles("ADMIN") | adds the authority ROLE_ADMIN |
User.builder().authorities("ADMIN") | adds the authority ADMIN |
The bug pattern: someone calls .roles("ROLE_ADMIN") and .hasRole("ADMIN"). The builder rejects the duplicated prefix and throws — or worse, on older versions silently produces ROLE_ROLE_ADMIN, which never matches.
Exam tip: Whenever you see both
roles(...)(orhasRole(...)) and the string"ROLE_..."in the same answer choice, look carefully — there is almost certainly a wrong answer there. The rule is: only one place gets theROLE_prefix, and you should not write it yourself when usinghasRole/roles.
→ Practice these gotchas with the question bank — security questions appear under topic "Spring Security".
Method Security
URL-based rules are not enough when business logic lives in services that are called from multiple Spring MVC controllers, scheduled jobs, or message listeners. Method security applies authorization rules directly to Java methods using annotations.
Enable it:
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
@Configuration
@EnableMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
public class MethodSecurityConfig {}@EnableMethodSecurity (Spring Security 6) replaces the old @EnableGlobalMethodSecurity (Spring Security 5). The new annotation defaults to prePostEnabled = true, so you can omit that flag — set the other two only if you use @Secured / @RolesAllowed.
The four annotations you need to know:
@Service
public class ReportService {
@PreAuthorize("hasRole('ADMIN')")
public void deleteAll() { /* runs only if caller is ROLE_ADMIN */ }
@PreAuthorize("hasRole('USER') and #userId == authentication.principal.id")
public Report find(Long userId) { /* SpEL with method args */ return null; }
@PostAuthorize("returnObject.owner == authentication.name")
public Document load(Long id) { /* check applied AFTER the method returns */ return null; }
@Secured("ROLE_ADMIN") // simple role check, no SpEL
public void purge() {}
@RolesAllowed("ADMIN") // JSR-250 equivalent of @Secured
public void archive() {}
}@PreAuthorize— evaluated before the method runs. Supports SpEL, method arguments (#userId),authentication, andprincipal. The most flexible and most-used.@PostAuthorize— evaluated after the method returns. Has access toreturnObject. Use sparingly: the method has already executed; only the return value is hidden if the check fails.@Secured— string-based role check, no SpEL. Enable withsecuredEnabled = true.@RolesAllowed— JSR-250 annotation, equivalent to@Secured. Enable withjsr250Enabled = true.
Method security works through Spring AOP proxies (the same machinery as @Transactional and @Cacheable). Two consequences:
- Self-invocation is bypassed. If method
a()callsthis.b()inside the same bean, the proxy is not in the call path andb()'s@PreAuthorizeis ignored. - The annotated method must be
publicwhen using the default JDK or CGLIB proxy. Method security cannot intercept package-private or private methods.
Exam tip: Use
@EnableMethodSecurity, not@EnableGlobalMethodSecurity. The latter is deprecated since Spring Security 5.6 and may appear in distractor answers.
CSRF Protection
Cross-Site Request Forgery (CSRF) tricks a logged-in user's browser into sending an unintended state-changing request to your site. Spring Security defends against this by requiring a per-session CSRF token on every state-changing HTTP request (POST, PUT, PATCH, DELETE).
CSRF protection is enabled by default. With form login, Spring's Thymeleaf integration adds the hidden token automatically; for plain JSP or static HTML you include it manually:
<form method="post" action="/transfer">
<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>
...
</form>For a single-page app or a stateless REST API authenticated by Authorization: Bearer …, CSRF tokens add little value (there is no implicit browser-credential to abuse) and you typically disable them:
http.csrf(csrf -> csrf.disable());If your front-end runs in the same origin as the API and uses cookie-based sessions, do not disable CSRF — emit the token to JavaScript via a cookie:
import org.springframework.security.web.csrf.CookieCsrfTokenRepository;
http.csrf(csrf -> csrf
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()));Exam tip: The exam favours the defaults. Be ready for these facts: CSRF is on by default; it blocks POST/PUT/PATCH/DELETE without a valid token; GET/HEAD/OPTIONS/TRACE are never blocked by CSRF; disabling CSRF wholesale is acceptable only for stateless APIs.
Form Login vs HTTP Basic
Spring Security ships with two ready-made authentication entry points. The choice depends on the client:
Form login — for human users in a browser. Renders an HTML login form, stores the principal in the HTTP session, redirects on success.
http.formLogin(form -> form
.loginPage("/login") // custom login page
.defaultSuccessUrl("/", true) // where to land after login
.failureUrl("/login?error")
.permitAll()
);HTTP Basic — for machine-to-machine clients and quick demos. The browser pops up the native auth dialog; clients (curl, integration tests) supply the Authorization: Basic base64(user:password) header on every request.
http.httpBasic(Customizer.withDefaults());You can enable both — the relevant filter handles whichever credential type the client supplies.
Exam tip:
httpBasic()is stateless on the credentials but Spring Security still creates an HTTP session by default to cache theSecurityContext. To make it truly stateless (for REST APIs), combine it withsessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS)).
Common Exam Traps
Distilled from the recurring mistakes on Spring Security questions:
| # | Trap | What to remember |
|---|---|---|
| 1 | hasRole("ROLE_ADMIN") vs hasRole("ADMIN") | hasRole(...) already prepends ROLE_. Pass "ADMIN", not "ROLE_ADMIN". |
| 2 | roles("ADMIN") vs authorities("ROLE_ADMIN") | Both end up storing ROLE_ADMIN. Don't mix the two on the same builder. |
| 3 | Extending WebSecurityConfigurerAdapter | Removed in SS 6. Expose a SecurityFilterChain @Bean. |
| 4 | antMatchers(...) / authorizeRequests() | Replaced by requestMatchers(...) / authorizeHttpRequests(...) in SS 6. |
| 5 | @EnableGlobalMethodSecurity | Deprecated. Use @EnableMethodSecurity. |
| 6 | Missing PasswordEncoder bean | DaoAuthenticationProvider requires one; absence causes startup failures or downgrades to NoOpPasswordEncoder (insecure). |
| 7 | User.withDefaultPasswordEncoder() in production | Demo/test only — deprecated. Define a real encoder. |
| 8 | Matcher ordering | Top-to-bottom, first match wins. anyRequest() must be last. |
| 9 | CSRF blocking a form POST | Form must include the _csrf token. The exam will not test you on disabling CSRF for browser apps. |
| 10 | Method security on a self-invoked method | Self-invocation bypasses the proxy; @PreAuthorize won't fire. Same caveat as @Transactional. |
→ Test what you just learned with 10 exam-style practice questions, then unlock the full bank when you're ready.
Frequently Asked Questions
Is Spring Security on the 2V0-72.22 exam? Yes — as a dedicated topic carrying ~6% of questions (roughly 3–4 out of 60). It is the smallest section but a frequent source of avoidable losses.
What replaced WebSecurityConfigurerAdapter in Spring Security 6?
A @Bean of type SecurityFilterChain. You configure security by chaining methods on the injected HttpSecurity and returning http.build().
Do I need to know OAuth2 or JWT for the exam? No — not in any depth. The exam scope is core security: filter chain, authentication, authorization, method security, CSRF, password encoding. OAuth2 resource server and custom JWT processing are out of scope.
What's the default username and password when I add Spring Boot Security?
Username is user. The password is randomly generated at startup and printed to the console (search the log for "Using generated security password"). The default user has no roles.
What is the difference between authentication and authorization?
Authentication establishes who you are (typically by validating credentials). Authorization decides what you are allowed to do once authenticated. Spring Security models them with two distinct pipelines — authentication via AuthenticationManager, authorization via authorizeHttpRequests / @PreAuthorize.
Do I need to memorise every filter in the chain?
No. Know that the chain is a SecurityFilterChain of servlet filters, that one filter per authentication mechanism extracts credentials and calls the AuthenticationManager, and that SecurityContextHolderFilter (formerly SecurityContextPersistenceFilter) propagates the SecurityContext per request. The exam does not ask for the full ordering.
Is method security implemented with AOP?
Yes — method security uses Spring AOP proxies, same mechanism as @Transactional and @Cacheable. This is why self-invocation bypasses @PreAuthorize. See the Spring AOP & Pointcut guide for proxy internals.
How do I write a test that runs as a specific user?
Annotate the test method with @WithMockUser(username = "alice", roles = {"ADMIN"}). Covered in detail in the Spring Boot Testing Exam Guide.
Next Steps
You have the Security domain covered. Continue with adjacent exam areas:
→ Spring Boot Testing Exam Guide — @SpringBootTest, @WebMvcTest, @WithMockUser, MockMvc
→ Spring Boot Actuator Exam Guide — built-in endpoints and how to secure them with SecurityFilterChain
→ Spring AOP & Pointcut Guide — how method security and @Transactional work under the hood
→ How to Prepare — 8-Week Study Plan — pillar guide with topic weights and resources
→ Spring Certification Exam Cost (2025) — registration, retake policy, voucher tips
→ Get Full Access — 970+ Practice Questions — full exam bank including the Security domain