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
98 lines
5.7 KiB
Markdown
98 lines
5.7 KiB
Markdown
[← 08 · meta-annotations](08-meta-annotations.md) · [chapter index](README.md)
|
|
|
|
# 09 · The audit checklist
|
|
|
|
What to run against an existing codebase, roughly in order of how often it finds something.
|
|
|
|
## Grep for it
|
|
|
|
```bash
|
|
# annotations on methods a proxy cannot advise
|
|
grep -rnE '@(Pre|Post)(Authorize|Filter)' --include='*.java' -A3 . \
|
|
| grep -E '(private|static|final) .*\('
|
|
|
|
# final classes carrying method security (context will refuse to start, but check anyway)
|
|
grep -rlE '@(Pre|Post)Authorize' --include='*.java' . | xargs grep -lE '^public final class'
|
|
|
|
# @Secured / JSR-250 in a codebase that never enabled them
|
|
grep -rlE '@(Secured|RolesAllowed|PermitAll|DenyAll)' --include='*.java' . >/dev/null \
|
|
&& grep -rn 'EnableMethodSecurity' --include='*.java' .
|
|
|
|
# @PreFilter reached with a list that may be immutable
|
|
grep -rn '@PreFilter' --include='*.java' -A5 . # then check every caller
|
|
|
|
# @PostAuthorize on a method that writes
|
|
grep -rn '@PostAuthorize' --include='*.java' -B3 . | grep -i 'transactional'
|
|
```
|
|
|
|
Self-invocation does not grep well. The signal is a public method with no annotation calling an
|
|
annotated method on the same class; an IDE "find usages" on each annotated method, filtered to
|
|
its own file, finds them faster than a regex.
|
|
|
|
## Check at runtime
|
|
|
|
Print the advisor chain for a bean you believe is secured. If the bean has zero advisors, none
|
|
of its annotations are doing anything:
|
|
|
|
```java
|
|
if (bean instanceof Advised advised) {
|
|
for (Advisor a : advised.getAdvisors()) {
|
|
System.out.println(((Ordered) a).getOrder() + " " + a);
|
|
}
|
|
}
|
|
```
|
|
|
|
Then confirm the build is passing `-parameters` — one reflection call answers it:
|
|
|
|
```java
|
|
SomeService.class.getMethod("byOwner", String.class).getParameters()[0].isNamePresent()
|
|
```
|
|
|
|
`false` means every `#parameterName` expression in the application is comparing against
|
|
nothing. See [chapter 02](02-spel-reference.md).
|
|
|
|
## Test for it
|
|
|
|
The thing worth asserting is the **negative**: that an unauthorised caller is refused. A test
|
|
that only checks the happy path passes identically whether or not the annotation is being
|
|
applied at all, which makes it worse than no test. `MethodSecurityTrapsTest` in this module is
|
|
14 such assertions —
|
|
[`src/test/java/com/ankurm/methodsec/MethodSecurityTrapsTest.java`](../src/test/java/com/ankurm/methodsec/MethodSecurityTrapsTest.java),
|
|
output in [`output/tests.txt`](output/tests.txt).
|
|
|
|
Use `@WithMockUser(roles = "USER")` and assert `AuthorizationDeniedException`. Call the method
|
|
through the injected bean, never through `new`.
|
|
|
|
## Edge-case index
|
|
|
|
Everything this module demonstrates, with the file that proves it:
|
|
|
|
| # | Behaviour | Where |
|
|
|---|---|---|
|
|
| 1 | `@Secured` / JSR-250 inert unless enabled | [01](01-how-method-security-runs.md), [`demo1.txt`](output/demo1.txt) |
|
|
| 2 | No `Authentication` gives `AuthenticationCredentialsNotFoundException`, not a denial | [01](01-how-method-security-runs.md), [`demo1.txt`](output/demo1.txt) |
|
|
| 3 | `hasAllRoles` / `hasAllAuthorities` exist | [02](02-spel-reference.md), [`demo4.txt`](output/demo4.txt) |
|
|
| 4 | `#root.args[0]` does not exist | [02](02-spel-reference.md), [`demo4.txt`](output/demo4.txt) |
|
|
| 5 | `#parameterName` needs `-parameters` | [02](02-spel-reference.md), [`demo9-*.txt`](output/) |
|
|
| 6 | `setRoleHierarchy` deprecated; `AuthorizationManagerFactory` is the 7.1 knob | [02](02-spel-reference.md), [`demo4.txt`](output/demo4.txt) |
|
|
| 7 | Self-invocation bypasses the check | [03](03-self-invocation.md), [`demo2.txt`](output/demo2.txt) |
|
|
| 8 | `final` / `static` / `private` methods are not advised | [04](04-non-proxyable-methods.md), [`demo3.txt`](output/demo3.txt) |
|
|
| 9 | Package-private methods **are** advised | [04](04-non-proxyable-methods.md), [`demo3.txt`](output/demo3.txt) |
|
|
| 10 | `final` class fails at startup | [04](04-non-proxyable-methods.md), [`demo3.txt`](output/demo3.txt) |
|
|
| 11 | JDK proxy hides non-interface methods entirely | [04](04-non-proxyable-methods.md), [`demo3.txt`](output/demo3.txt) |
|
|
| 12 | `@PreFilter` on an immutable collection is a silent no-op | [05](05-filtering.md), [`demo5.txt`](output/demo5.txt) |
|
|
| 13 | `@PreFilter` mutates the caller's own collection | [05](05-filtering.md), [`demo1.txt`](output/demo1.txt) |
|
|
| 14 | `@PreFilter` needs `filterTarget` past one argument; rejects arrays | [05](05-filtering.md), [`demo1.txt`](output/demo1.txt), [`demo5.txt`](output/demo5.txt) |
|
|
| 15 | `@PostFilter` returns the same instance it filtered | [05](05-filtering.md), [`demo5.txt`](output/demo5.txt) |
|
|
| 16 | `Optional` and `Page` are not filterable | [05](05-filtering.md), [`demo5.txt`](output/demo5.txt) |
|
|
| 17 | `AuthorizationDeniedException` carries an `AuthorizationResult` | [06](06-denied-handling.md), [`demo7.txt`](output/demo7.txt) |
|
|
| 18 | `@AuthorizeReturnObject` cannot secure a record | [06](06-denied-handling.md), [`demo7.txt`](output/demo7.txt) |
|
|
| 19 | `AuthorizationProxyFactory` package correction | [06](06-denied-handling.md), [`demo7.txt`](output/demo7.txt) |
|
|
| 20 | `@PostAuthorize` sees the already-filtered return value | [07](07-ordering-and-transactions.md), [`demo6.txt`](output/demo6.txt) |
|
|
| 21 | A denied `@PostAuthorize` does not roll back by default | [07](07-ordering-and-transactions.md), [`demo6.txt`](output/demo6.txt) |
|
|
| 22 | `{value}` templates work without the defaults bean | [08](08-meta-annotations.md), [`demo8.txt`](output/demo8.txt) |
|
|
| 23 | Method-level `@PreAuthorize` replaces the class-level one | [08](08-meta-annotations.md), [`demo8.txt`](output/demo8.txt) |
|
|
| 24 | Conflicting inherited annotations fail at call time, not startup | [08](08-meta-annotations.md), [`demo8.txt`](output/demo8.txt) |
|
|
|
|
[← 08 · meta-annotations](08-meta-annotations.md) · [chapter index](README.md)
|