Built from your Spring Security bank plus the three Security-adjacent questions in the Boot bank — 12 Security, 3 Boot. Security is the one section that has never had a clinic, and it shows: it has swung 100% → 75% → 33% → 50% → 67% across your last five papers. The good news is the shape of these fifteen: five of them are one table — which method-security annotation supports SpEL, and which attribute switches it on.
The bank tests the same handful of facts repeatedly, and you missed them
repeatedly. "Which annotations support SpEL" appears twice (Q2 and Q43) and you ticked
@RolesAllowed both times. Enabling @Secured appears three times (Q11, Q34, Q42)
and all three went. That's five marks from one table.
Five of the fifteen are this table. Learn the two right-hand columns and a third of the bank closes.
| Annotation | Source | SpEL? | Enabled by | Special variable |
|---|---|---|---|---|
@PreAuthorize | Spring Security | yes | prePostEnabled | #param, authentication |
@PostAuthorize | Spring Security | yes | prePostEnabled | returnObject |
@PreFilter | Spring Security | yes | prePostEnabled | filterObject |
@PostFilter | Spring Security | yes | prePostEnabled | filterObject |
@Secured | Spring Security | NO | securedEnabled | — |
@RolesAllowed | JSR-250 | NO | jsr250Enabled | — |
@PermitAll / @DenyAll | JSR-250 | no | jsr250Enabled | — |
Two rules and you have the whole table:
(1) The @Pre/@Post family takes SpEL; the role-only pair (@Secured,
@RolesAllowed) does not.
(2) Three attributes, one per family: prePostEnabled → the four Pre/Post,
securedEnabled → @Secured, jsr250Enabled → the JSR-250 trio.
prePostEnabled never enables @RolesAllowed — that was Q34's trap.
And there is no runAsEnabled attribute.
Your exam targets Spring Framework 5.3 / Boot 2.5 / Security 5.x. Two of these questions describe Security 6 behaviour, and the answers differ:
| Security 5.x — your exam | Security 6 — what the bank says | |
|---|---|---|
| Enabling method security | @EnableGlobalMethodSecurity(prePostEnabled = true) — all three attributes default to false | @EnableMethodSecurity — prePostEnabled defaults to true |
| URL matching | antMatchers() and mvcMatchers() are correct | both removed; only requestMatchers() |
So on your exam, "which is the recommended way to configure
URL rules" is antMatchers/mvcMatchers, not requestMatchers — the
bank's Q32 names the Security 6 answer. If a question names a version, follow it; if not, answer for 5.x. Learn
both, but don't let Q32 overwrite what your exam expects.
Two questions, and both offered the other class's description as a distractor.
HTTP request
│
▼
┌──────────────────────┐
│ DelegatingFilterProxy│ a standard SERVLET filter — the bridge into Spring
└──────────────────────┘
│ delegates to a Spring bean named springSecurityFilterChain
▼
┌──────────────────────┐
│ FilterChainProxy │ a Spring BEAN — picks the matching chain
└──────────────────────┘
│
├─► SecurityFilterChain 1 pattern /api/** [filters…]
└─► SecurityFilterChain 2 pattern /** [filters…]
│
▼
SecurityContextHolder
(holds the Authentication for this thread)
| Class | One-line definition |
|---|---|
DelegatingFilterProxy | a Servlet filter that delegates to a Spring-managed bean implementing Filter |
FilterChainProxy | a class that delegates to a list of Spring-managed filter beans |
SecurityFilterChain | an interface — a chain that can be matched against an HttpServletRequest |
SecurityContextHolder | associates a SecurityContext with the current thread |
Read the definitions for their keyword and each is unique: "Servlet filter delegating to a bean" ⇒ DelegatingFilterProxy · "delegates to a list of filter beans" ⇒ FilterChainProxy · "interface … matched against a request" ⇒ SecurityFilterChain · "current execution thread" ⇒ SecurityContextHolder.
Three questions, and LDAP has now caught you three times because it belongs to the middle row.
| Layer | Answers | Members |
|---|---|---|
| Mechanism | How do credentials arrive? | Basic · Digest · Form · X.509 · OAuth2/OIDC |
| User store | Where do the users live? | LDAP · JDBC · in-memory |
| Principal | Who is the authenticated user? | UserDetails, held in the Authentication |
The sentence that ties all three together — and it is Q45 verbatim:
Upon successful authentication, the principal (most often aUserDetailsfrom theUserDetailsService) is used to build theAuthenticationobject stored in theSecurityContextHolder.
Authentication always precedes authorization. If a stem says "upon successful …", the word is authentication.
A third of the bank, and all five are master table 1.
@Pre/@Post family — never @Secured or @RolesAllowed@RolesAllowed on both questions.
It's JSR-250 and takes role-name strings only — there's nowhere for an expression to go.The four SpEL annotations and what each can see:
@PreAuthorize("hasRole('ADMIN') and #userId == authentication.principal.id")
public void deleteUser(Long userId) { } // sees method params + authentication
@PostAuthorize("returnObject.owner == authentication.name")
public Document get(Long id) { } // sees returnObject
@PreFilter("filterObject.owner == authentication.name")
public void process(List<Item> items) { } // filters the INPUT collection
@PostFilter("filterObject.owner == authentication.name")
public List<Item> findAll() { } // filters the RETURNED collection
Why the split exists: @Secured and @RolesAllowed predate
SpEL support and answer only "does this user have this role?". The Pre/Post family was added to answer
"does this user have the right to this particular object?" — which needs an expression that can
reach the parameters and the return value.
Also useful: SpEL can call a bean — @PreAuthorize("@authService.canAccess(#id)") —
which is how complex rules stay out of the annotation.
runAsEnabled (doesn't exist). Q42: you ticked jsr250Enabled and
"none of the above". Three angles on the same table.| Attribute | Enables |
|---|---|
prePostEnabled | @PreAuthorize, @PostAuthorize, @PreFilter, @PostFilter — not @RolesAllowed |
securedEnabled | @Secured |
jsr250Enabled | @RolesAllowed, @PermitAll, @DenyAll |
runAsEnabled | does not exist |
@Configuration
@EnableGlobalMethodSecurity( // Security 5.x — your exam
prePostEnabled = true,
securedEnabled = true,
jsr250Enabled = true)
public class SecurityConfig { }
The version difference matters here: in Security 5 all three default to
false — you must switch on what you use. In Security 6 the annotation is
@EnableMethodSecurity and prePostEnabled defaults to true.
securedEnabled and jsr250Enabled still default to false in both.
And Q11's other fact: @Secured and @RolesAllowed both work on
classes as well as methods — a class-level annotation applies to every method in it.
Here is why it keeps winning, stated as plainly as it can be: LDAP is the right answer to the question next door.
| If the stem says… | LDAP is… |
|---|---|
| "authentication mechanism" / "how credentials are collected" | OUT ❌ |
| "user store" / "where user details are kept" | IN ✅ |
A mechanism is a wire format — how credentials travel: Basic (Base64 header), Digest (hashed header), Form (HTML POST), X.509 (client certificate), OAuth2/OIDC (token). LDAP is a directory protocol for looking users up. In a real app Form collects the password and LDAP verifies it — two layers, one request.
Read the noun in the stem before anything else. That single habit converts this question in either direction, and it has now cost you three marks.
| Claim | Verdict |
|---|---|
| Auth data from databases, LDAP and others | TRUE |
| Access control at the method level | TRUE |
| "A strict implementation of the Java EE Security spec" | false — Spring Security is independent of it |
| "A JAAS policy file must be configured" | false — JAAS integration exists, but is optional and rare |
"permitAll() bypasses Spring Security completely" | false — the chain still runs |
The recurring theme in the wrong options is mandatory external standards — Java EE Security, JAAS. Spring Security predates and deliberately sidesteps both; that independence is its whole design premise, the same "non-invasive" idea as the rest of Spring.
The permitAll() distractor is the one worth keeping: permitAll() runs
the full filter chain and grants access; web.ignoring() is what skips the chain — and
loses CSRF protection and security headers with it.
The flow, five steps:
| Step | Component |
|---|---|
| 1. Credentials submitted | a filter for the mechanism (e.g. UsernamePasswordAuthenticationFilter) |
| 2. Delegated for checking | AuthenticationManager → AuthenticationProvider |
| 3. User loaded | UserDetailsService.loadUserByUsername() |
| 4. Result becomes the principal | a UserDetails |
| 5. Stored for the thread | Authentication → SecurityContextHolder |
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
UserDetails user = (UserDetails) auth.getPrincipal();
The one-word test: authentication = who are you; authorization = what may you do. Authentication always comes first, so "upon successful ___" is always authentication.
FilterChainProxy vs DelegatingFilterProxySecurityContextHolder and
DelegatingFilterProxy descriptions. Q30: you ticked DelegatingProxy, which
doesn't exist. Every option in Q25 was a real class's real definition — you had to match
them.The chain again, with the job of each in one word:
| Class | Job | Lives in |
|---|---|---|
DelegatingFilterProxy | bridges | the Servlet container |
FilterChainProxy | routes | the Spring context |
SecurityFilterChain | matches (it's an interface) | the Spring context |
SecurityContextHolder | holds (per thread) | a ThreadLocal |
Why two proxies rather than one: the Servlet container can only register plain
Filters, and it knows nothing about Spring beans. DelegatingFilterProxy is a plain
filter whose only job is to look up a Spring bean by name and hand off — that's the bridge. Everything
after it is ordinary Spring.
Q30 accepted both real classes because both genuinely intercept requests —
DelegatingFilterProxy at the container edge, FilterChainProxy immediately behind it.
@PostAuthorize 1 miss@PostAuthorize behaves@PostFilter you're thinking of — it needs
a collection.| Annotation | Return type | Effect on failure |
|---|---|---|
@PostAuthorize | any | throws AccessDeniedException |
@PostFilter | a collection | silently removes non-matching elements |
The fact worth carrying: @PostAuthorize does not stop the method
running — it runs, produces a result, and only then is the expression evaluated. So any side effects
inside the method have already happened: rows written, emails sent, counters incremented. Only the
return value is withheld.
That's why @PreAuthorize is preferred whenever the decision can be made from the
parameters — it blocks the call before anything happens. Use @PostAuthorize only when the decision
genuinely depends on what came back (typically ownership).
One more from that question: @EnableMethodSecurity belongs on a
@Configuration class, not on the @Service where the snippet put it. It compiles
either way, which is what makes it a nasty real-world bug.
antMatchers()/mvcMatchers(). Security 6: requestMatchers().restrictMatchers (invented) and
antMatchers. antMatchers is actually right for your
exam version — the bank marked it wrong because it's written for Security 6.| Method | Security 5.x (your exam) | Security 6 |
|---|---|---|
antMatchers() | current | removed |
mvcMatchers() | current, preferred | removed |
regexMatchers() | current | removed |
requestMatchers() | added in 5.8 | the only option |
restrictMatchers() | never existed | |
Why the change happened, which is the transferable part:
antMatchers("/admin") matched that string exactly, while Spring MVC would also serve
/admin/ and /admin.html — so an Ant matcher could leave a route reachable but
unprotected. requestMatchers() picks MvcRequestMatcher when Spring MVC is present,
closing the gap automatically.
For the exam: if a question names Security 6 or Boot 3, answer
requestMatchers. Otherwise answer antMatchers/mvcMatchers. Either way,
every real matcher ends in Matchers — which kills restrictMatchers and
match() without knowing any version.
Matchers. 5.x = ant/mvc/regex · 6 = requestMatchers only.DataSourceHealthIndicator and RedisHealthIndicatorRead the stack in the stem and map it directly:
| Stem says | Indicator |
|---|---|
| RDBMS | DataSourceHealthIndicator |
| clustered cache | RedisHealthIndicator — Redis is the clustered cache |
| (not mentioned) | Jms · Elasticsearch · Cassandra · Mail |
DiskSpace is the interesting rejection: it's always registered anyway, so it
isn't something you'd "enable" for this stack. The question asks what you'd add for these components.
The rule from the Boot clinic still does the work: indicators are named after a technology you can ping, and each is auto-configured when that client is on the classpath.
| Presence | Absence |
|---|---|
@ConditionalOnClass | @ConditionalOnMissingClass |
@ConditionalOnBean | @ConditionalOnMissingBean |
@ConditionalOnMissingBean is the important one — it's how every Boot auto-configuration
backs off when you define your own bean.
And the two ordering tools, which the correct options covered:
| Use | When |
|---|---|
@AutoConfigureBefore / @AutoConfigureAfter | you know the specific configuration you must precede or follow |
@AutoConfigureOrder | the configurations have no knowledge of each other — you just want to be early or late |
That distinction — relative vs absolute — is exactly what the question tested, and it's the same pair you met on attempt #11's Q36.
@EnableResourceServer is gone@EnableResourceServer is required".
The first contradicts the code in the question; the second names a deprecated annotation.EndpointRequest is the piece worth knowing — a matcher factory built for Actuator:
http.authorizeHttpRequests(auth -> auth
.requestMatchers(EndpointRequest.to(HealthEndpoint.class)).permitAll()
.requestMatchers(EndpointRequest.to(InfoEndpoint.class, MetricsEndpoint.class))
.hasAuthority("SCOPE_actuator.read")
.requestMatchers(EndpointRequest.toAnyEndpoint()).authenticated())
.oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults()));
| Matcher | Matches |
|---|---|
EndpointRequest.to(X.class) | specific endpoints |
EndpointRequest.toAnyEndpoint() | all Actuator endpoints |
EndpointRequest.toLinks() | the discovery page at the base path |
Why EndpointRequest rather than a path string: the base path is
configurable (management.endpoints.web.base-path), so hardcoding /actuator/** breaks
the moment someone changes it. EndpointRequest resolves the real paths at runtime.
On @EnableResourceServer: it belonged to the old, now-retired
spring-security-oauth2 project. Since Security 5.2, resource-server support is built in and
configured with .oauth2ResourceServer(...) — no separate annotation. Note the
SCOPE_ prefix: JWT scopes become authorities named SCOPE_<scope> by default.
@PreAuthorize, @PostAuthorize, @PreFilter, @PostFilter. Role-only: @Secured, @RolesAllowedprePostEnabled → the four Pre/Post · securedEnabled → @Secured · jsr250Enabled → @RolesAllowed/@PermitAll/@DenyAllprePostEnabled does NOT enable @RolesAllowed, and there is no runAsEnabledfalse. Security 6: @EnableMethodSecurity, prePostEnabled defaults true@Secured and @RolesAllowed work on classes as well as methodsUserDetails) → Authentication → SecurityContextHolder. AuthN before AuthZpermitAll() runs the chain and allows; web.ignoring() skips it (losing CSRF and headers)@PostAuthorize: any return type, and the method has already run — side effects happen. @PostFilter needs a collection@EnableMethodSecurity/@EnableGlobalMethodSecurity belongs on a @Configuration classantMatchers/mvcMatchers/regexMatchers · 6 = requestMatchers only. All real ones end in MatchersEndpointRequest.to(...), never a hardcoded path. @EnableResourceServer is retired; JWT scopes become SCOPE_ authorities@EnableGlobalMethodSecurity,
everything off by default, antMatchers/mvcMatchers. Two questions in this bank answer
for Security 6.
(4) Drill, don't re-read. 27 questions here. Security is now the fourth clinic — rotate all of them for
20 minutes before your next attempt rather than reading one new page.