2V0-72.22 Exam Guide
Spring Security Configuration on the 2V0-72.22
Spring Security has changed significantly in recent versions, and the exam sits awkwardly across both old and new APIs. You need to recognise WebSecurityConfigurerAdapter when you see it, know why it was deprecated, and be fluent with the modern SecurityFilterChain approach. The configuration questions are usually about understanding what a given config actually does — not writing it from memory.
Old vs modern configuration style
WebSecurityConfigurerAdapter was deprecated in Spring Security 5.7. The modern approach defines a SecurityFilterChain bean. Both can appear on the exam — you need to read them and understand what they configure, even if you would never write the old style today.
// OLD — deprecated since Spring Security 5.7, still appears on the exam
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests() // deprecated
.antMatchers("/admin/**").hasRole("ADMIN") // deprecated
.anyRequest().authenticated()
.and()
.formLogin();
}
}
// MODERN — SecurityFilterChain bean
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
)
.formLogin(Customizer.withDefaults());
return http.build();
}
}Key differences to recognise: authorizeRequests → authorizeHttpRequests, and antMatchers → requestMatchers. The exam tests that you know which is current and which is deprecated.
Request matcher ordering — the order matters
Rules are evaluated top to bottom. The first matching rule wins. A common exam trap is putting a broad rule before a specific one, making the specific rule unreachable.
// WRONG — anyRequest() catches everything; the /admin rule is never reached
http.authorizeHttpRequests(auth -> auth
.anyRequest().authenticated()
.requestMatchers("/admin/**").hasRole("ADMIN") // unreachable
);
// CORRECT — specific rules first, broad rules last
http.authorizeHttpRequests(auth -> auth
.requestMatchers("/public/**").permitAll()
.requestMatchers("/admin/**").hasRole("ADMIN")
.requestMatchers("/api/**").hasAnyRole("USER", "ADMIN")
.anyRequest().authenticated()
);A question will often show a misconfigured chain and ask why a user with ROLE_ADMIN cannot access /admin. Check the rule ordering first.
Method security — @PreAuthorize vs @Secured vs @RolesAllowed
Method security must be explicitly enabled. The modern annotation is @EnableMethodSecurity (replaces the deprecated @EnableGlobalMethodSecurity).
@Configuration
@EnableMethodSecurity // enables @PreAuthorize, @PostAuthorize, @Secured, @RolesAllowed
public class MethodSecurityConfig { }
@Service
public class AdminService {
// SpEL expression — evaluated before method executes
@PreAuthorize("hasRole('ADMIN')")
public void deleteUser(Long id) { ... }
// SpEL with method parameter
@PreAuthorize("hasRole('ADMIN') or #userId == authentication.principal.id")
public User getUser(Long userId) { ... }
// Evaluated after method returns — can access return value
@PostAuthorize("returnObject.owner == authentication.name")
public Document getDocument(Long id) { ... }
// @Secured — no SpEL, role name must include "ROLE_" prefix
@Secured("ROLE_ADMIN")
public void adminTask() { ... }
// @RolesAllowed — JSR-250, no SpEL, no "ROLE_" prefix needed
@RolesAllowed("ADMIN")
public void anotherAdminTask() { ... }
}The key distinction the exam tests: @PreAuthorize supports full SpEL expressions including method parameters and the authentication object. @Secured and @RolesAllowed only accept role names.
UserDetailsService and password encoding
@Bean
public UserDetailsService userDetailsService() {
// In-memory — for testing and exam examples
UserDetails user = User.withDefaultPasswordEncoder()
.username("user")
.password("password")
.roles("USER")
.build();
return new InMemoryUserDetailsManager(user);
}
// In production: implement UserDetailsService
@Service
public class CustomUserDetailsService implements UserDetailsService {
@Autowired
private UserRepository userRepo;
@Override
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException {
return userRepo.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException(username));
}
}
// PasswordEncoder is required when not using withDefaultPasswordEncoder()
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}Exam questions on UserDetailsService usually ask about the interface contract: it throws UsernameNotFoundException (not returns null), and it returns UserDetails. The PasswordEncoder bean must be present or Spring Security will reject the configuration.
CSRF — when it applies and when to disable it
CSRF protection is enabled by default. It protects state-changing requests (POST, PUT, DELETE, PATCH) by requiring a token. REST APIs that use stateless authentication (JWT, OAuth2) should disable it — the token-based auth already provides the protection CSRF is designed for.
// CSRF enabled by default — fine for server-rendered apps with sessions
// Disable for stateless REST APIs
http.csrf(csrf -> csrf.disable());
// Or: disable only for specific paths
http.csrf(csrf -> csrf
.ignoringRequestMatchers("/api/**")
);The exam may ask: a REST API with JWT authentication is returning 403 on POST requests. What is the likely cause? CSRF is still enabled. The fix is to disable it for the API endpoints.
Multiple filter chains and ordering
You can define multiple SecurityFilterChain beans to apply different rules to different URL patterns. Order matters — use @Order to control which chain is evaluated first. The first chain whose securityMatcher matches wins.
@Bean
@Order(1) // evaluated first
public SecurityFilterChain apiFilterChain(HttpSecurity http) throws Exception {
http
.securityMatcher("/api/**") // only applies to /api/** paths
.authorizeHttpRequests(auth -> auth.anyRequest().authenticated())
.sessionManagement(session ->
session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.csrf(csrf -> csrf.disable());
return http.build();
}
@Bean
@Order(2) // evaluated second — catches everything else
public SecurityFilterChain webFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/public/**").permitAll()
.anyRequest().authenticated()
)
.formLogin(Customizer.withDefaults());
return http.build();
}Accessing the authenticated user
// From SecurityContextHolder — anywhere in the code
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
String username = auth.getName();
Collection<? extends GrantedAuthority> roles = auth.getAuthorities();
// In a Spring MVC controller — injected automatically
@GetMapping("/profile")
public String profile(@AuthenticationPrincipal UserDetails userDetails) {
return userDetails.getUsername();
}
// With custom principal type
@GetMapping("/profile")
public String profile(@AuthenticationPrincipal CustomUser user) {
return user.getEmail();
}Quick reference: what the exam actually asks
- —
WebSecurityConfigurerAdapter: deprecated since 5.7, replaced bySecurityFilterChainbean - —
authorizeRequests+antMatchers: deprecated, replaced byauthorizeHttpRequests+requestMatchers - — Request matcher order: specific rules must come before
anyRequest() - —
@PreAuthorize: supports SpEL, including method parameters andauthentication - —
@Secured: role names only, requiresROLE_prefix - — CSRF: enabled by default, should be disabled for stateless REST APIs
- —
UserDetailsService.loadUserByUsername: throwsUsernameNotFoundException, never returns null - — Multiple filter chains:
@Ordercontrols priority, lower value wins - —
@EnableMethodSecurityreplaces deprecated@EnableGlobalMethodSecurity
Practice Spring Security questions under exam conditions
PrepForge mock exams include Security configuration scenarios with full explanations.
Start a Mock Exam
