Add AllAuthoritiesReactiveAuthorizationManager
Issue gh-17916
This commit is contained in:
parent
096dfd4046
commit
bb6b8ae3f3
|
|
@ -18,6 +18,7 @@ package org.springframework.security.authorization;
|
|||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
|
|
@ -36,7 +37,7 @@ import org.springframework.util.Assert;
|
|||
*
|
||||
* @author Rob Winch
|
||||
* @since 7.0
|
||||
* @see AuthoritiesAuthorizationManager
|
||||
* @see AuthorityAuthorizationManager
|
||||
*/
|
||||
public final class AllAuthoritiesAuthorizationManager<T> implements AuthorizationManager<T> {
|
||||
|
||||
|
|
@ -83,6 +84,9 @@ public final class AllAuthoritiesAuthorizationManager<T> implements Authorizatio
|
|||
}
|
||||
|
||||
private List<String> getGrantedAuthorities(Authentication authentication) {
|
||||
if (authentication == null || !authentication.isAuthenticated()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return this.roleHierarchy.getReachableGrantedAuthorities(authentication.getAuthorities())
|
||||
.stream()
|
||||
.map(GrantedAuthority::getAuthority)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,158 @@
|
|||
/*
|
||||
* Copyright 2004-present 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.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.security.access.hierarchicalroles.NullRoleHierarchy;
|
||||
import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.authority.AuthorityUtils;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A {@link ReactiveAuthorizationManager} that determines if the current user is
|
||||
* authorized by evaluating if the {@link Authentication} contains all the specified
|
||||
* authorities.
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @since 7.0
|
||||
* @see AuthorityReactiveAuthorizationManager
|
||||
*/
|
||||
public final class AllAuthoritiesReactiveAuthorizationManager<T> implements ReactiveAuthorizationManager<T> {
|
||||
|
||||
private static final String ROLE_PREFIX = "ROLE_";
|
||||
|
||||
private RoleHierarchy roleHierarchy = new NullRoleHierarchy();
|
||||
|
||||
private final List<String> requiredAuthorities;
|
||||
|
||||
private final AuthorityAuthorizationDecision defaultDecision;
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
* @param requiredAuthorities the authorities that are required.
|
||||
*/
|
||||
private AllAuthoritiesReactiveAuthorizationManager(String... requiredAuthorities) {
|
||||
Assert.notEmpty(requiredAuthorities, "requiredAuthorities cannot be empty");
|
||||
this.requiredAuthorities = Arrays.asList(requiredAuthorities);
|
||||
this.defaultDecision = new AuthorityAuthorizationDecision(false,
|
||||
AuthorityUtils.createAuthorityList(this.requiredAuthorities));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link RoleHierarchy} to be used. Default is {@link NullRoleHierarchy}.
|
||||
* Cannot be null.
|
||||
* @param roleHierarchy the {@link RoleHierarchy} to use
|
||||
*/
|
||||
public void setRoleHierarchy(RoleHierarchy roleHierarchy) {
|
||||
Assert.notNull(roleHierarchy, "roleHierarchy cannot be null");
|
||||
this.roleHierarchy = roleHierarchy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the current user is authorized by evaluating if the
|
||||
* {@link Authentication} contains any of specified authorities.
|
||||
* @param authentication the {@link Supplier} of the {@link Authentication} to check
|
||||
* @param object the object to check authorization on (not used).
|
||||
* @return an {@link AuthorityAuthorizationDecision}
|
||||
*/
|
||||
@Override
|
||||
public Mono<AuthorizationResult> authorize(Mono<Authentication> authentication, T object) {
|
||||
// @formatter:off
|
||||
return authentication
|
||||
.filter(Authentication::isAuthenticated)
|
||||
.map(this::getGrantedAuthorities)
|
||||
.defaultIfEmpty(Collections.emptyList())
|
||||
.map((authenticatedAuthorities) -> {
|
||||
List<String> missingAuthorities = new ArrayList<>(this.requiredAuthorities);
|
||||
missingAuthorities.removeIf(authenticatedAuthorities::contains);
|
||||
return new AuthorityAuthorizationDecision(missingAuthorities.isEmpty(),
|
||||
AuthorityUtils.createAuthorityList(missingAuthorities));
|
||||
});
|
||||
// @formatter:on
|
||||
|
||||
}
|
||||
|
||||
private List<String> getGrantedAuthorities(Authentication authentication) {
|
||||
return this.roleHierarchy.getReachableGrantedAuthorities(authentication.getAuthorities())
|
||||
.stream()
|
||||
.map(GrantedAuthority::getAuthority)
|
||||
.toList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance of {@link AllAuthoritiesReactiveAuthorizationManager} with the
|
||||
* provided authorities.
|
||||
* @param roles the authorities to check for prefixed with "ROLE_". Each role should
|
||||
* not start with "ROLE_" since it is automatically prepended already.
|
||||
* @param <T> the type of object being authorized
|
||||
* @return the new instance
|
||||
*/
|
||||
public static <T> AllAuthoritiesReactiveAuthorizationManager<T> hasAllRoles(String... roles) {
|
||||
return hasAllPrefixedAuthorities(ROLE_PREFIX, roles);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance of {@link AllAuthoritiesReactiveAuthorizationManager} with the
|
||||
* provided authorities.
|
||||
* @param prefix the prefix for <code>authorities</code>
|
||||
* @param authorities the authorities to check for prefixed with <code>prefix</code>
|
||||
* @param <T> the type of object being authorized
|
||||
* @return the new instance
|
||||
*/
|
||||
public static <T> AllAuthoritiesReactiveAuthorizationManager<T> hasAllPrefixedAuthorities(String prefix,
|
||||
String... authorities) {
|
||||
Assert.notNull(prefix, "rolePrefix cannot be null");
|
||||
Assert.notEmpty(authorities, "roles cannot be empty");
|
||||
Assert.noNullElements(authorities, "roles cannot contain null values");
|
||||
return hasAllAuthorities(toNamedRolesArray(prefix, authorities));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance of {@link AllAuthoritiesReactiveAuthorizationManager} with the
|
||||
* provided authorities.
|
||||
* @param authorities the authorities to check for
|
||||
* @param <T> the type of object being authorized
|
||||
* @return the new instance
|
||||
*/
|
||||
public static <T> AllAuthoritiesReactiveAuthorizationManager<T> hasAllAuthorities(String... authorities) {
|
||||
Assert.notEmpty(authorities, "authorities cannot be empty");
|
||||
Assert.noNullElements(authorities, "authorities cannot contain null values");
|
||||
return new AllAuthoritiesReactiveAuthorizationManager<>(authorities);
|
||||
}
|
||||
|
||||
private static String[] toNamedRolesArray(String rolePrefix, String[] roles) {
|
||||
String[] result = new String[roles.length];
|
||||
for (int i = 0; i < roles.length; i++) {
|
||||
String role = roles[i];
|
||||
Assert.isTrue(rolePrefix.isEmpty() || !role.startsWith(rolePrefix), () -> role + " should not start with "
|
||||
+ rolePrefix + " since " + rolePrefix
|
||||
+ " is automatically prepended when using hasAnyRole. Consider using hasAnyAuthority instead.");
|
||||
result[i] = rolePrefix + role;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -34,7 +34,6 @@ import org.springframework.util.Assert;
|
|||
*
|
||||
* @author Evgeniy Cheban
|
||||
* @since 6.1
|
||||
* @see AllAuthoritiesAuthorizationManager
|
||||
*/
|
||||
public final class AuthoritiesAuthorizationManager implements AuthorizationManager<Collection<String>> {
|
||||
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ import org.springframework.util.Assert;
|
|||
* @param <T> the type of object being authorized.
|
||||
* @author Evgeniy Cheban
|
||||
* @since 5.5
|
||||
* @see AllAuthoritiesAuthorizationManager
|
||||
*/
|
||||
public final class AuthorityAuthorizationManager<T> implements AuthorizationManager<T> {
|
||||
|
||||
|
|
|
|||
|
|
@ -67,14 +67,24 @@ class AllAuthoritiesAuthorizationManagerTests {
|
|||
@Test
|
||||
void authorizeWhenGranted() {
|
||||
Authentication authentication = new TestingAuthenticationToken("user", "password", ROLE_USER);
|
||||
AllAuthoritiesAuthorizationManager manager = AllAuthoritiesAuthorizationManager.hasAllAuthorities(ROLE_USER);
|
||||
AllAuthoritiesAuthorizationManager<Object> manager = AllAuthoritiesAuthorizationManager
|
||||
.hasAllAuthorities(ROLE_USER);
|
||||
assertThat(manager.authorize(() -> authentication, "").isGranted()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void authorizeWhenNotAuthenticated() {
|
||||
Authentication authentication = new TestingAuthenticationToken("user", "password", ROLE_USER);
|
||||
authentication.setAuthenticated(false);
|
||||
AllAuthoritiesAuthorizationManager<Object> manager = AllAuthoritiesAuthorizationManager
|
||||
.hasAllAuthorities(ROLE_USER);
|
||||
assertThat(manager.authorize(() -> authentication, "").isGranted()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void hasAllRolesAuthorizeWhenGranted() {
|
||||
Authentication authentication = new TestingAuthenticationToken("user", "password", ROLE_USER);
|
||||
AllAuthoritiesAuthorizationManager manager = AllAuthoritiesAuthorizationManager.hasAllRoles("USER");
|
||||
AllAuthoritiesAuthorizationManager<Object> manager = AllAuthoritiesAuthorizationManager.hasAllRoles("USER");
|
||||
assertThat(manager.authorize(() -> authentication, "").isGranted()).isTrue();
|
||||
}
|
||||
|
||||
|
|
@ -85,7 +95,7 @@ class AllAuthoritiesAuthorizationManagerTests {
|
|||
String authority2 = "AUTHORITY2";
|
||||
Authentication authentication = new TestingAuthenticationToken("user", "password", prefix + authority1,
|
||||
prefix + authority2);
|
||||
AllAuthoritiesAuthorizationManager manager = AllAuthoritiesAuthorizationManager
|
||||
AllAuthoritiesAuthorizationManager<Object> manager = AllAuthoritiesAuthorizationManager
|
||||
.hasAllPrefixedAuthorities(prefix, authority1, authority2);
|
||||
assertThat(manager.authorize(() -> authentication, "").isGranted()).isTrue();
|
||||
}
|
||||
|
|
@ -93,15 +103,16 @@ class AllAuthoritiesAuthorizationManagerTests {
|
|||
@Test
|
||||
void authorizeWhenSingleMissingThenDenied() {
|
||||
Authentication authentication = new TestingAuthenticationToken("user", "password", ROLE_USER);
|
||||
AllAuthoritiesAuthorizationManager manager = AllAuthoritiesAuthorizationManager.hasAllAuthorities(ROLE_ADMIN);
|
||||
AllAuthoritiesAuthorizationManager<Object> manager = AllAuthoritiesAuthorizationManager
|
||||
.hasAllAuthorities(ROLE_ADMIN);
|
||||
assertThat(manager.authorize(() -> authentication, "").isGranted()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void authorizeWhenMultipleMissingOneThenDenied() {
|
||||
Authentication authentication = new TestingAuthenticationToken("user", "password", ROLE_USER);
|
||||
AllAuthoritiesAuthorizationManager manager = AllAuthoritiesAuthorizationManager.hasAllAuthorities(ROLE_ADMIN,
|
||||
ROLE_USER);
|
||||
AllAuthoritiesAuthorizationManager<Object> manager = AllAuthoritiesAuthorizationManager
|
||||
.hasAllAuthorities(ROLE_ADMIN, ROLE_USER);
|
||||
AuthorityAuthorizationDecision result = manager.authorize(() -> authentication, "");
|
||||
assertThat(result.isGranted()).isFalse();
|
||||
assertThat(result.getAuthorities()).hasSize(1);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,151 @@
|
|||
/*
|
||||
* Copyright 2004-present 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.ArrayList;
|
||||
import java.util.Collection;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Captor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
|
||||
import org.springframework.security.authentication.TestingAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.authority.AuthorityUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class AllAuthoritiesReactiveAuthorizationManagerTests {
|
||||
|
||||
public static final String ROLE_USER = "ROLE_USER";
|
||||
|
||||
public static final String ROLE_ADMIN = "ROLE_ADMIN";
|
||||
|
||||
@Mock
|
||||
private RoleHierarchy roleHierarchy;
|
||||
|
||||
@Captor
|
||||
private ArgumentCaptor<Collection<? extends GrantedAuthority>> authoritiesCaptor;
|
||||
|
||||
@Test
|
||||
void hasAllAuthoritiesWhenNullAuthoritiesThenIllegalArgumentException() {
|
||||
String[] requiredAuthorities = null;
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> AllAuthoritiesReactiveAuthorizationManager.hasAllAuthorities(requiredAuthorities));
|
||||
}
|
||||
|
||||
@Test
|
||||
void hasAllAuthortiesWhenEmptyAuthoritiesThenIllegalArgumentException() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> AllAuthoritiesReactiveAuthorizationManager.hasAllAuthorities((new String[0])));
|
||||
}
|
||||
|
||||
@Test
|
||||
void authorizeWhenGranted() {
|
||||
Authentication authentication = new TestingAuthenticationToken("user", "password", ROLE_USER);
|
||||
AllAuthoritiesReactiveAuthorizationManager<Object> manager = AllAuthoritiesReactiveAuthorizationManager
|
||||
.hasAllAuthorities(ROLE_USER);
|
||||
assertThat(manager.authorize(Mono.just(authentication), "").block().isGranted()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void authorizeWhenNotAuthenticated() {
|
||||
Authentication authentication = new TestingAuthenticationToken("user", "password", ROLE_USER);
|
||||
authentication.setAuthenticated(false);
|
||||
AllAuthoritiesReactiveAuthorizationManager<Object> manager = AllAuthoritiesReactiveAuthorizationManager
|
||||
.hasAllAuthorities(ROLE_USER);
|
||||
assertThat(manager.authorize(Mono.just(authentication), "").block().isGranted()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void hasAllRolesAuthorizeWhenGranted() {
|
||||
Authentication authentication = new TestingAuthenticationToken("user", "password", ROLE_USER);
|
||||
AllAuthoritiesReactiveAuthorizationManager<Object> manager = AllAuthoritiesReactiveAuthorizationManager
|
||||
.hasAllRoles("USER");
|
||||
assertThat(manager.authorize(Mono.just(authentication), "").block().isGranted()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void hasAllPrefixedAuthoritiesAuthorizeWhenGranted() {
|
||||
String prefix = "PREFIX_";
|
||||
String authority1 = "AUTHORITY1";
|
||||
String authority2 = "AUTHORITY2";
|
||||
Authentication authentication = new TestingAuthenticationToken("user", "password", prefix + authority1,
|
||||
prefix + authority2);
|
||||
AllAuthoritiesReactiveAuthorizationManager<Object> manager = AllAuthoritiesReactiveAuthorizationManager
|
||||
.hasAllPrefixedAuthorities(prefix, authority1, authority2);
|
||||
assertThat(manager.authorize(Mono.just(authentication), "").block().isGranted()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void authorizeWhenSingleMissingThenDenied() {
|
||||
Authentication authentication = new TestingAuthenticationToken("user", "password", ROLE_USER);
|
||||
AllAuthoritiesReactiveAuthorizationManager<Object> manager = AllAuthoritiesReactiveAuthorizationManager
|
||||
.hasAllAuthorities(ROLE_ADMIN);
|
||||
assertThat(manager.authorize(Mono.just(authentication), "").block().isGranted()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void authorizeWhenMultipleMissingOneThenDenied() {
|
||||
Authentication authentication = new TestingAuthenticationToken("user", "password", ROLE_USER);
|
||||
AllAuthoritiesReactiveAuthorizationManager<Object> manager = AllAuthoritiesReactiveAuthorizationManager
|
||||
.hasAllAuthorities(ROLE_ADMIN, ROLE_USER);
|
||||
AuthorityAuthorizationDecision result = manager.authorize(Mono.just(authentication), "")
|
||||
.cast(AuthorityAuthorizationDecision.class)
|
||||
.block();
|
||||
assertThat(result.isGranted()).isFalse();
|
||||
assertThat(result.getAuthorities()).hasSize(1);
|
||||
assertThat(new ArrayList<>(result.getAuthorities()).get(0).getAuthority()).isEqualTo(ROLE_ADMIN);
|
||||
}
|
||||
|
||||
@Test
|
||||
void setRoleHierarchyWhenNullThenIllegalArgumentException() {
|
||||
AllAuthoritiesReactiveAuthorizationManager<?> manager = AllAuthoritiesReactiveAuthorizationManager
|
||||
.hasAllAuthorities(ROLE_USER);
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> manager.setRoleHierarchy(null));
|
||||
}
|
||||
|
||||
@Test
|
||||
void setRoleHierarchyThenUsesResult() {
|
||||
Collection result = AuthorityUtils.createAuthorityList(ROLE_USER, ROLE_ADMIN);
|
||||
given(this.roleHierarchy.getReachableGrantedAuthorities(any())).willReturn(result);
|
||||
AllAuthoritiesReactiveAuthorizationManager<Object> manager = AllAuthoritiesReactiveAuthorizationManager
|
||||
.hasAllAuthorities(ROLE_USER);
|
||||
manager.setRoleHierarchy(this.roleHierarchy);
|
||||
|
||||
Authentication authentication = new TestingAuthenticationToken("user", "password", ROLE_USER);
|
||||
|
||||
AuthorityAuthorizationDecision authz = manager.authorize(Mono.just(authentication), "")
|
||||
.cast(AuthorityAuthorizationDecision.class)
|
||||
.block();
|
||||
assertThat(authz.isGranted()).isTrue();
|
||||
verify(this.roleHierarchy).getReachableGrantedAuthorities(this.authoritiesCaptor.capture());
|
||||
assertThat(this.authoritiesCaptor.getValue()).map(GrantedAuthority::getAuthority).contains(ROLE_USER);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -16,6 +16,7 @@ Each section that follows will indicate the more notable removals as well as the
|
|||
== Core
|
||||
|
||||
* Removed `AuthorizationManager#check` in favor of `AuthorizationManager#authorize`
|
||||
* Added javadoc:org.springframework.security.authorization.AllAuthoritiesAuthorizationManager[] and javadoc:org.springframework.security.authorization.AllAuthoritiesReactiveAuthorizationManager[]
|
||||
* Added xref:servlet/authorization/architecture.adoc#authz-authorization-manager-factory[`AuthorizationManagerFactory`] for creating `AuthorizationManager` instances in xref:servlet/authorization/authorize-http-requests.adoc#customizing-authorization-managers[request-based] and xref:servlet/authorization/method-security.adoc#customizing-authorization-managers[method-based] authorization components
|
||||
* Added `Authentication.Builder` for mutating and merging `Authentication` instances
|
||||
* Moved Access API (`AccessDecisionManager`, `AccessDecisionVoter`, etc.) to a new module, `spring-security-access`
|
||||
|
|
|
|||
Loading…
Reference in New Issue