Split into per-article modules and add the method-security module
Moves the existing virtual-thread/context-propagation project into context-propagation/ and adds method-security/ for the Spring Security 7 method-security article: nine runnable demos, fourteen assertions, and every transcript the article quotes, regenerated by scripts/run-all.sh. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RSrsDSRKVsY588yFiMJMo9
This commit is contained in:
76
method-security/docs/01-how-method-security-runs.md
Normal file
76
method-security/docs/01-how-method-security-runs.md
Normal file
@@ -0,0 +1,76 @@
|
||||
[← 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)
|
||||
Reference in New Issue
Block a user