1
0

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
This commit is contained in:
2026-08-25 02:01:29 +00:00
parent 9f950bffa9
commit 5e9e7f1b12
65 changed files with 4088 additions and 119 deletions

View File

@@ -0,0 +1,50 @@
==============================================================================
Demo 1 -- the four pre/post annotations, @Secured and JSR-250, all switched on
==============================================================================
SLF4J(W): No SLF4J providers were found.
SLF4J(W): Defaulting to no-operation (NOP) logger implementation
SLF4J(W): See https://www.slf4j.org/codes.html#noProviders for further details.
as alice (ROLE_USER)
--------------------
@PreAuthorize hasRole('ADMIN') DENIED -> AuthorizationDeniedException: Access Denied
@PreAuthorize #owner == authentication.name ALLOWED -> [Account[1,alice,100], Account[3,alice,300]]
@PreAuthorize #owner == authentication.name DENIED -> AuthorizationDeniedException: Access Denied
@PostAuthorize returnObject.owner == ...name ALLOWED -> Account[1,alice,100]
@PostAuthorize returnObject.owner == ...name DENIED -> AuthorizationDeniedException: Access Denied
@PostFilter filterObject.owner == ...name ALLOWED -> [Account[1,alice,100], Account[3,alice,300]]
@Secured("ROLE_ADMIN") DENIED -> AuthorizationDeniedException: Access Denied
@RolesAllowed("ADMIN") DENIED -> AuthorizationDeniedException: Access Denied
@PermitAll ALLOWED -> open payload
@DenyAll DENIED -> AuthorizationDeniedException: Access Denied
as root (ROLE_ADMIN, ROLE_USER)
-------------------------------
@PreAuthorize hasRole('ADMIN') ALLOWED -> the admin console
@Secured("ROLE_ADMIN") ALLOWED -> secured payload
@RolesAllowed("ADMIN") ALLOWED -> jsr250 payload
@PostFilter filterObject.owner == ...name ALLOWED -> []
with no Authentication at all (SecurityContextHolder cleared)
-------------------------------------------------------------
@PreAuthorize hasRole('ADMIN') DENIED -> AuthenticationCredentialsNotFoundException: An Authentication object was not found in the SecurityContext
@PermitAll ALLOWED -> open payload
@PreFilter -- filtering the ARGUMENT, as alice
----------------------------------------------
caller's list before the call : [Account[1,alice,100], Account[2,bob,200], Account[3,alice,300]]
method body saw : [Account[1,alice,150], Account[3,alice,350]]
caller's list after the call : [Account[1,alice,150], Account[3,alice,350]]
@PreFilter did not hand the method a copy. It removed bob's account from
the caller's own list, in place, before the method body ever ran. That is
why Demo 5's immutable List.of(..) blows up.
@PreFilter on a method with more than one argument
--------------------------------------------------
no filterTarget, 2 args DENIED -> IllegalStateException: Unable to determine the method argument for filtering. Specify the filter target.
method body saw : [Account[4,alice,60]]
filterTarget = "accounts" ALLOWED -> (void)
This one is loud, not silent -- but it only fires when the method is
actually called, so a rarely-exercised path can ship broken.

View File

@@ -0,0 +1,27 @@
==============================================================================
Demo 2 -- self-invocation: the annotation is there, the check is not
==============================================================================
SLF4J(W): No SLF4J providers were found.
SLF4J(W): Defaulting to no-operation (NOP) logger implementation
SLF4J(W): See https://www.slf4j.org/codes.html#noProviders for further details.
alice has ROLE_USER only. adminReport() requires ROLE_ADMIN.
------------------------------------------------------------
reports.adminReport() (via proxy) DENIED -> AuthorizationDeniedException: Access Denied
reports.userEntryPoint() (this.adminReport()) ALLOWED -> TOP SECRET REVENUE NUMBERS
Is the annotation actually there? (reflection on the target class)
------------------------------------------------------------------
ReportService.adminReport() @PreAuthorize -> @org.springframework.security.access.prepost.PreAuthorize("hasRole('ADMIN')")
bean is an AOP proxy -> true
proxy class -> com.ankurm.methodsec.Demo2SelfInvocation$ReportService$$SpringCGLIB$$0
target class -> com.ankurm.methodsec.Demo2SelfInvocation$ReportService
The annotation is present, the bean IS proxied, and the call was still
not checked. The proxy only sees calls that arrive from outside.
Three ways to make the inner call go through the proxy
------------------------------------------------------
self-injection (ObjectProvider) DENIED -> AuthorizationDeniedException: Access Denied
AopContext.currentProxy() DENIED -> AuthorizationDeniedException: Access Denied
call a different bean (collaborator) DENIED -> AuthorizationDeniedException: Access Denied

View File

@@ -0,0 +1,37 @@
==============================================================================
Demo 3 -- @PreAuthorize on methods the proxy cannot override
==============================================================================
SLF4J(W): No SLF4J providers were found.
SLF4J(W): Defaulting to no-operation (NOP) logger implementation
SLF4J(W): See https://www.slf4j.org/codes.html#noProviders for further details.
alice has ROLE_USER. Every method below says hasRole('ADMIN').
--------------------------------------------------------------
public (overridable) DENIED -> AuthorizationDeniedException: Access Denied
public final (NOT overridable) ALLOWED -> final payload
static (NOT overridable) ALLOWED -> static payload
package-private (overridable, same package) DENIED -> AuthorizationDeniedException: Access Denied
private, reached via a public wrapper ALLOWED -> private payload
What the proxy actually overrode
--------------------------------
publicAdminOnly declared final=false overridden by proxy=true
finalAdminOnly declared final=true overridden by proxy=false
packagePrivateAdminOnly declared final=false overridden by proxy=true
proxy class -> com.ankurm.methodsec.Demo3NonProxyable$Vault$$SpringCGLIB$$0
A JDK dynamic proxy only advises methods that are ON the interface
------------------------------------------------------------------
proxy is a JDK proxy -> true
proxied interfaces -> [interface com.ankurm.methodsec.Demo3NonProxyable$LedgerOperations]
onTheInterface() (advised) DENIED -> AuthorizationDeniedException: Access Denied
notOnTheInterface() is public and annotated, but the JDK proxy does not
implement it at all -- a caller cannot even reach it without casting to
the implementation class, and that cast throws ClassCastException.
cast proxy to Ledger impl class DENIED -> ClassCastException: class jdk.proxy2.$Proxy18 cannot be cast to class com.ankurm.methodsec.Demo3NonProxyable$Ledger (jdk.proxy2.$Proxy18 is in module jdk.proxy2 of loader 'app'; com.ankurm.methodsec.Demo3NonProxyable$Ledger is in unnamed module of loader 'app')
A final CLASS is the loud one
-----------------------------
startup FAILED -> BeanCreationException
root cause -> java.lang.IllegalArgumentException
message -> Cannot subclass final class com.ankurm.methodsec.Demo3NonProxyable$SealedVault

View File

@@ -0,0 +1,69 @@
==============================================================================
Demo 4 -- what you can actually write inside @PreAuthorize / @PostAuthorize
==============================================================================
SLF4J(W): No SLF4J providers were found.
SLF4J(W): Defaulting to no-operation (NOP) logger implementation
SLF4J(W): See https://www.slf4j.org/codes.html#noProviders for further details.
alice: ROLE_USER, ROLE_AUDITOR, plus the authority 'report:read'
----------------------------------------------------------------
permitAll ALLOWED -> ok
denyAll DENIED -> AuthorizationDeniedException: Access Denied
isAuthenticated() ALLOWED -> ok
isAnonymous() DENIED -> AuthorizationDeniedException: Access Denied
isFullyAuthenticated() ALLOWED -> ok
isRememberMe() DENIED -> AuthorizationDeniedException: Access Denied
hasRole('ADMIN') DENIED -> AuthorizationDeniedException: Access Denied
hasAnyRole('ADMIN','AUDITOR') ALLOWED -> ok
hasAllRoles('USER','AUDITOR') ALLOWED -> ok
hasAuthority('report:read') ALLOWED -> ok
hasAnyAuthority('report:read','x') ALLOWED -> ok
hasAllAuthorities('report:read','x') DENIED -> AuthorizationDeniedException: Access Denied
authentication.name == 'alice' ALLOWED -> ok
principal == 'alice' ALLOWED -> ok
root: ROLE_ADMIN only (RoleHierarchy says ADMIN > USER > GUEST)
---------------------------------------------------------------
permitAll ALLOWED -> ok
denyAll DENIED -> AuthorizationDeniedException: Access Denied
isAuthenticated() ALLOWED -> ok
isAnonymous() DENIED -> AuthorizationDeniedException: Access Denied
isFullyAuthenticated() ALLOWED -> ok
isRememberMe() DENIED -> AuthorizationDeniedException: Access Denied
hasRole('ADMIN') ALLOWED -> ok
hasAnyRole('ADMIN','AUDITOR') ALLOWED -> ok
hasAllRoles('USER','AUDITOR') DENIED -> AuthorizationDeniedException: Access Denied
hasAuthority('report:read') DENIED -> AuthorizationDeniedException: Access Denied
hasAnyAuthority('report:read','x') DENIED -> AuthorizationDeniedException: Access Denied
hasAllAuthorities('report:read','x') DENIED -> AuthorizationDeniedException: Access Denied
authentication.name == 'alice' DENIED -> AuthorizationDeniedException: Access Denied
principal == 'alice' DENIED -> AuthorizationDeniedException: Access Denied
Role prefix and hierarchy
-------------------------
hasRole('USER') -> ROLE_USER ALLOWED -> ok
hasAuthority('USER') -> literal 'USER' DENIED -> AuthorizationDeniedException: Access Denied
hasAuthority('ROLE_USER') ALLOWED -> ok
root hasRole('GUEST') via RoleHierarchy ALLOWED -> ok
Method arguments, the return value, and bean references
-------------------------------------------------------
#owner == authentication.name ("alice") ALLOWED -> ok
#owner == authentication.name ("bob") DENIED -> AuthorizationDeniedException: Access Denied
@P("o") alias, #o == ...name ("alice") ALLOWED -> ok
#root.this (the target object) ALLOWED -> ok
#root.args[0] -- no such property DENIED -> IllegalArgumentException: Failed to evaluate expression '#root.args[0] == authentication.name' [cause: SpelEvaluationException: EL1008E: Property or field 'args' cannot be found on object of type 'org.springframework.security.access.expression.method.MethodSecurityExpressionRoot' - maybe not public or not valid?]
@policy.canRead(authentication, #id) id=1 ALLOWED -> ok
@policy.canRead(authentication, #id) id=9 DENIED -> AuthorizationDeniedException: Access Denied
hasPermission(#id, 'account', 'read') id=1 ALLOWED -> ok
hasPermission(#id, 'account', 'read') id=9 DENIED -> AuthorizationDeniedException: Access Denied
T(java.time.LocalDate) type reference ALLOWED -> ok
@PostAuthorize returnObject.owner == ...name ALLOWED -> Account[1,alice,100]
@PostAuthorize returnObject.owner == ...name DENIED -> AuthorizationDeniedException: Access Denied
The literal constants on SecurityExpressionRoot
-----------------------------------------------
permitAll / denyAll exist as BOTH a boolean field and a no-arg method,
and read/write/create/delete/admin are String constants meant for
hasPermission(..) -- e.g. hasPermission(#id, 'account', read).
hasPermission(#id, 'account', read) id=1 ALLOWED -> ok

View File

@@ -0,0 +1,58 @@
==============================================================================
Demo 5 -- filterObject: mutability, container types, and the silent no-op
==============================================================================
SLF4J(W): No SLF4J providers were found.
SLF4J(W): Defaulting to no-operation (NOP) logger implementation
SLF4J(W): See https://www.slf4j.org/codes.html#noProviders for further details.
@PreFilter needs a MUTABLE argument -- and does not tell you when it is not
---------------------------------------------------------------------------
method body saw: [Account[1,alice,100], Account[3,alice,300]]
new ArrayList<>(..) ALLOWED -> (void)
method body saw: [Account[1,alice,100], Account[2,bob,200], Account[3,alice,300]]
List.of(..) (immutable) ALLOWED -> (void)
method body saw: [Account[1,alice,100], Account[2,bob,200], Account[3,alice,300]]
List.copyOf(..) (immutable) ALLOWED -> (void)
method body saw: [Account[1,alice,100], Account[2,bob,200], Account[3,alice,300]]
Arrays.asList(..) ALLOWED -> (void)
method body saw: [Account[1,alice,100], Account[2,bob,200], Account[3,alice,300]]
Collections.unmodifiableList(..) ALLOWED -> (void)
method body saw: [Account[1,alice,100], Account[2,bob,200], Account[3,alice,300]]
stream().toList() (unmodifiable since 16) ALLOWED -> (void)
method body saw: [Account[1,alice,100], Account[3,alice,300]]
stream().collect(toList()) (ArrayList) ALLOWED -> (void)
Account[] (arrays rejected outright) DENIED -> IllegalStateException: Pre-filtering on array types is not supported. Using a Collection will solve this problem.
Read the second and third lines again: bob's account reached the method
body. @PreFilter filters by CLEARING the caller's collection and adding
the survivors back. On an immutable list that throws, and
DefaultMethodSecurityExpressionHandler.filterCollection catches the
UnsupportedOperationException and returns a fresh list instead -- which
PreFilterAuthorizationMethodInterceptor.invoke then discards, because it
ignores filter()'s return value entirely. No exception, no WARN, no 403.
The only trace it leaves (same call, logger at TRACE)
-----------------------------------------------------
method body saw: [Account[1,alice,100], Account[2,bob,200], Account[3,alice,300]]
List.of(..) with TRACE on ALLOWED -> (void)
What @PostFilter accepts as a return type
-----------------------------------------
List<Account> ALLOWED -> [Account[1,alice,100], Account[3,alice,300]]
Account[] ALLOWED -> [Account[1,alice,100], Account[3,alice,300]]
Stream<Account> (collected here) ALLOWED -> [alice, alice]
Map<String, Account> ALLOWED -> {acct-1=Account[1,alice,100], acct-3=Account[3,alice,300]}
Optional<Account> (alice's) DENIED -> IllegalArgumentException: Filter target must be a collection, array, map or stream type, but was Optional[Account[1,alice,100]]
Optional<Account> (bob's) DENIED -> IllegalArgumentException: Filter target must be a collection, array, map or stream type, but was Optional[Account[2,bob,200]]
List.of(..) (immutable return) ALLOWED -> [Account[1,alice,100], Account[3,alice,300]]
Ledger (a type Spring Security does not know) DENIED -> IllegalArgumentException: Filter target must be a collection, array, map or stream type, but was Ledger[Account[1,alice,100], Account[2,bob,200], Account[3,alice,300]]
Page<Account> (real Spring Data PageImpl) DENIED -> IllegalArgumentException: Filter target must be a collection, array, map or stream type, but was Page 1 of 1 containing com.ankurm.methodsec.Account instances
Identity: does @PostFilter hand back the same object?
-----------------------------------------------------
returned == the list the method returned : true
the method's own list, after filtering : [Account[1,alice,100], Account[3,alice,300]]
@PostFilter mutates the returned collection in place and hands the same
reference back. If that collection is a cached or shared instance, you
have just deleted rows from it for every future caller.

View File

@@ -0,0 +1,58 @@
==============================================================================
Demo 6 -- interceptor order, and @PostAuthorize vs @Transactional
==============================================================================
AuthorizationInterceptorsOrder, read from the enum itself
---------------------------------------------------------
CONSTANT getOrder()
FIRST -2147483648
PRE_FILTER 100
PRE_AUTHORIZE 200
SECURED 300
JSR250 400
SECURE_RESULT 450
POST_AUTHORIZE 500
POST_FILTER 600
LAST 2147483647
Lower order = higher precedence = further OUT in the chain. Spring's own
@Transactional advisor defaults to Ordered.LOWEST_PRECEDENCE (2147483647),
which is larger than every number above -- so security wraps transactions,
not the other way round.
SLF4J(W): No SLF4J providers were found.
SLF4J(W): Defaulting to no-operation (NOP) logger implementation
SLF4J(W): See https://www.slf4j.org/codes.html#noProviders for further details.
The advisor chain on a bean carrying all four annotations
---------------------------------------------------------
advisors applied to the proxy: 4
ORDER ADVISOR BEAN (as registered by @EnableMethodSecurity)
100 preFilterAuthorizationMethodInterceptor
200 preAuthorizeAuthorizationMethodInterceptor
450 authorizeReturnObjectMethodInterceptor
500 postAuthorizeAuthorizationMethodInterceptor
600 postFilterAuthorizationMethodInterceptor
(authorizeReturnObject sits at SECURE_RESULT = 450 and is registered
whether or not anything in the app uses @AuthorizeReturnObject.)
For BEFORE advice, a lower order runs earlier: @PreFilter (100) really
does run before @PreAuthorize (200). For AFTER advice the same numbers
mean the opposite. @PostAuthorize (500) sits FURTHER OUT than
@PostFilter (600), so on the way back out @PostFilter finishes first
and @PostAuthorize evaluates returnObject on the ALREADY-FILTERED list.
Both methods below return the same 3 elements and filter one away:
@PostAuthorize returnObject.size() == 3 DENIED -> AuthorizationDeniedException: Access Denied
@PostAuthorize returnObject.size() == 2 ALLOWED -> [a, b]
Default order: @PostAuthorize denies AFTER the transaction commits
------------------------------------------------------------------
rows before : 0
recordAndReturn("bob") @PostAuthorize DENIED -> AuthorizationDeniedException: Access Denied
rows after the denial : 1
@EnableTransactionManagement(order = FIRST): the write rolls back
-----------------------------------------------------------------
rows before : 0
recordAndReturn("bob") @PostAuthorize DENIED -> AuthorizationDeniedException: Access Denied
rows after the denial : 0

View File

@@ -0,0 +1,34 @@
==============================================================================
Demo 7 -- @HandleAuthorizationDenied and @AuthorizeReturnObject
==============================================================================
SLF4J(W): No SLF4J providers were found.
SLF4J(W): Defaulting to no-operation (NOP) logger implementation
SLF4J(W): See https://www.slf4j.org/codes.html#noProviders for further details.
The exception type you actually catch
-------------------------------------
thrown -> org.springframework.security.authorization.AuthorizationDeniedException
is AccessDeniedException -> true
is AuthorizationDeniedException -> true
carries an AuthorizationResult -> ExpressionAuthorizationDecision granted=false
Handlers written against AccessDeniedException still work -- but the
concrete type carries the AuthorizationResult that explains the denial.
@HandleAuthorizationDenied: return something instead of throwing
----------------------------------------------------------------
maskedBalance() (alice, no ROLE_FINANCE) ALLOWED -> ***masked***
maskedList() (alice, no ROLE_FINANCE) ALLOWED -> []
maskedBalance() (cfo, has ROLE_FINANCE) ALLOWED -> 1,204,993.22
@AuthorizeReturnObject: the check moves onto the returned object
----------------------------------------------------------------
returned instance -> com.ankurm.methodsec.Demo7DeniedHandling$Customer$$SpringCGLIB$$0
customer.getName() (no authority needed) ALLOWED -> alice
customer.getEmail() (needs 'pii:read') DENIED -> AuthorizationDeniedException: Access Denied
customer.getEmail() (has 'pii:read') ALLOWED -> alice@example.com
Same thing without the annotation, via AuthorizationProxyFactory
----------------------------------------------------------------
raw.getEmail() (unproxied object) ALLOWED -> alice@example.com
wrapped.getEmail() (proxied object) DENIED -> AuthorizationDeniedException: Access Denied

View File

@@ -0,0 +1,47 @@
==============================================================================
Demo 8 -- meta-annotations, templates, class-level rules, ambiguity
==============================================================================
A plain meta-annotation needs no extra configuration
----------------------------------------------------
SLF4J(W): No SLF4J providers were found.
SLF4J(W): Defaulting to no-operation (NOP) logger implementation
SLF4J(W): See https://www.slf4j.org/codes.html#noProviders for further details.
@IsAdmin (alice, ROLE_USER) DENIED -> AuthorizationDeniedException: Access Denied
@IsAdmin (root, ROLE_ADMIN) ALLOWED -> ok
A TEMPLATED meta-annotation, with NO AnnotationTemplateExpressionDefaults bean
------------------------------------------------------------------------------
@HasRole("ADMIN") as root (ROLE_ADMIN) ALLOWED -> ok
@HasRole("ADMIN") as alice (ROLE_USER) DENIED -> AuthorizationDeniedException: Access Denied
'{value}' was substituted anyway. The reference documentation says you
must publish an AnnotationTemplateExpressionDefaults bean for templated
meta-annotations to work; in 7.1.1 you do not.
PreAuthorizeExpressionAttributeRegistry initialises its scanner with
SecurityAnnotationScanners.requireUnique(PreAuthorize.class), and that
overload constructs a default AnnotationTemplateExpressionDefaults for
you. Publishing the bean only changes ignoreUnknown.
The same annotation WITH the AnnotationTemplateExpressionDefaults bean
----------------------------------------------------------------------
@HasRole("ADMIN") as root (ROLE_ADMIN) ALLOWED -> ok
@HasRole("ADMIN") as alice (ROLE_USER) DENIED -> AuthorizationDeniedException: Access Denied
@HasRole("USER") as alice (ROLE_USER) ALLOWED -> ok
Class-level rules, and what a method-level one does to them
-----------------------------------------------------------
inherited from the class (needs ADMIN) DENIED -> AuthorizationDeniedException: Access Denied
method-level overrides it (needs USER) ALLOWED -> ok
class @PreAuthorize AND method @PostAuthorize DENIED -> AuthorizationDeniedException: Access Denied
Two interfaces, two different @PreAuthorize on the same method
--------------------------------------------------------------
context started fine.
bean type -> jdk.proxy2.$Proxy21
read() -- inherits two conflicting rules DENIED -> AnnotationConfigurationException: Please ensure there is one unique annotation of type [interface org.springframework.security.access.prepost.PreAuthorize] attributed to public abstract java.lang.String com.ankurm.methodsec.Demo8MetaAnnotations$ReadsAsUser.read(). Found 2 competing annotations: [@org.springframework.security.access.prepost.PreAuthorize("hasRole('USER')"), @org.springframework.security.access.prepost.PreAuthorize("hasRole('ADMIN')")]
It is not a startup failure: the context refreshes, the bean is
proxied, and the conflict only surfaces when the method is called.
The fix is to put @PreAuthorize on the implementation method, which
is the nearest declaration and therefore wins outright.

View File

@@ -0,0 +1,24 @@
==============================================================================
Demo 9 -- #parameterName and the -parameters compiler flag
==============================================================================
compiled with -parameters : true
byParameterName param[0] : owner
byParameterAlias param[0] : owner (annotated @P("o"))
SLF4J(W): No SLF4J providers were found.
SLF4J(W): Defaulting to no-operation (NOP) logger implementation
SLF4J(W): See https://www.slf4j.org/codes.html#noProviders for further details.
alice calling with her own name
-------------------------------
#owner == authentication.name ALLOWED -> ok
#o == authentication.name (@P("o")) ALLOWED -> ok
alice calling with somebody else's name
---------------------------------------
#owner == authentication.name DENIED -> AuthorizationDeniedException: Access Denied
#o == authentication.name (@P("o")) DENIED -> AuthorizationDeniedException: Access Denied
Without -parameters the first expression denies BOTH calls -- it fails
closed, which is the good direction, but it fails silently in the sense
that nothing tells you the rule is not the rule you wrote. @P("o") does
not depend on the flag, because the name is in the class file either way.

View File

@@ -0,0 +1,24 @@
==============================================================================
Demo 9 -- #parameterName and the -parameters compiler flag
==============================================================================
compiled with -parameters : false
byParameterName param[0] : arg0
byParameterAlias param[0] : arg0 (annotated @P("o"))
SLF4J(W): No SLF4J providers were found.
SLF4J(W): Defaulting to no-operation (NOP) logger implementation
SLF4J(W): See https://www.slf4j.org/codes.html#noProviders for further details.
alice calling with her own name
-------------------------------
#owner == authentication.name DENIED -> AuthorizationDeniedException: Access Denied
#o == authentication.name (@P("o")) ALLOWED -> ok
alice calling with somebody else's name
---------------------------------------
#owner == authentication.name DENIED -> AuthorizationDeniedException: Access Denied
#o == authentication.name (@P("o")) DENIED -> AuthorizationDeniedException: Access Denied
Without -parameters the first expression denies BOTH calls -- it fails
closed, which is the good direction, but it fails silently in the sense
that nothing tells you the rule is not the rule you wrote. @P("o") does
not depend on the flag, because the name is in the class file either way.

View File

@@ -0,0 +1,6 @@
mvn test -- MethodSecurityTrapsTest (14 tests pinning every claim the demos print)
-------------------------------------------------------------------------------
Test set: com.ankurm.methodsec.MethodSecurityTrapsTest
-------------------------------------------------------------------------------
Tests run: 14, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 1.076 s -- in com.ankurm.methodsec.MethodSecurityTrapsTest