Problem Description
The current interaction between URL-based security (requestMatchers()
) and method-level annotations (@PermitAll
, @PreAuthorize
) creates confusion regarding precedence rules. Specifically:
-
Unexpected Behavior:
When a path is secured viarequestMatchers("/api/**").authenticated()
, adding@PermitAll
on a controller method does not override the URL-level restriction, contrary to what many developers expect . -
Documentation Gap:
The official documentation doesn't clearly state thatrequestMatchers()
takes precedence over method annotations .
Proposed Solutions
Option 1: Behavior Change (Breaking Change)
Make method-level annotations override URL configurations when:
-
Add new
@OverrideSecurityConfig
meta-annotation -
Or introduce explicit configuration:
java
@Configuration
@EnableMethodSecurity(overrideUrlSecurity = true) // New flag
Option 2: Documentation Enhancement
Clearly document the current precedence rules with warning notes:
**Security Configuration Precedence**:
1. URL patterns (`requestMatchers()`)
2. Method annotations (`@PreAuthorize`, `@PermitAll`)
Important: Method annotations cannot override URL-level `authenticated()`/`denyAll()`.
Use `@PermitAll` only when the URL config is `permitAll()`.
Option 3: Hybrid Approach
Introduce a new annotation @SecurityOverride
that explicitly declares intent:
@PermitAll
@SecurityOverride // Explicitly declares overriding URL config
@GetMapping("/api/public")
public String publicApi() { ... }
Why This Matters
-
Developer Experience:
Most developers expect method annotations to naturally override coarser URL configurations . -
Security Pitfalls:
The current behavior may lead to accidental exposure of endpoints when developers rely on@PermitAll
that doesn't actually work . -
Consistency:
Other security frameworks (e.g., Apache Shiro) allow method-level rules to override path-based rules.
Current Workaround
The only way to achieve method-level overriding today is through custom implementation:
@Component
public class CustomAuthManager implements AuthorizationManager<MethodInvocation> {
@Override
public AuthorizationDecision check(Supplier<Authentication> auth, MethodInvocation method) {
// Custom logic to check annotations first
}
}
This is unnecessarily complex for a common use case .