[← 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)