Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add SingleResultAuthorizationManager #16612

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -33,6 +33,7 @@
import org.springframework.security.authorization.AuthorizationEventPublisher;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.authorization.AuthorizationManagers;
import org.springframework.security.authorization.SingleResultAuthorizationManager;
import org.springframework.security.authorization.SpringAuthorizationEventPublisher;
import org.springframework.security.config.ObjectPostProcessor;
import org.springframework.security.config.annotation.web.AbstractRequestMatcherRegistry;
Expand All @@ -57,11 +58,6 @@
public final class AuthorizeHttpRequestsConfigurer<H extends HttpSecurityBuilder<H>>
extends AbstractHttpConfigurer<AuthorizeHttpRequestsConfigurer<H>, H> {

static final AuthorizationDecision AUTHORIZATION_DECISION = new AuthorizationDecision(true);

static final AuthorizationManager<RequestAuthorizationContext> PERMIT_ALL_AUTHORIZATION_MANAGER = (a,
o) -> AUTHORIZATION_DECISION;

private final AuthorizationManagerRequestMatcherRegistry registry;

private final AuthorizationEventPublisher publisher;
Expand Down Expand Up @@ -289,7 +285,7 @@ public AuthorizedUrl not() {
* customizations
*/
public AuthorizationManagerRequestMatcherRegistry permitAll() {
return access(PERMIT_ALL_AUTHORIZATION_MANAGER);
return access(SingleResultAuthorizationManager.PERMIT_ALL());
}

/**
Expand All @@ -298,7 +294,7 @@ public AuthorizationManagerRequestMatcherRegistry permitAll() {
* customizations
*/
public AuthorizationManagerRequestMatcherRegistry denyAll() {
return access((a, o) -> new AuthorizationDecision(false));
return access(SingleResultAuthorizationManager.DENY_ALL());
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -19,6 +19,7 @@
import jakarta.servlet.http.HttpServletRequest;

import org.springframework.security.access.SecurityConfig;
import org.springframework.security.authorization.SingleResultAuthorizationManager;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.config.annotation.web.configurers.AbstractConfigAttributeRequestMatcherRegistry.UrlMapping;
import org.springframework.security.web.util.matcher.RequestMatcher;
Expand Down Expand Up @@ -63,7 +64,7 @@ static void permitAll(HttpSecurityBuilder<? extends HttpSecurityBuilder<?>> http
SecurityConfig.createList(ExpressionUrlAuthorizationConfigurer.permitAll)));
}
else {
httpConfigurer.addFirst(matcher, AuthorizeHttpRequestsConfigurer.PERMIT_ALL_AUTHORIZATION_MANAGER);
httpConfigurer.addFirst(matcher, SingleResultAuthorizationManager.PERMIT_ALL());
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.springframework.security.authorization;

import java.util.function.Supplier;

import org.springframework.security.core.Authentication;

/**
* An {@link AuthorizationManager} which creates permit-all and deny-all
* {@link AuthorizationManager} instances.
*
* @author Max Batischev
* @since 6.5
*/
public final class SingleResultAuthorizationManager<C> implements AuthorizationManager<C> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add tests for the class.


private static final AuthorizationDecision DENY = new AuthorizationDecision(false);

private static final AuthorizationDecision PERMIT = new AuthorizationDecision(true);

/**
* Creates permit-all {@link AuthorizationManager} instance.
* @param <C>
* @return permit-all {@link AuthorizationManager} instance
*/
public static <C> AuthorizationManager<C> PERMIT_ALL() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Spring Security conventionally uses camelCase for methods, even ones that return a constant; please change this to permitAll().

return (a, o) -> PERMIT;
}

/**
* Creates deny-all {@link AuthorizationManager} instance.
* @param <C>
* @return deny-all {@link AuthorizationManager} instance
*/
public static <C> AuthorizationManager<C> DENY_ALL() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Spring Security conventionally uses camelCase for methods, even ones that return a constant; please change this to denyAll().

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should return the concrete type. Just like constructors emit the concrete type. Can you please change both method to return SingleResultAuthorizationManager?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, we can't do this, java uses LambdaMetafactory for lambda creation. This factory creates CallSite object, which returns intenface implementation. Therefore these methods must return an interface.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, you can ignore this comment :)

return (a, o) -> DENY;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of a lambda, consider returning a static final instance of SingleResultAuthorizationManager.

}

private SingleResultAuthorizationManager() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While having this for permitAll and denyAll is great, we can expand it by taking an AuthorizationResult in the constructor and having it be public.

}

@Override
public AuthorizationDecision check(Supplier<Authentication> authentication, C object) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's please implement authorize, returning whatever single result was set in the constructor.

throw new UnsupportedOperationException("Not supported");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's still implement this. If the private member result isn't of type AuthenticationDecision, we can throw and exception.

}

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -34,6 +34,7 @@
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.authorization.AuthorizationResult;
import org.springframework.security.authorization.SingleResultAuthorizationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.annotation.SecurityAnnotationScanner;
import org.springframework.security.core.annotation.SecurityAnnotationScanners;
Expand Down Expand Up @@ -106,10 +107,10 @@ private final class Jsr250AuthorizationManagerRegistry extends AbstractAuthoriza
AuthorizationManager<MethodInvocation> resolveManager(Method method, Class<?> targetClass) {
Annotation annotation = findJsr250Annotation(method, targetClass);
if (annotation instanceof DenyAll) {
return (a, o) -> new AuthorizationDecision(false);
return SingleResultAuthorizationManager.DENY_ALL();
}
if (annotation instanceof PermitAll) {
return (a, o) -> new AuthorizationDecision(true);
return SingleResultAuthorizationManager.PERMIT_ALL();
}
if (annotation instanceof RolesAllowed rolesAllowed) {
return (AuthorizationManagerCheckAdapter<MethodInvocation>) (a,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -33,12 +33,10 @@ public class AuthorizationManagerTests {

@Test
public void verifyWhenCheckReturnedGrantedDecisionThenPasses() {
AuthorizationManager<Object> manager = (a, o) -> new AuthorizationDecision(true);

Authentication authentication = new TestingAuthenticationToken("user", "password", "ROLE_1", "ROLE_2");
Object object = new Object();

manager.verify(() -> authentication, object);
SingleResultAuthorizationManager.PERMIT_ALL().verify(() -> authentication, object);
}

@Test
Expand All @@ -53,13 +51,11 @@ public void verifyWhenCheckReturnedNullThenPasses() {

@Test
public void verifyWhenCheckReturnedDeniedDecisionThenAccessDeniedException() {
AuthorizationManager<Object> manager = (a, o) -> new AuthorizationDecision(false);

Authentication authentication = new TestingAuthenticationToken("user", "password", "ROLE_1", "ROLE_2");
Object object = new Object();

assertThatExceptionOfType(AccessDeniedException.class)
.isThrownBy(() -> manager.verify(() -> authentication, object))
.isThrownBy(() -> SingleResultAuthorizationManager.DENY_ALL().verify(() -> authentication, object))
.withMessage("Access Denied");
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -29,8 +29,8 @@ class AuthorizationManagersTests {

@Test
void checkAnyOfWhenOneGrantedThenGrantedDecision() {
AuthorizationManager<?> composed = AuthorizationManagers.anyOf((a, o) -> new AuthorizationDecision(false),
(a, o) -> new AuthorizationDecision(true));
AuthorizationManager<?> composed = AuthorizationManagers.anyOf(SingleResultAuthorizationManager.DENY_ALL(),
SingleResultAuthorizationManager.PERMIT_ALL());
AuthorizationDecision decision = composed.check(null, null);
assertThat(decision).isNotNull();
assertThat(decision.isGranted()).isTrue();
Expand Down Expand Up @@ -118,8 +118,8 @@ void checkAnyOfWhenAllAbstainDefaultDecisionIsAbstainAndAllManagersAbstainThenAb

@Test
void checkAllOfWhenAllGrantedThenGrantedDecision() {
AuthorizationManager<?> composed = AuthorizationManagers.allOf((a, o) -> new AuthorizationDecision(true),
(a, o) -> new AuthorizationDecision(true));
AuthorizationManager<?> composed = AuthorizationManagers.allOf(SingleResultAuthorizationManager.PERMIT_ALL(),
SingleResultAuthorizationManager.PERMIT_ALL());
AuthorizationDecision decision = composed.check(null, null);
assertThat(decision).isNotNull();
assertThat(decision.isGranted()).isTrue();
Expand Down Expand Up @@ -158,7 +158,7 @@ void checkAllOfWhenOneDeniedThenDeniedDecision() {
void checkAllOfWithAllAbstainDefaultDecisionWhenOneDeniedThenDeniedDecision() {
AuthorizationDecision allAbstainDefaultDecision = new AuthorizationDecision(true);
AuthorizationManager<?> composed = AuthorizationManagers.allOf(allAbstainDefaultDecision,
(a, o) -> new AuthorizationDecision(true), (a, o) -> new AuthorizationDecision(false));
SingleResultAuthorizationManager.PERMIT_ALL(), SingleResultAuthorizationManager.DENY_ALL());
AuthorizationDecision decision = composed.check(null, null);
assertThat(decision).isNotNull();
assertThat(decision.isGranted()).isFalse();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -31,6 +31,7 @@
import org.springframework.security.authorization.AuthorityAuthorizationManager;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.authorization.SingleResultAuthorizationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.messaging.util.matcher.MessageMatcher;
import org.springframework.security.messaging.util.matcher.SimpDestinationMessageMatcher;
Expand Down Expand Up @@ -319,15 +320,15 @@ public Builder hasAnyAuthority(String... authorities) {
* @return the {@link Builder} for further customization
*/
public Builder permitAll() {
return access((authentication, context) -> new AuthorizationDecision(true));
return access(SingleResultAuthorizationManager.PERMIT_ALL());
}

/**
* Specify that Messages are not allowed by anyone.
* @return the {@link Builder} for further customization
*/
public Builder denyAll() {
return access((authorization, context) -> new AuthorizationDecision(false));
return access(SingleResultAuthorizationManager.DENY_ALL());
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -30,6 +30,7 @@
import org.springframework.security.authorization.AuthorityAuthorizationManager;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.authorization.SingleResultAuthorizationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.util.UrlUtils;
import org.springframework.security.web.util.matcher.AnyRequestMatcher;
Expand Down Expand Up @@ -201,15 +202,15 @@ private AuthorizedUrl(List<RequestMatcher> matchers) {
* @return the {@link Builder} for further customizations
*/
public Builder permitAll() {
return access((a, o) -> new AuthorizationDecision(true));
return access(SingleResultAuthorizationManager.PERMIT_ALL());
}

/**
* Specify that URLs are not allowed by anyone.
* @return the {@link Builder} for further customizations
*/
public Builder denyAll() {
return access((a, o) -> new AuthorizationDecision(false));
return access(SingleResultAuthorizationManager.DENY_ALL());
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -26,6 +26,7 @@
import org.springframework.security.authorization.AuthenticatedAuthorizationManager;
import org.springframework.security.authorization.AuthorityAuthorizationManager;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.SingleResultAuthorizationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
Expand Down Expand Up @@ -55,7 +56,7 @@ public void buildWhenMappingsEmptyThenException() {
public void addWhenMatcherNullThenException() {
assertThatIllegalArgumentException()
.isThrownBy(() -> RequestMatcherDelegatingAuthorizationManager.builder()
.add(null, (a, o) -> new AuthorizationDecision(true))
.add(null, SingleResultAuthorizationManager.PERMIT_ALL())
.build())
.withMessage("matcher cannot be null");
}
Expand All @@ -72,8 +73,8 @@ public void addWhenManagerNullThenException() {
@Test
public void checkWhenMultipleMappingsConfiguredThenDelegatesMatchingManager() {
RequestMatcherDelegatingAuthorizationManager manager = RequestMatcherDelegatingAuthorizationManager.builder()
.add(new MvcRequestMatcher(null, "/grant"), (a, o) -> new AuthorizationDecision(true))
.add(new MvcRequestMatcher(null, "/deny"), (a, o) -> new AuthorizationDecision(false))
.add(new MvcRequestMatcher(null, "/grant"), SingleResultAuthorizationManager.PERMIT_ALL())
.add(new MvcRequestMatcher(null, "/deny"), SingleResultAuthorizationManager.DENY_ALL())
.build();

Supplier<Authentication> authentication = () -> new TestingAuthenticationToken("user", "password", "ROLE_USER");
Expand All @@ -97,11 +98,11 @@ public void checkWhenMultipleMappingsConfiguredWithConsumerThenDelegatesMatching
RequestMatcherDelegatingAuthorizationManager manager = RequestMatcherDelegatingAuthorizationManager.builder()
.mappings((m) -> {
m.add(new RequestMatcherEntry<>(new MvcRequestMatcher(null, "/grant"),
(a, o) -> new AuthorizationDecision(true)));
SingleResultAuthorizationManager.PERMIT_ALL()));
m.add(new RequestMatcherEntry<>(AnyRequestMatcher.INSTANCE,
AuthorityAuthorizationManager.hasRole("ADMIN")));
m.add(new RequestMatcherEntry<>(new MvcRequestMatcher(null, "/afterAny"),
(a, o) -> new AuthorizationDecision(true)));
SingleResultAuthorizationManager.PERMIT_ALL()));
})
.build();

Expand Down