diff --git a/config/src/integration-test/java/org/springframework/security/config/annotation/authentication/ldap/LdapAuthenticationProviderBuilderSecurityBuilderTests.java b/config/src/integration-test/java/org/springframework/security/config/annotation/authentication/ldap/LdapAuthenticationProviderBuilderSecurityBuilderTests.java index 9542f58917..f1d1bb8085 100644 --- a/config/src/integration-test/java/org/springframework/security/config/annotation/authentication/ldap/LdapAuthenticationProviderBuilderSecurityBuilderTests.java +++ b/config/src/integration-test/java/org/springframework/security/config/annotation/authentication/ldap/LdapAuthenticationProviderBuilderSecurityBuilderTests.java @@ -18,7 +18,6 @@ package org.springframework.security.config.annotation.authentication.ldap; import java.io.IOException; import java.net.ServerSocket; -import java.util.Collections; import java.util.List; import javax.naming.directory.SearchControls; @@ -39,7 +38,6 @@ import org.springframework.security.config.annotation.configuration.ObjectPostPr import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; -import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.ldap.DefaultSpringSecurityContextSource; @@ -120,8 +118,7 @@ public class LdapAuthenticationProviderBuilderSecurityBuilderTests { this.spring.register(BindAuthenticationConfig.class).autowire(); this.mockMvc.perform(formLogin().user("bob").password("bobspassword")) - .andExpect(authenticated().withUsername("bob") - .withAuthorities(Collections.singleton(new SimpleGrantedAuthority("ROLE_DEVELOPERS")))); + .andExpect(authenticated().withUsername("bob").withRoles("DEVELOPERS")); } // SEC-2472 @@ -130,8 +127,7 @@ public class LdapAuthenticationProviderBuilderSecurityBuilderTests { this.spring.register(PasswordEncoderConfig.class).autowire(); this.mockMvc.perform(formLogin().user("bcrypt").password("password")) - .andExpect(authenticated().withUsername("bcrypt") - .withAuthorities(Collections.singleton(new SimpleGrantedAuthority("ROLE_DEVELOPERS")))); + .andExpect(authenticated().withUsername("bcrypt").withRoles("DEVELOPERS")); } private LdapAuthenticationProvider ldapProvider() { diff --git a/config/src/integration-test/java/org/springframework/security/config/annotation/authentication/ldap/LdapAuthenticationProviderConfigurerTests.java b/config/src/integration-test/java/org/springframework/security/config/annotation/authentication/ldap/LdapAuthenticationProviderConfigurerTests.java index 0d13741be6..7de7ac2fe8 100644 --- a/config/src/integration-test/java/org/springframework/security/config/annotation/authentication/ldap/LdapAuthenticationProviderConfigurerTests.java +++ b/config/src/integration-test/java/org/springframework/security/config/annotation/authentication/ldap/LdapAuthenticationProviderConfigurerTests.java @@ -16,8 +16,6 @@ package org.springframework.security.config.annotation.authentication.ldap; -import java.util.Collections; - import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -28,8 +26,6 @@ import org.springframework.security.config.annotation.authentication.ldap.LdapAu import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; -import org.springframework.security.core.authority.AuthorityUtils; -import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders; import org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers; import org.springframework.test.web.servlet.MockMvc; @@ -64,7 +60,7 @@ public class LdapAuthenticationProviderConfigurerTests { .password("bobspassword"); SecurityMockMvcResultMatchers.AuthenticatedMatcher expectedUser = authenticated() .withUsername("bob") - .withAuthorities(Collections.singleton(new SimpleGrantedAuthority("ROLE_DEVELOPERS"))); + .withRoles("DEVELOPERS"); // @formatter:on this.mockMvc.perform(request).andExpect(expectedUser); } @@ -79,7 +75,7 @@ public class LdapAuthenticationProviderConfigurerTests { .password("bobspassword"); SecurityMockMvcResultMatchers.AuthenticatedMatcher expectedUser = authenticated() .withUsername("bob") - .withAuthorities(Collections.singleton(new SimpleGrantedAuthority("ROL_DEVELOPERS"))); + .withRoles("ROL_", new String[] { "DEVELOPERS" }); // @formatter:on this.mockMvc.perform(request).andExpect(expectedUser); } @@ -108,8 +104,7 @@ public class LdapAuthenticationProviderConfigurerTests { .password("otherbenspassword"); SecurityMockMvcResultMatchers.AuthenticatedMatcher expectedUser = authenticated() .withUsername("otherben") - .withAuthorities( - AuthorityUtils.createAuthorityList("ROLE_SUBMANAGERS", "ROLE_MANAGERS", "ROLE_DEVELOPERS")); + .withRoles("SUBMANAGERS", "MANAGERS", "DEVELOPERS"); // @formatter:on this.mockMvc.perform(request).andExpect(expectedUser); } diff --git a/config/src/integration-test/java/org/springframework/security/config/annotation/authentication/ldap/NamespaceLdapAuthenticationProviderTests.java b/config/src/integration-test/java/org/springframework/security/config/annotation/authentication/ldap/NamespaceLdapAuthenticationProviderTests.java index 58dd74c9b6..673b32755e 100644 --- a/config/src/integration-test/java/org/springframework/security/config/annotation/authentication/ldap/NamespaceLdapAuthenticationProviderTests.java +++ b/config/src/integration-test/java/org/springframework/security/config/annotation/authentication/ldap/NamespaceLdapAuthenticationProviderTests.java @@ -16,7 +16,6 @@ package org.springframework.security.config.annotation.authentication.ldap; -import java.util.Collections; import java.util.HashSet; import java.util.Set; @@ -34,7 +33,6 @@ import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.AuthorityUtils; -import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.ldap.DefaultSpringSecurityContextSource; import org.springframework.security.ldap.userdetails.DefaultLdapAuthoritiesPopulator; import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders; @@ -79,7 +77,7 @@ public class NamespaceLdapAuthenticationProviderTests { .user("bob") .password("bobspassword"); SecurityMockMvcResultMatchers.AuthenticatedMatcher user = authenticated() - .withAuthorities(Collections.singleton(new SimpleGrantedAuthority("PREFIX_DEVELOPERS"))); + .withRoles("PREFIX_", new String[] { "DEVELOPERS" }); // @formatter:on this.mockMvc.perform(request).andExpect(user); } @@ -103,7 +101,7 @@ public class NamespaceLdapAuthenticationProviderTests { .user("bob") .password("bobspassword"); SecurityMockMvcResultMatchers.AuthenticatedMatcher user = authenticated() - .withAuthorities(Collections.singleton(new SimpleGrantedAuthority("ROLE_EXTRA"))); + .withRoles("EXTRA"); // @formatter:on this.mockMvc.perform(request).andExpect(user); } diff --git a/test/src/main/java/org/springframework/security/test/web/servlet/response/SecurityMockMvcResultMatchers.java b/test/src/main/java/org/springframework/security/test/web/servlet/response/SecurityMockMvcResultMatchers.java index 99e67893a9..c046a25607 100644 --- a/test/src/main/java/org/springframework/security/test/web/servlet/response/SecurityMockMvcResultMatchers.java +++ b/test/src/main/java/org/springframework/security/test/web/servlet/response/SecurityMockMvcResultMatchers.java @@ -18,7 +18,9 @@ package org.springframework.security.test.web.servlet.response; import java.util.ArrayList; import java.util.Collection; +import java.util.List; import java.util.function.Consumer; +import java.util.function.Predicate; import org.jspecify.annotations.NullUnmarked; import org.jspecify.annotations.Nullable; @@ -94,6 +96,8 @@ public final class SecurityMockMvcResultMatchers { private @Nullable Collection expectedGrantedAuthorities; + private Predicate ignoreAuthorities = (authority) -> false; + private @Nullable Consumer assertAuthentication; AuthenticatedMatcher() { @@ -132,7 +136,8 @@ public final class SecurityMockMvcResultMatchers { } if (this.expectedGrantedAuthorities != null) { AssertionErrors.assertTrue("Authentication cannot be null", auth != null); - Collection authorities = auth.getAuthorities(); + Collection authorities = new ArrayList<>(auth.getAuthorities()); + authorities.removeIf(this.ignoreAuthorities); AssertionErrors.assertTrue( authorities + " does not contain the same authorities as " + this.expectedGrantedAuthorities, authorities.containsAll(this.expectedGrantedAuthorities)); @@ -212,16 +217,43 @@ public final class SecurityMockMvcResultMatchers { } /** - * Specifies the {@link Authentication#getAuthorities()} + * Specifies the expected roles. + *

+ * Since a set of authorities can contain more than just roles, this method + * differs from {@link #withAuthorities} in that it only verifies the authorities + * prefixed by {@code ROLE_}. Other authorities are ignored. + *

+ * If you want to validate more than just roles, please use + * {@link #withAuthorities}. * @param roles the roles. Each value is automatically prefixed with "ROLE_" * @return the {@link AuthenticatedMatcher} for further customization */ public AuthenticatedMatcher withRoles(String... roles) { - Collection authorities = new ArrayList<>(); + return withRoles("ROLE_", roles); + } + + /** + * Specifies the expected roles. + *

+ * Since a set of authorities can contain more than just roles, this method + * differs from {@link #withAuthorities} in that it only verifies the authorities + * prefixed by {@code ROLE_}. Other authorities are ignored. + *

+ * If you want to validate more than just roles, please use + * {@link #withAuthorities}. + * @param rolePrefix the role prefix + * @param roles the roles. Each value is automatically prefixed with the + * {@code rolePrefix} + * @return the {@link AuthenticatedMatcher} for further customization + * @since 7.0 + */ + public AuthenticatedMatcher withRoles(String rolePrefix, String[] roles) { + List withPrefix = new ArrayList<>(); for (String role : roles) { - authorities.add(new SimpleGrantedAuthority("ROLE_" + role)); + withPrefix.add(new SimpleGrantedAuthority(rolePrefix + role)); } - return withAuthorities(authorities); + this.ignoreAuthorities = (authority) -> !authority.getAuthority().startsWith(rolePrefix); + return withAuthorities(withPrefix); } }