diff --git a/core/src/main/java/org/springframework/security/authorization/method/Jsr250AuthorizationManager.java b/core/src/main/java/org/springframework/security/authorization/method/Jsr250AuthorizationManager.java index a93c0d336d..7b787446fe 100644 --- a/core/src/main/java/org/springframework/security/authorization/method/Jsr250AuthorizationManager.java +++ b/core/src/main/java/org/springframework/security/authorization/method/Jsr250AuthorizationManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2023 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. @@ -18,6 +18,7 @@ package org.springframework.security.authorization.method; import java.lang.annotation.Annotation; import java.lang.reflect.Method; +import java.util.Collection; import java.util.HashSet; import java.util.Set; import java.util.function.Supplier; @@ -30,7 +31,7 @@ import org.aopalliance.intercept.MethodInvocation; import org.springframework.aop.support.AopUtils; import org.springframework.core.annotation.AnnotationConfigurationException; import org.springframework.lang.NonNull; -import org.springframework.security.authorization.AuthorityAuthorizationManager; +import org.springframework.security.authorization.AuthoritiesAuthorizationManager; import org.springframework.security.authorization.AuthorizationDecision; import org.springframework.security.authorization.AuthorizationManager; import org.springframework.security.core.Authentication; @@ -57,8 +58,23 @@ public final class Jsr250AuthorizationManager implements AuthorizationManager> authoritiesAuthorizationManager = new AuthoritiesAuthorizationManager(); + private String rolePrefix = "ROLE_"; + /** + * Sets an {@link AuthorizationManager} that accepts a collection of authority + * strings. + * @param authoritiesAuthorizationManager the {@link AuthorizationManager} that + * accepts a collection of authority strings to use + * @since 6.1 + */ + public void setAuthoritiesAuthorizationManager( + AuthorizationManager> authoritiesAuthorizationManager) { + Assert.notNull(authoritiesAuthorizationManager, "authoritiesAuthorizationManager cannot be null"); + this.authoritiesAuthorizationManager = authoritiesAuthorizationManager; + } + /** * Sets the role prefix. Defaults to "ROLE_". * @param rolePrefix the role prefix to use @@ -95,10 +111,8 @@ public final class Jsr250AuthorizationManager implements AuthorizationManager new AuthorizationDecision(true); } - if (annotation instanceof RolesAllowed) { - RolesAllowed rolesAllowed = (RolesAllowed) annotation; - return AuthorityAuthorizationManager.hasAnyRole(Jsr250AuthorizationManager.this.rolePrefix, - rolesAllowed.value()); + if (annotation instanceof RolesAllowed rolesAllowed) { + return (a, o) -> Jsr250AuthorizationManager.this.authoritiesAuthorizationManager.check(a, getAllowedRolesWithPrefix(rolesAllowed)); } return NULL_MANAGER; } @@ -145,6 +159,14 @@ public final class Jsr250AuthorizationManager implements AuthorizationManager getAllowedRolesWithPrefix(RolesAllowed rolesAllowed) { + Set roles = new HashSet<>(); + for (int i = 0; i < rolesAllowed.value().length; i++) { + roles.add(Jsr250AuthorizationManager.this.rolePrefix + rolesAllowed.value()[i]); + } + return roles; + } + } } diff --git a/core/src/test/java/org/springframework/security/authorization/method/Jsr250AuthorizationManagerTests.java b/core/src/test/java/org/springframework/security/authorization/method/Jsr250AuthorizationManagerTests.java index 2bb8e5bf30..b56912f0b7 100644 --- a/core/src/test/java/org/springframework/security/authorization/method/Jsr250AuthorizationManagerTests.java +++ b/core/src/test/java/org/springframework/security/authorization/method/Jsr250AuthorizationManagerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2023 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. @@ -18,6 +18,8 @@ package org.springframework.security.authorization.method; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.Collection; +import java.util.Set; import java.util.function.Supplier; import jakarta.annotation.security.DenyAll; @@ -30,11 +32,14 @@ import org.springframework.security.access.intercept.method.MockMethodInvocation import org.springframework.security.authentication.TestAuthentication; import org.springframework.security.authentication.TestingAuthenticationToken; import org.springframework.security.authorization.AuthorizationDecision; +import org.springframework.security.authorization.AuthorizationManager; import org.springframework.security.core.Authentication; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; /** * Tests for {@link Jsr250AuthorizationManager}. @@ -63,6 +68,27 @@ public class Jsr250AuthorizationManagerTests { assertThat(manager).extracting("rolePrefix").isEqualTo("CUSTOM_"); } + @Test + public void setAuthoritiesAuthorizationManagerWhenNullThenException() { + Jsr250AuthorizationManager manager = new Jsr250AuthorizationManager(); + assertThatIllegalArgumentException().isThrownBy(() -> manager.setAuthoritiesAuthorizationManager(null)) + .withMessage("authoritiesAuthorizationManager cannot be null"); + } + + @Test + public void setAuthoritiesAuthorizationManagerWhenNotNullThenVerifyUsage() throws Exception { + AuthorizationManager> authoritiesAuthorizationManager = mock(AuthorizationManager.class); + Jsr250AuthorizationManager manager = new Jsr250AuthorizationManager(); + manager.setAuthoritiesAuthorizationManager(authoritiesAuthorizationManager); + MockMethodInvocation methodInvocation = new MockMethodInvocation(new ClassLevelAnnotations(), + ClassLevelAnnotations.class, "rolesAllowedAdmin"); + Supplier authentication = () -> new TestingAuthenticationToken("user", "password", + "ROLE_ADMIN"); + AuthorizationDecision decision = manager.check(authentication, methodInvocation); + assertThat(decision).isNull(); + verify(authoritiesAuthorizationManager).check(authentication, Set.of("ROLE_ADMIN")); + } + @Test public void checkDoSomethingWhenNoJsr250AnnotationsThenNullDecision() throws Exception { MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class, @@ -123,7 +149,7 @@ public class Jsr250AuthorizationManagerTests { } @Test - public void checkMultipleAnnotationsWhenInvokedThenAnnotationConfigurationException() throws Exception { + public void checkMultipleMethodAnnotationsWhenInvokedThenAnnotationConfigurationException() throws Exception { Supplier authentication = () -> new TestingAuthenticationToken("user", "password", "ROLE_ANONYMOUS"); MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class, @@ -133,6 +159,16 @@ public class Jsr250AuthorizationManagerTests { .isThrownBy(() -> manager.check(authentication, methodInvocation)); } + @Test + public void checkMultipleClassAnnotationsWhenInvokedThenAnnotationConfigurationException() throws Exception { + Supplier authentication = () -> new TestingAuthenticationToken("user", "password", "ROLE_USER"); + MockMethodInvocation methodInvocation = new MockMethodInvocation(new ClassLevelIllegalAnnotations(), + ClassLevelIllegalAnnotations.class, "inheritedAnnotations"); + Jsr250AuthorizationManager manager = new Jsr250AuthorizationManager(); + assertThatExceptionOfType(AnnotationConfigurationException.class) + .isThrownBy(() -> manager.check(authentication, methodInvocation)); + } + @Test public void checkRequiresAdminWhenClassAnnotationsThenMethodAnnotationsTakePrecedence() throws Exception { Supplier authentication = () -> new TestingAuthenticationToken("user", "password", "ROLE_USER"); @@ -247,6 +283,15 @@ public class Jsr250AuthorizationManagerTests { } + @MyIllegalRolesAllowed + public static class ClassLevelIllegalAnnotations { + + public void inheritedAnnotations() { + + } + + } + public interface InterfaceAnnotationsOne { @RolesAllowed("ADMIN") @@ -274,4 +319,11 @@ public class Jsr250AuthorizationManagerTests { } + @DenyAll + @RolesAllowed("USER") + @Retention(RetentionPolicy.RUNTIME) + public @interface MyIllegalRolesAllowed { + + } + }