[← 03 · self-invocation](03-self-invocation.md) · [chapter index](README.md) · [next: filtering →](05-filtering.md) # 04 · Methods the proxy cannot advise Run: `java -cp target/classes:$(cat cp.txt) com.ankurm.methodsec.Demo3NonProxyable` Output: [`output/demo3.txt`](output/demo3.txt) Source: [`Demo3NonProxyable.java`](../src/main/java/com/ankurm/methodsec/Demo3NonProxyable.java) A CGLIB proxy is a generated subclass. It intercepts a method by overriding it. Anything that cannot be overridden cannot be advised. ## The results Same class, same `@PreAuthorize("hasRole('ADMIN')")` on every method, same `ROLE_USER` caller: | Declaration | Advised? | How you find out | |---|---|---| | `public` | yes | denied, as intended | | `public final` | **no** | a `WARNING` from `CglibAopProxy` at startup | | `static` | **no** | nothing at all | | package-private | yes | denied — the generated subclass is in the same package | | `private` | **no** | nothing at all; also unreachable except by self-invocation | | `final class` | n/a | the context **fails to start** | The package-private row is the surprise. Received wisdom is "only public methods are secured"; the generated subclass lands in the same package as the target, so it can and does override a package-private method. Verified by reflection in the same run: ``` publicAdminOnly declared final=false overridden by proxy=true finalAdminOnly declared final=true overridden by proxy=false packagePrivateAdminOnly declared final=false overridden by proxy=true ``` ## `final` is the loud one, `static` and `private` are not Spring does warn about a public `final` method — `CglibAopProxy.doValidateClass` logs: ``` WARNING: Public final method [public final java.lang.String ...Vault.finalAdminOnly()] cannot get proxied via CGLIB, consider removing the final marker or using interface-based JDK proxies. ``` It is in [`output/demo3.txt`](output/demo3.txt), at the very top, at startup, mixed in with everything else an application logs while booting. It is easy to miss and it is more than you get for `static` and `private`, which produce nothing. A `final` **class** is different again: CGLIB cannot subclass it at all, so the container refuses to start with `IllegalArgumentException: Cannot subclass final class ...SealedVault`. That is the friendliest failure in this chapter. It is also the reason a Java `record` cannot carry method security on its own methods — records are final. If you need a secured getter on a returned object (see [chapter 06](06-denied-handling.md)), it has to be a non-final class. ## Interfaces and JDK proxies `@EnableMethodSecurity(proxyTargetClass = false)` is the default, so a bean that implements an interface gets a **JDK dynamic proxy**, which implements only the interfaces. A public annotated method that is not on the interface is then not merely unadvised — it is not on the proxy at all: ``` proxy is a JDK proxy -> true proxied interfaces -> [interface ...LedgerOperations] cast proxy to Ledger impl -> ClassCastException: class jdk.proxy2.$Proxy18 cannot be cast to ...Ledger ``` In a Spring Boot application this is usually moot: Boot sets `proxyTargetClass = true` globally via `spring.aop.proxy-target-class`, which defaults to `true`. In a plain Spring context, or with that property flipped, it is live. Either way, the `ClassCastException` is loud — the dangerous version is the one where the interface method *is* annotated and the implementation carries a second, different annotation; see [chapter 08](08-meta-annotations.md). ## What to actually do - Do not put `@PreAuthorize` on anything `private` or `static`. Neither is reachable through a proxy, and neither will tell you. - Remove `final` from methods that carry security annotations, or make peace with the fact that the annotation is documentation. - If you use records or other final classes as return values, secure the method that returns them, not the accessors on them. - Grep for the combination. [Chapter 09](09-audit-checklist.md) has the patterns. [← 03 · self-invocation](03-self-invocation.md) · [chapter index](README.md) · [next: filtering →](05-filtering.md)