1
0
Files
spring-security-demo/method-security/docs/01-how-method-security-runs.md
asmhatre 73ab67b171 Add the filter-chain module
Companion project for "The Spring Security Filter Chain Explained". A real
Spring Boot 4.1.1 servlet application whose scenarios are Spring profiles, plus
a diagnostic controller that prints the live FilterChainProxy, the reflected
FilterOrderRegistration table, and the servlet container's own registrations.

Twelve captured transcripts under docs/output/, nine cross-linked doc chapters,
21 assertions.

Also fixes a broken relative link in method-security/docs/01: the cross-module
reference to context-propagation/README.md needed two levels up, not one.
2026-08-26 02:21:47 +00:00

77 lines
3.9 KiB
Markdown

[← chapter index](README.md) · [next: the SpEL reference →](02-spel-reference.md)
# 01 · How method security actually runs
Run: `java -cp target/classes:$(cat cp.txt) com.ankurm.methodsec.Demo1AnnotationsInAction`
Output: [`output/demo1.txt`](output/demo1.txt)
Source: [`Demo1AnnotationsInAction.java`](../src/main/java/com/ankurm/methodsec/Demo1AnnotationsInAction.java)
## The one-sentence model
`@PreAuthorize` is not a keyword the JVM understands. It is an annotation that a Spring AOP
**advisor** matches, on a **proxy** that wraps your bean, intercepting calls that arrive from
outside. Every trap in this repository follows from that sentence.
## What `@EnableMethodSecurity` switches on
Nothing happens without it. Spring Boot's security auto-configuration does not enable method
security; the annotation is yours to add. Its attributes, read out of the `AnnotationDefault`
attributes in `spring-security-config-7.1.1.jar` rather than from documentation:
| Attribute | Default | Effect |
|---|---|---|
| `prePostEnabled` | `true` | `@PreAuthorize`, `@PostAuthorize`, `@PreFilter`, `@PostFilter` |
| `securedEnabled` | `false` | `@Secured` |
| `jsr250Enabled` | `false` | `@RolesAllowed`, `@PermitAll`, `@DenyAll` |
| `proxyTargetClass` | `false` | Force CGLIB even when the bean implements interfaces |
| `mode` | `AdviceMode.PROXY` | The alternative is `ASPECTJ`, which sidesteps chapters 03 and 04 entirely |
| `offset` | `0` | Shifts every security advisor's order by this amount |
Note the third row. `@Secured("ROLE_ADMIN")` and `@RolesAllowed("ADMIN")` compile, look
correct in review, and do nothing at all until you switch them on. That is the zeroth silent
failure, and unlike the two the article is named for it takes one attribute to fix.
There is no `order` attribute, despite what several guides say. It is `offset`, and it moves
all the interceptors together — see [chapter 07](07-ordering-and-transactions.md).
## The call path
```
caller
└─ proxy (CGLIB subclass, or JDK dynamic proxy)
└─ @PreFilter advisor, order 100 mutates the argument collection
└─ @PreAuthorize advisor, order 200 evaluates SpEL, throws or proceeds
└─ @PostAuthorize advisor, order 500
└─ @PostFilter advisor, order 600
└─ your method body
```
Each advisor builds a `MethodSecurityExpressionRoot` over the `Authentication` from
`SecurityContextHolder` plus the `MethodInvocation`, hands it to a
`MethodSecurityExpressionHandler`, and evaluates the annotation's expression against it.
## What a denial looks like
`Demo1` runs the same service as three identities. Two details from
[`output/demo1.txt`](output/demo1.txt) are worth internalising:
- With an `Authentication` present but insufficient, you get `AuthorizationDeniedException`.
- With **no** `Authentication` at all you get `AuthenticationCredentialsNotFoundException`
instead — a different exception, from a different place, which an `@ExceptionHandler` written
only for `AccessDeniedException` will not catch. `@PermitAll` still returns normally, because
it never asks for the `Authentication`.
In a servlet application both are normally translated by `ExceptionTranslationFilter`, so you
see 403 and 401 respectively. Outside a request — a scheduled job, a message listener, a test —
nothing translates them and they surface raw.
## Where the `Authentication` comes from
`SecurityContextHolder`, on the calling thread. If the call happens on a thread that never
received the context, method security does not fail open; it throws
`AuthenticationCredentialsNotFoundException`. Getting the context onto that thread is a
separate topic with its own module in this repository — see
[`../context-propagation/`](../../context-propagation/README.md).
[← chapter index](README.md) · [next: the SpEL reference →](02-spel-reference.md)