Topic revision clinic · Spring Security · 15 misses · 27 drills

Spring Security — the clinic you didn't have

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.

5
Method security (a third)
3
Auth vocabulary
3
Missed LDAP a 3rd time
67%
Latest Security score
Where the fifteen fall. One cluster is a third of the bank on its own.
Method-security annotations 5SpEL support ×2 · enabling attributes ×3 Authentication vocabulary 3mechanism vs store vs principal Boot / Actuator 3health indicators · auto-config · Actuator + OAuth2 The filter chain 2FilterChainProxy vs DelegatingFilterProxy @PostAuthorize 1— URL matchers 1version-sensitive — see errata

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.

Master table 1 — method security: SpEL and enablement

Five of the fifteen are this table. Learn the two right-hand columns and a third of the bank closes.

AnnotationSourceSpEL?Enabled bySpecial variable
@PreAuthorizeSpring SecurityyesprePostEnabled#param, authentication
@PostAuthorizeSpring SecurityyesprePostEnabledreturnObject
@PreFilterSpring SecurityyesprePostEnabledfilterObject
@PostFilterSpring SecurityyesprePostEnabledfilterObject
@SecuredSpring SecurityNOsecuredEnabled—
@RolesAllowedJSR-250NOjsr250Enabled—
@PermitAll / @DenyAllJSR-250nojsr250Enabled—

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.

Version warning — two questions in this bank are written for Spring Security 6

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 examSecurity 6 — what the bank says
Enabling method security@EnableGlobalMethodSecurity(prePostEnabled = true) — all three attributes default to false@EnableMethodSecurity — prePostEnabled defaults to true
URL matchingantMatchers() and mvcMatchers() are correctboth 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.

Master table 2 — the filter chain, four classes

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)
ClassOne-line definition
DelegatingFilterProxya Servlet filter that delegates to a Spring-managed bean implementing Filter
FilterChainProxya class that delegates to a list of Spring-managed filter beans
SecurityFilterChainan interface — a chain that can be matched against an HttpServletRequest
SecurityContextHolderassociates 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.

Master table 3 — the three layers people confuse

Three questions, and LDAP has now caught you three times because it belongs to the middle row.

LayerAnswersMembers
MechanismHow do credentials arrive?Basic · Digest · Form · X.509 · OAuth2/OIDC
User storeWhere do the users live?LDAP · JDBC · in-memory
PrincipalWho 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 a UserDetails from the UserDetailsService) is used to build the Authentication object stored in the SecurityContextHolder.

Authentication always precedes authorization. If a stem says "upon successful …", the word is authentication.

Jump to a section

① Method security — 5 ② Authentication vocabulary — 3 ③ The filter chain — 2 ④ @PostAuthorize — 1 ⑤ URL matchers — 1 ⑥ Boot & Actuator — 3

① Method security 5 misses

A third of the bank, and all five are master table 1.

Q2 & Q43 · Which annotations support SpEL asked twice, missed twice

The @Pre/@Post family — never @Secured or @RolesAllowed

Your answer: you ticked @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.

Memory hook: Pre/Post = SpEL. Secured/RolesAllowed = role names only. Two of them, four of the others.
Q11, Q34 & Q42 · Enabling the annotations asked three times, missed three times

Three attributes, one per family — and none is on by default in Security 5

Q11: you ticked "@Secured supports SpEL". Q34: you ticked runAsEnabled (doesn't exist). Q42: you ticked jsr250Enabled and "none of the above". Three angles on the same table.
AttributeEnables
prePostEnabled@PreAuthorize, @PostAuthorize, @PreFilter, @PostFilter — not @RolesAllowed
securedEnabled@Secured
jsr250Enabled@RolesAllowed, @PermitAll, @DenyAll
runAsEnableddoes 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.

Memory hook: prePostEnabled → the four Pre/Post · securedEnabled → @Secured · jsr250Enabled → @RolesAllowed. No runAsEnabled. All false by default in Security 5.

② Authentication vocabulary 3 misses

Q5 · Authentication mechanisms third time — #11, #12, here

Five mechanisms; LDAP is a user store

Your answer: LDAP, for the third time in three weeks. Attempt #11 Q17, attempt #12 Q45, and now.

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.

Memory hook: Mechanism = how credentials arrive. Store = where users live. LDAP is a store — and the right answer to the other question.
Q10 · General statements about Spring Security

Multiple auth sources · method-level access control — no Java EE spec, no JAAS file

Your answer: "it implements the Java EE Security specification" and "a JAAS policy file must be configured". Both attach Spring Security to a standard it deliberately doesn't follow.
ClaimVerdict
Auth data from databases, LDAP and othersTRUE
Access control at the method levelTRUE
"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.

Memory hook: Spring Security implements no external spec and needs no JAAS file. permitAll() allows; web.ignoring() bypasses.
Q45 · "Upon successful ___, the ___ (usually a ___) builds the Authentication"

authentication · principal · UserDetails

Your answer: two options beginning "authorization" or naming an "authorization service". Authorization happens after authentication — it can't be what succeeds first.

The flow, five steps:

StepComponent
1. Credentials submitteda filter for the mechanism (e.g. UsernamePasswordAuthenticationFilter)
2. Delegated for checkingAuthenticationManager → AuthenticationProvider
3. User loadedUserDetailsService.loadUserByUsername()
4. Result becomes the principala UserDetails
5. Stored for the threadAuthentication → 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.

Memory hook: authentication → principal (a UserDetails) → Authentication → SecurityContextHolder. AuthN before AuthZ, always.

③ The filter chain 2 misses

Q25 & Q30 · FilterChainProxy vs DelegatingFilterProxy

Two classes, two jobs — the distractors were each other's definitions

Q25: you ticked the SecurityContextHolder 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:

ClassJobLives in
DelegatingFilterProxybridgesthe Servlet container
FilterChainProxyroutesthe Spring context
SecurityFilterChainmatches (it's an interface)the Spring context
SecurityContextHolderholds (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.

Memory hook: DelegatingFilterProxy bridges · FilterChainProxy routes · SecurityFilterChain matches · SecurityContextHolder holds. No class is called "DelegatingProxy".

④ @PostAuthorize 1 miss

Q12 · How @PostAuthorize behaves

Four of five true — it works with any return type, not just collections

Your answer: you ticked "@PostAuthorize is only effective if the return type is a collection or array". That's @PostFilter you're thinking of — it needs a collection.
AnnotationReturn typeEffect on failure
@PostAuthorizeanythrows AccessDeniedException
@PostFiltera collectionsilently 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.

Memory hook: @PostAuthorize: any return type, method already ran, side effects already happened. @PostFilter: collections only, silently trims.

⑤ URL matchers 1 miss · version-sensitive

Q32 · Configuring URL-based rules invented option + version conflict

Your exam: antMatchers()/mvcMatchers(). Security 6: requestMatchers().

Your answer: restrictMatchers (invented) and antMatchers. antMatchers is actually right for your exam version — the bank marked it wrong because it's written for Security 6.
MethodSecurity 5.x (your exam)Security 6
antMatchers()currentremoved
mvcMatchers()current, preferredremoved
regexMatchers()currentremoved
requestMatchers()added in 5.8the 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.

Memory hook: All real matchers end in Matchers. 5.x = ant/mvc/regex · 6 = requestMatchers only.

⑥ Boot & Actuator 3 misses

Q21 · Which health indicators for RDBMS + a clustered cache Boot clinic §2

DataSourceHealthIndicator and RedisHealthIndicator

Your answer: Jms, DiskSpace and Elasticsearch. None matches the described stack — the question names an RDBMS and a cache, nothing else.

Read the stack in the stem and map it directly:

Stem saysIndicator
RDBMSDataSourceHealthIndicator
clustered cacheRedisHealthIndicator — 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.

Memory hook: Match the indicator to the technology named in the stem. DiskSpace and Ping are always on, so they're never the thing you "enable".
Q36 · Writing your own auto-configuration

Both presence and absence conditions exist — and there are two ordering tools

Your answer: "you can condition on absence but not presence". Both directions exist for classes and beans — that symmetry is the point of the conditional family.
PresenceAbsence
@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:

UseWhen
@AutoConfigureBefore / @AutoConfigureAfteryou know the specific configuration you must precede or follow
@AutoConfigureOrderthe 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.

Memory hook: OnClass/OnMissingClass and OnBean/OnMissingBean — both directions. Before/After = relative; Order = absolute, for configs that don't know each other.
Q37 · Securing Actuator endpoints with OAuth 2.0

Fine-grained scope rules work, and @EnableResourceServer is gone

Your answer: "all authenticated users get all endpoints" and "@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()));
MatcherMatches
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.

Memory hook: EndpointRequest.to(...) / toAnyEndpoint() — never hardcode /actuator/**. @EnableResourceServer is retired; use .oauth2ResourceServer(). JWT scopes → SCOPE_ authorities.

The 15 facts, one line each

Drill these — don't read them
How to use this page (1) Master table 1 first — it is a third of the bank. Cover the two right-hand columns and reconstruct them. Which four take SpEL, and which of three attributes switches each family on. Ten minutes. (2) Then master table 3 — the mechanism/store/principal split. LDAP has now cost you three marks in three weeks purely because it's the right answer to the adjacent question. Read the noun in the stem. (3) Note the version warning. Your exam is Security 5.x: @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.
I'm your teacher — ask me anything. Say "drill method security" for a round on master table 1 alone, "drill the security vocabulary" for mechanism-vs-store-vs-principal, or "drill all five clinics" for the full rotation. Spring Core is now the only section without a clinic — point me at a Core bank whenever you like.
← Dashboard Mixed practice Testing clinic Boot clinic Data clinic Lesson 14 · Security