Skip to content

Exclude anonymous from additionalAuthorization#19335

Open
junhyeong9812 wants to merge 1 commit into
spring-projects:mainfrom
junhyeong9812:fix/authorization-factory-anonymous-additional
Open

Exclude anonymous from additionalAuthorization#19335
junhyeong9812 wants to merge 1 commit into
spring-projects:mainfrom
junhyeong9812:fix/authorization-factory-anonymous-additional

Conversation

@junhyeong9812

Copy link
Copy Markdown

Overview

DefaultAuthorizationManagerFactory.anonymous() applies the configured additionalAuthorization, contradicting the documented contract of setAdditionalAuthorization(...):

This does not affect anonymous, permitAll, or denyAll.

Problem

anonymous() routes through the same createManager(AuthenticatedAuthorizationManager) path as authenticated(), fullyAuthenticated(), and rememberMe(), which wraps the manager via withAdditionalAuthorization(...):

return AuthorizationManagers.allOf(new AuthorizationDecision(false), this.additionalAuthorization, manager);

permitAll() and denyAll() are inherited interface defaults and never go through this path, so they correctly stay unaffected. anonymous() does not.

As a result, when an additionalAuthorization is configured (for example via AuthorizationManagerFactories.multiFactor().requireFactors(...)), an anonymous request is denied because an anonymous user holds none of the required factors — even though the Javadoc promises anonymous is unaffected.

The existing test anonymousWhenAdditionalAuthorizationThenNotInvoked only called factory.anonymous() and asserted verifyNoInteractions(...). Because the allOf wrapping is lazy, that assertion passed at construction time even with the bug, so the regression was not caught.

Fix

Have anonymous() construct the AuthenticatedAuthorizationManager directly (still setting the trustResolver) and return it without withAdditionalAuthorization, mirroring how permitAll()/denyAll() stay unaffected. authenticated(), fullyAuthenticated(), and rememberMe() continue to apply additionalAuthorization.

The existing test is strengthened to actually authorize(...) an anonymous authentication and assert it is granted, which fails on the previous code.

Note on impact

This is a behavior change in the documented direction: an endpoint guarded only by anonymous() will now allow anonymous principals even when a configured additionalAuthorization (e.g. an MFA/IP/tenant gate) would fail. This matches the documented contract. Callers that intentionally want both conditions should compose them explicitly, e.g. allOf(factory.anonymous(), extraCondition). anonymous() still denies authenticated users, so anonymous-only endpoints do not become open to logged-in users.

Closes gh-19334

DefaultAuthorizationManagerFactory.anonymous() routed through the same
createManager path as authenticated(), so additionalAuthorization was
applied to it, contradicting the documented contract that it does not
affect anonymous, permitAll, or denyAll. Return the anonymous manager
directly so the contract holds.

Closes spring-projectsgh-19334

Signed-off-by: junhyeong9812 <pickjog@gmail.com>
@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged label Jun 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

status: waiting-for-triage An issue we've not yet triaged

Projects

None yet

Development

Successfully merging this pull request may close these issues.

DefaultAuthorizationManagerFactory.anonymous() applies additionalAuthorization

2 participants