SecurityMockMvcResultMatchers.withAuthorities(String...)
CodeQL Advanced / codeql-analysis-call (push) Waiting to run Details
CI / Build (17, ubuntu-latest) (push) Waiting to run Details
CI / Build (17, windows-latest) (push) Waiting to run Details
CI / Deploy Artifacts (push) Blocked by required conditions Details
CI / Deploy Docs (push) Blocked by required conditions Details
CI / Deploy Schema (push) Blocked by required conditions Details
CI / Perform Release (push) Blocked by required conditions Details
CI / Send Notification (push) Blocked by required conditions Details
Deploy Docs / build (push) Waiting to run Details

Closes gh-17974
This commit is contained in:
Rob Winch 2025-09-29 15:55:28 -05:00
parent 0e99324c43
commit 7f10897de3
No known key found for this signature in database
3 changed files with 65 additions and 1 deletions

View File

@ -70,6 +70,10 @@ http.csrf((csrf) -> csrf.spa());
* Made so that SLO still returns `<saml2:LogoutResponse>` even when validation fails
* Removed Open SAML 4 support; applications should migrate to Open SAML 5
== Test
* https://github.com/spring-projects/spring-security/issues/17974[Add SecurityMockMvcResultMatchers.withAuthorities(String...)]
== Web
* Removed `MvcRequestMatcher` and `AntPathRequestMatcher` in favor of `PathPatternRequestMatcher`

View File

@ -17,6 +17,7 @@
package org.springframework.security.test.web.servlet.response;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.function.Consumer;
@ -38,6 +39,7 @@ import org.springframework.test.util.AssertionErrors;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.ResultMatcher;
import org.springframework.util.Assert;
/**
* Security related {@link MockMvc} {@link ResultMatcher}s.
@ -96,6 +98,8 @@ public final class SecurityMockMvcResultMatchers {
private @Nullable Collection<? extends GrantedAuthority> expectedGrantedAuthorities;
private @Nullable Collection<String> expectedAuthorities;
private Predicate<GrantedAuthority> ignoreAuthorities = (authority) -> false;
private @Nullable Consumer<Authentication> assertAuthentication;
@ -145,6 +149,20 @@ public final class SecurityMockMvcResultMatchers {
this.expectedGrantedAuthorities + " does not contain the same authorities as " + authorities,
this.expectedGrantedAuthorities.containsAll(authorities));
}
if (this.expectedAuthorities != null) {
AssertionErrors.assertTrue("Authentication cannot be null", auth != null);
List<String> authorities = auth.getAuthorities()
.stream()
.filter(Predicate.not(this.ignoreAuthorities))
.map(GrantedAuthority::getAuthority)
.toList();
AssertionErrors.assertTrue(
authorities + " does not contain the same authorities as " + this.expectedAuthorities,
this.expectedAuthorities.containsAll(authorities));
AssertionErrors.assertTrue(
this.expectedAuthorities + " does not contain the same authorities as " + authorities,
authorities.containsAll(this.expectedAuthorities));
}
}
/**
@ -206,6 +224,17 @@ public final class SecurityMockMvcResultMatchers {
return this;
}
/**
* Specifies the {@link GrantedAuthority#getAuthority()}
* @param authorities the authorityNames
* @return the {@link AuthenticatedMatcher} for further customization
*/
public AuthenticatedMatcher withAuthorities(String... authorities) {
Assert.notNull(authorities, "authorities cannot be null");
this.expectedAuthorities = Arrays.asList(authorities);
return this;
}
/**
* Specifies the {@link Authentication#getAuthorities()}
* @param expected the {@link Authentication#getAuthorities()}

View File

@ -28,6 +28,7 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.GrantedAuthorities;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
@ -53,6 +54,8 @@ import static org.springframework.security.test.web.servlet.setup.SecurityMockMv
@WebAppConfiguration
public class SecurityMockWithAuthoritiesMvcResultMatchersTests {
private static final String ROLE_CUSTOM = "ROLE_CUSTOM";
@Autowired
private WebApplicationContext context;
@ -80,6 +83,12 @@ public class SecurityMockWithAuthoritiesMvcResultMatchersTests {
() -> this.mockMvc.perform(formLogin()).andExpect(authenticated().withAuthorities(grantedAuthorities)));
}
@Test
public void withAuthoritiesStringSupportsCustomAuthority() throws Exception {
this.mockMvc.perform(formLogin().user("custom"))
.andExpect(authenticated().withAuthorities(ROLE_CUSTOM, GrantedAuthorities.FACTOR_PASSWORD_AUTHORITY));
}
@Configuration
@EnableWebSecurity
@EnableWebMvc
@ -89,7 +98,8 @@ public class SecurityMockWithAuthoritiesMvcResultMatchersTests {
UserDetailsService userDetailsService() {
// @formatter:off
UserDetails user = User.withDefaultPasswordEncoder().username("user").password("password").roles("ADMIN", "SELLER").build();
return new InMemoryUserDetailsManager(user);
UserDetails customAuthorityUser = User.withDefaultPasswordEncoder().username("custom").password("password").authorities(new CustomAuthority(ROLE_CUSTOM)).build();
return new InMemoryUserDetailsManager(user, customAuthorityUser);
// @formatter:on
}
@ -105,4 +115,25 @@ public class SecurityMockWithAuthoritiesMvcResultMatchersTests {
}
/**
* A custom {@link GrantedAuthority} for testing.
*
* @author Rob Winch
* @since 7.0
*/
static class CustomAuthority implements GrantedAuthority {
private final String authority;
CustomAuthority(String authority) {
this.authority = authority;
}
@Override
public String getAuthority() {
return this.authority;
}
}
}