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

5.7 KiB

← 08 · meta-annotations · chapter index

09 · The audit checklist

What to run against an existing codebase, roughly in order of how often it finds something.

Grep for it

# 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:

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:

SomeService.class.getMethod("byOwner", String.class).getParameters()[0].isNamePresent()

false means every #parameterName expression in the application is comparing against nothing. See chapter 02.

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, output in 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, demo1.txt
2 No Authentication gives AuthenticationCredentialsNotFoundException, not a denial 01, demo1.txt
3 hasAllRoles / hasAllAuthorities exist 02, demo4.txt
4 #root.args[0] does not exist 02, demo4.txt
5 #parameterName needs -parameters 02, demo9-*.txt
6 setRoleHierarchy deprecated; AuthorizationManagerFactory is the 7.1 knob 02, demo4.txt
7 Self-invocation bypasses the check 03, demo2.txt
8 final / static / private methods are not advised 04, demo3.txt
9 Package-private methods are advised 04, demo3.txt
10 final class fails at startup 04, demo3.txt
11 JDK proxy hides non-interface methods entirely 04, demo3.txt
12 @PreFilter on an immutable collection is a silent no-op 05, demo5.txt
13 @PreFilter mutates the caller's own collection 05, demo1.txt
14 @PreFilter needs filterTarget past one argument; rejects arrays 05, demo1.txt, demo5.txt
15 @PostFilter returns the same instance it filtered 05, demo5.txt
16 Optional and Page are not filterable 05, demo5.txt
17 AuthorizationDeniedException carries an AuthorizationResult 06, demo7.txt
18 @AuthorizeReturnObject cannot secure a record 06, demo7.txt
19 AuthorizationProxyFactory package correction 06, demo7.txt
20 @PostAuthorize sees the already-filtered return value 07, demo6.txt
21 A denied @PostAuthorize does not roll back by default 07, demo6.txt
22 {value} templates work without the defaults bean 08, demo8.txt
23 Method-level @PreAuthorize replaces the class-level one 08, demo8.txt
24 Conflicting inherited annotations fail at call time, not startup 08, demo8.txt

← 08 · meta-annotations · chapter index