Skip to content

Commit c64b086

Browse files
committed
Add SecurityAssertions
This commit introduces a simple, internal test API for verifying aspects of an Authentication, like its name and authorities. Closes gh-17844
1 parent de10e08 commit c64b086

File tree

15 files changed

+180
-108
lines changed

15 files changed

+180
-108
lines changed

config/src/test/java/org/springframework/security/config/annotation/authentication/AuthenticationManagerBuilderTests.java

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import org.springframework.security.authentication.AuthenticationManager;
3333
import org.springframework.security.authentication.AuthenticationProvider;
3434
import org.springframework.security.authentication.ProviderManager;
35+
import org.springframework.security.authentication.SecurityAssertions;
3536
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
3637
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
3738
import org.springframework.security.config.ObjectPostProcessor;
@@ -44,7 +45,6 @@
4445
import org.springframework.security.config.test.SpringTestContextExtension;
4546
import org.springframework.security.core.Authentication;
4647
import org.springframework.security.core.AuthenticationException;
47-
import org.springframework.security.core.GrantedAuthority;
4848
import org.springframework.security.core.userdetails.PasswordEncodedUser;
4949
import org.springframework.security.core.userdetails.UserDetailsService;
5050
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
@@ -107,8 +107,7 @@ public void getAuthenticationManagerWhenGlobalPasswordEncoderBeanThenUsed() thro
107107
.getAuthenticationManager();
108108
Authentication auth = manager
109109
.authenticate(UsernamePasswordAuthenticationToken.unauthenticated("user", "password"));
110-
assertThat(auth.getName()).isEqualTo("user");
111-
assertThat(auth.getAuthorities()).extracting(GrantedAuthority::getAuthority).containsOnly("ROLE_USER");
110+
SecurityAssertions.assertThat(auth).name("user").hasAuthority("ROLE_USER");
112111
}
113112

114113
@Test
@@ -119,8 +118,7 @@ public void getAuthenticationManagerWhenProtectedPasswordEncoderBeanThenUsed() t
119118
.getAuthenticationManager();
120119
Authentication auth = manager
121120
.authenticate(UsernamePasswordAuthenticationToken.unauthenticated("user", "password"));
122-
assertThat(auth.getName()).isEqualTo("user");
123-
assertThat(auth.getAuthorities()).extracting(GrantedAuthority::getAuthority).containsOnly("ROLE_USER");
121+
SecurityAssertions.assertThat(auth).name("user").hasAuthority("ROLE_USER");
124122
}
125123

126124
@Test

config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2LoginConfigurerTests.java

Lines changed: 21 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
import org.springframework.mock.web.MockHttpServletRequest;
4646
import org.springframework.mock.web.MockHttpServletResponse;
4747
import org.springframework.security.authentication.AuthenticationProvider;
48+
import org.springframework.security.authentication.SecurityAssertions;
4849
import org.springframework.security.authentication.event.AuthenticationSuccessEvent;
4950
import org.springframework.security.config.Customizer;
5051
import org.springframework.security.config.ObjectPostProcessor;
@@ -217,10 +218,9 @@ public void oauth2Login() throws Exception {
217218
Authentication authentication = this.securityContextRepository
218219
.loadContext(new HttpRequestResponseHolder(this.request, this.response))
219220
.getAuthentication();
220-
assertThat(authentication.getAuthorities()).hasSize(1);
221-
assertThat(authentication.getAuthorities()).first()
222-
.isInstanceOf(OAuth2UserAuthority.class)
223-
.hasToString("OAUTH2_USER");
221+
SecurityAssertions.assertThat(authentication)
222+
.hasAuthority("OAUTH2_USER")
223+
.isInstanceOf(OAuth2UserAuthority.class);
224224
}
225225

226226
@Test
@@ -234,10 +234,9 @@ public void requestWhenCustomSecurityContextHolderStrategyThenUses() throws Exce
234234
Authentication authentication = this.securityContextRepository
235235
.loadContext(new HttpRequestResponseHolder(this.request, this.response))
236236
.getAuthentication();
237-
assertThat(authentication.getAuthorities()).hasSize(1);
238-
assertThat(authentication.getAuthorities()).first()
239-
.isInstanceOf(OAuth2UserAuthority.class)
240-
.hasToString("OAUTH2_USER");
237+
SecurityAssertions.assertThat(authentication)
238+
.hasAuthority("OAUTH2_USER")
239+
.isInstanceOf(OAuth2UserAuthority.class);
241240
SecurityContextHolderStrategy strategy = this.context.getBean(SecurityContextHolderStrategy.class);
242241
verify(strategy, atLeastOnce()).getDeferredContext();
243242
SecurityContextChangedListener listener = this.context.getBean(SecurityContextChangedListener.class);
@@ -255,10 +254,9 @@ public void requestWhenOauth2LoginInLambdaThenAuthenticationContainsOauth2UserAu
255254
Authentication authentication = this.securityContextRepository
256255
.loadContext(new HttpRequestResponseHolder(this.request, this.response))
257256
.getAuthentication();
258-
assertThat(authentication.getAuthorities()).hasSize(1);
259-
assertThat(authentication.getAuthorities()).first()
260-
.isInstanceOf(OAuth2UserAuthority.class)
261-
.hasToString("OAUTH2_USER");
257+
SecurityAssertions.assertThat(authentication)
258+
.hasAuthority("OAUTH2_USER")
259+
.isInstanceOf(OAuth2UserAuthority.class);
262260
}
263261

264262
// gh-6009
@@ -296,9 +294,7 @@ public void oauth2LoginCustomWithConfigurer() throws Exception {
296294
Authentication authentication = this.securityContextRepository
297295
.loadContext(new HttpRequestResponseHolder(this.request, this.response))
298296
.getAuthentication();
299-
assertThat(authentication.getAuthorities()).hasSize(2);
300-
assertThat(authentication.getAuthorities()).first().hasToString("OAUTH2_USER");
301-
assertThat(authentication.getAuthorities()).last().hasToString("ROLE_OAUTH2_USER");
297+
SecurityAssertions.assertThat(authentication).hasAuthorities("OAUTH2_USER", "ROLE_OAUTH2_USER");
302298
}
303299

304300
@Test
@@ -317,9 +313,7 @@ public void oauth2LoginCustomWithBeanRegistration() throws Exception {
317313
Authentication authentication = this.securityContextRepository
318314
.loadContext(new HttpRequestResponseHolder(this.request, this.response))
319315
.getAuthentication();
320-
assertThat(authentication.getAuthorities()).hasSize(2);
321-
assertThat(authentication.getAuthorities()).first().hasToString("OAUTH2_USER");
322-
assertThat(authentication.getAuthorities()).last().hasToString("ROLE_OAUTH2_USER");
316+
SecurityAssertions.assertThat(authentication).hasAuthorities("OAUTH2_USER", "ROLE_OAUTH2_USER");
323317
}
324318

325319
@Test
@@ -338,9 +332,7 @@ public void oauth2LoginCustomWithUserServiceBeanRegistration() throws Exception
338332
Authentication authentication = this.securityContextRepository
339333
.loadContext(new HttpRequestResponseHolder(this.request, this.response))
340334
.getAuthentication();
341-
assertThat(authentication.getAuthorities()).hasSize(2);
342-
assertThat(authentication.getAuthorities()).first().hasToString("OAUTH2_USER");
343-
assertThat(authentication.getAuthorities()).last().hasToString("ROLE_OAUTH2_USER");
335+
SecurityAssertions.assertThat(authentication).hasAuthorities("OAUTH2_USER", "ROLE_OAUTH2_USER");
344336
}
345337

346338
// gh-5488
@@ -361,10 +353,9 @@ public void oauth2LoginConfigLoginProcessingUrl() throws Exception {
361353
Authentication authentication = this.securityContextRepository
362354
.loadContext(new HttpRequestResponseHolder(this.request, this.response))
363355
.getAuthentication();
364-
assertThat(authentication.getAuthorities()).hasSize(1);
365-
assertThat(authentication.getAuthorities()).first()
366-
.isInstanceOf(OAuth2UserAuthority.class)
367-
.hasToString("OAUTH2_USER");
356+
SecurityAssertions.assertThat(authentication)
357+
.hasAuthority("OAUTH2_USER")
358+
.isInstanceOf(OAuth2UserAuthority.class);
368359
}
369360

370361
// gh-5521
@@ -570,10 +561,7 @@ public void oidcLogin() throws Exception {
570561
Authentication authentication = this.securityContextRepository
571562
.loadContext(new HttpRequestResponseHolder(this.request, this.response))
572563
.getAuthentication();
573-
assertThat(authentication.getAuthorities()).hasSize(1);
574-
assertThat(authentication.getAuthorities()).first()
575-
.isInstanceOf(OidcUserAuthority.class)
576-
.hasToString("OIDC_USER");
564+
SecurityAssertions.assertThat(authentication).hasAuthority("OIDC_USER").isInstanceOf(OidcUserAuthority.class);
577565
}
578566

579567
@Test
@@ -593,9 +581,7 @@ public void requestWhenOauth2LoginInLambdaAndOidcThenAuthenticationContainsOidcU
593581
.loadContext(new HttpRequestResponseHolder(this.request, this.response))
594582
.getAuthentication();
595583
assertThat(authentication.getAuthorities()).hasSize(1);
596-
assertThat(authentication.getAuthorities()).first()
597-
.isInstanceOf(OidcUserAuthority.class)
598-
.hasToString("OIDC_USER");
584+
SecurityAssertions.assertThat(authentication).hasAuthority("OIDC_USER").isInstanceOf(OidcUserAuthority.class);
599585
}
600586

601587
@Test
@@ -614,9 +600,7 @@ public void oidcLoginCustomWithConfigurer() throws Exception {
614600
Authentication authentication = this.securityContextRepository
615601
.loadContext(new HttpRequestResponseHolder(this.request, this.response))
616602
.getAuthentication();
617-
assertThat(authentication.getAuthorities()).hasSize(2);
618-
assertThat(authentication.getAuthorities()).first().hasToString("OIDC_USER");
619-
assertThat(authentication.getAuthorities()).last().hasToString("ROLE_OIDC_USER");
603+
SecurityAssertions.assertThat(authentication).hasAuthorities("OIDC_USER", "ROLE_OIDC_USER");
620604
}
621605

622606
@Test
@@ -635,9 +619,7 @@ public void oidcLoginCustomWithBeanRegistration() throws Exception {
635619
Authentication authentication = this.securityContextRepository
636620
.loadContext(new HttpRequestResponseHolder(this.request, this.response))
637621
.getAuthentication();
638-
assertThat(authentication.getAuthorities()).hasSize(2);
639-
assertThat(authentication.getAuthorities()).first().hasToString("OIDC_USER");
640-
assertThat(authentication.getAuthorities()).last().hasToString("ROLE_OIDC_USER");
622+
SecurityAssertions.assertThat(authentication).hasAuthorities("OIDC_USER", "ROLE_OIDC_USER");
641623
}
642624

643625
@Test
@@ -690,11 +672,7 @@ public void oidcLoginWhenOAuth2ClientBeansConfiguredThenNotShared() throws Excep
690672
Authentication authentication = this.securityContextRepository
691673
.loadContext(new HttpRequestResponseHolder(this.request, this.response))
692674
.getAuthentication();
693-
assertThat(authentication.getAuthorities()).hasSize(1);
694-
assertThat(authentication.getAuthorities()).first()
695-
.isInstanceOf(OidcUserAuthority.class)
696-
.hasToString("OIDC_USER");
697-
675+
SecurityAssertions.assertThat(authentication).hasAuthority("OIDC_USER").isInstanceOf(OidcUserAuthority.class);
698676
// Ensure shared objects set for OAuth2 Client are not used
699677
ClientRegistrationRepository clientRegistrationRepository = this.spring.getContext()
700678
.getBean(ClientRegistrationRepository.class);

config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurerTests.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2674,6 +2674,7 @@ String authenticated(Authentication authentication) {
26742674
String requiresReadScope(JwtAuthenticationToken token) {
26752675
return token.getAuthorities()
26762676
.stream()
2677+
.filter((ga) -> ga.getAuthority().startsWith("SCOPE_"))
26772678
.map(GrantedAuthority::getAuthority)
26782679
.collect(Collectors.toList())
26792680
.toString();
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
/*
2+
* Copyright 2004-present the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.security.authentication;
18+
19+
import java.util.Arrays;
20+
import java.util.Collection;
21+
import java.util.List;
22+
import java.util.Set;
23+
import java.util.function.Predicate;
24+
25+
import org.assertj.core.api.AbstractObjectAssert;
26+
import org.assertj.core.api.Assertions;
27+
import org.assertj.core.api.CollectionAssert;
28+
import org.assertj.core.api.Condition;
29+
import org.assertj.core.api.ObjectAssert;
30+
import org.jspecify.annotations.NullMarked;
31+
import org.jspecify.annotations.Nullable;
32+
33+
import org.springframework.security.core.Authentication;
34+
import org.springframework.security.core.GrantedAuthority;
35+
import org.springframework.security.core.authority.AuthorityUtils;
36+
37+
@NullMarked
38+
public final class SecurityAssertions {
39+
40+
private SecurityAssertions() {
41+
42+
}
43+
44+
public static AuthenticationAssert assertThat(@Nullable Authentication authentication) {
45+
Assertions.assertThat(authentication).isNotNull();
46+
return new AuthenticationAssert(authentication);
47+
}
48+
49+
public static final class AuthenticationAssert extends AbstractObjectAssert<AuthenticationAssert, Authentication> {
50+
51+
private final Authentication authentication;
52+
53+
private AuthenticationAssert(Authentication authentication) {
54+
super(authentication, AuthenticationAssert.class);
55+
this.authentication = authentication;
56+
}
57+
58+
public AuthenticationAssert name(String name) {
59+
Assertions.assertThat(this.authentication.getName()).isEqualTo(name);
60+
return this;
61+
}
62+
63+
public ObjectAssert<GrantedAuthority> hasAuthority(String authority) {
64+
Collection<? extends GrantedAuthority> actual = this.authentication.getAuthorities();
65+
for (GrantedAuthority element : actual) {
66+
if (element.getAuthority().equals(authority)) {
67+
return new ObjectAssert<>(element);
68+
}
69+
}
70+
throw new AssertionError(actual + " does not contain " + authority + " as expected");
71+
}
72+
73+
public CollectionAssert<GrantedAuthority> hasAuthorities(String... authorities) {
74+
HasAuthoritiesPredicate test = new HasAuthoritiesPredicate(authorities);
75+
return authorities().has(new Condition<>(test, "contains %s", Arrays.toString(authorities)));
76+
}
77+
78+
public CollectionAssert<GrantedAuthority> authorities() {
79+
return new CollectionAssert<>(this.authentication.getAuthorities());
80+
}
81+
82+
}
83+
84+
private static final class HasAuthoritiesPredicate implements Predicate<Collection<? extends GrantedAuthority>> {
85+
86+
private final Collection<String> expected;
87+
88+
private HasAuthoritiesPredicate(String... expected) {
89+
this.expected = List.of(expected);
90+
}
91+
92+
@Override
93+
public boolean test(Collection<? extends GrantedAuthority> actual) {
94+
Set<String> asString = AuthorityUtils.authorityListToSet(actual);
95+
return asString.containsAll(this.expected);
96+
}
97+
98+
}
99+
100+
}

ldap/spring-security-ldap.gradle

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ dependencies {
1919
exclude(group: 'org.springframework.data', module: 'spring-data-commons')
2020
}
2121

22-
testImplementation project(':spring-security-test')
22+
testImplementation project(path : ':spring-security-core', configuration : 'tests')
23+
testImplementation project(":spring-security-test")
2324
testImplementation 'org.slf4j:slf4j-api'
2425
testImplementation "org.assertj:assertj-core"
2526
testImplementation "org.junit.jupiter:junit-jupiter-api"

ldap/src/test/java/org/springframework/security/ldap/authentication/ad/ActiveDirectoryLdapAuthenticationProviderTests.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
import org.springframework.security.authentication.DisabledException;
4343
import org.springframework.security.authentication.InternalAuthenticationServiceException;
4444
import org.springframework.security.authentication.LockedException;
45+
import org.springframework.security.authentication.SecurityAssertions;
4546
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
4647
import org.springframework.security.core.Authentication;
4748
import org.springframework.security.ldap.authentication.ad.ActiveDirectoryLdapAuthenticationProvider.ContextFactory;
@@ -357,10 +358,10 @@ private void checkAuthentication(String rootDn, ActiveDirectoryLdapAuthenticatio
357358
.willReturn(new MockNamingEnumeration(sr));
358359
provider.contextFactory = createContextFactoryReturning(this.ctx);
359360
Authentication result = provider.authenticate(this.joe);
360-
assertThat(result.getAuthorities()).isEmpty();
361+
SecurityAssertions.assertThat(result).authorities().doesNotHaveToString("Admin");
361362
dca.addAttributeValue("memberOf", "CN=Admin,CN=Users,DC=mydomain,DC=eu");
362363
result = provider.authenticate(this.joe);
363-
assertThat(result.getAuthorities()).hasSize(1);
364+
SecurityAssertions.assertThat(result).hasAuthority("Admin");
364365
}
365366

366367
static class MockNamingEnumeration implements NamingEnumeration<SearchResult> {

oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/authentication/OAuth2LoginAuthenticationProviderTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ public void authenticateWhenLoginSuccessThenReturnAuthentication() {
165165
assertThat(authentication.isAuthenticated()).isTrue();
166166
assertThat(authentication.getPrincipal()).isEqualTo(principal);
167167
assertThat(authentication.getCredentials()).isEqualTo("");
168-
assertThat(authentication.getAuthorities()).isEqualTo(authorities);
168+
assertThat(authentication.getAuthorities()).containsAll(authorities);
169169
assertThat(authentication.getClientRegistration()).isEqualTo(this.clientRegistration);
170170
assertThat(authentication.getAuthorizationExchange()).isEqualTo(this.authorizationExchange);
171171
assertThat(authentication.getAccessToken()).isEqualTo(accessTokenResponse.getAccessToken());

oauth2/oauth2-resource-server/spring-security-oauth2-resource-server.gradle

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ dependencies {
1414

1515
provided 'jakarta.servlet:jakarta.servlet-api'
1616

17+
testImplementation project(path : ':spring-security-core', configuration : 'tests')
1718
testImplementation project(path: ':spring-security-oauth2-jose', configuration: 'tests')
1819
testImplementation 'com.squareup.okhttp3:mockwebserver'
1920
testImplementation 'com.fasterxml.jackson.core:jackson-databind'
@@ -27,5 +28,6 @@ dependencies {
2728
testImplementation "org.mockito:mockito-junit-jupiter"
2829
testImplementation "org.springframework:spring-test"
2930

31+
3032
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
3133
}

oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/authentication/JwtAuthenticationConverterTests.java

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323

2424
import org.springframework.core.convert.converter.Converter;
2525
import org.springframework.security.authentication.AbstractAuthenticationToken;
26+
import org.springframework.security.authentication.SecurityAssertions;
2627
import org.springframework.security.core.GrantedAuthority;
2728
import org.springframework.security.core.authority.SimpleGrantedAuthority;
2829
import org.springframework.security.oauth2.jwt.Jwt;
@@ -46,9 +47,7 @@ public class JwtAuthenticationConverterTests {
4647
public void convertWhenDefaultGrantedAuthoritiesConverterSet() {
4748
Jwt jwt = TestJwts.jwt().claim("scope", "message:read message:write").build();
4849
AbstractAuthenticationToken authentication = this.jwtAuthenticationConverter.convert(jwt);
49-
Collection<GrantedAuthority> authorities = authentication.getAuthorities();
50-
assertThat(authorities).containsExactly(new SimpleGrantedAuthority("SCOPE_message:read"),
51-
new SimpleGrantedAuthority("SCOPE_message:write"));
50+
SecurityAssertions.assertThat(authentication).hasAuthorities("SCOPE_message:read", "SCOPE_message:write");
5251
}
5352

5453
@Test
@@ -65,8 +64,7 @@ public void convertWithOverriddenGrantedAuthoritiesConverter() {
6564
.asList(new SimpleGrantedAuthority("blah"));
6665
this.jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(grantedAuthoritiesConverter);
6766
AbstractAuthenticationToken authentication = this.jwtAuthenticationConverter.convert(jwt);
68-
Collection<GrantedAuthority> authorities = authentication.getAuthorities();
69-
assertThat(authorities).containsExactly(new SimpleGrantedAuthority("blah"));
67+
SecurityAssertions.assertThat(authentication).hasAuthority("blah");
7068
}
7169

7270
@Test

0 commit comments

Comments
 (0)