1
0
Files
spring-security-demo/method-security/docs/09-audit-checklist.md
asmhatre 5e9e7f1b12 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
2026-08-25 02:01:29 +00:00

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)