Skip to content

Commit 84ba3dd

Browse files
committed
Add oauth2Login MockMvc Support
Fixes gh-7789
1 parent 2df1099 commit 84ba3dd

File tree

4 files changed

+399
-20
lines changed

4 files changed

+399
-20
lines changed

samples/boot/oauth2login/src/integration-test/java/sample/OAuth2LoginApplicationTests.java

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,7 @@
7373
import static org.mockito.ArgumentMatchers.any;
7474
import static org.mockito.Mockito.mock;
7575
import static org.mockito.Mockito.when;
76-
import static org.springframework.security.oauth2.core.oidc.IdTokenClaimNames.SUB;
77-
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.oidcLogin;
76+
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.oauth2Login;
7877
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
7978
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model;
8079

@@ -261,12 +260,12 @@ public void requestAuthorizationCodeGrantWhenInvalidStateParamThenDisplayLoginPa
261260
}
262261

263262
@Test
264-
public void requestWhenMockOidcLoginThenIndex() throws Exception {
263+
public void requestWhenMockOAuth2LoginThenIndex() throws Exception {
265264
ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId("github");
266-
this.mvc.perform(get("/").with(oidcLogin().clientRegistration(clientRegistration)))
265+
this.mvc.perform(get("/").with(oauth2Login().clientRegistration(clientRegistration)))
267266
.andExpect(model().attribute("userName", "test-subject"))
268267
.andExpect(model().attribute("clientName", "GitHub"))
269-
.andExpect(model().attribute("userAttributes", Collections.singletonMap(SUB, "test-subject")));
268+
.andExpect(model().attribute("userAttributes", Collections.singletonMap("sub", "test-subject")));
270269
}
271270

272271
private void assertLoginPage(HtmlPage page) {

samples/boot/oauth2login/src/test/java/sample/web/OAuth2LoginControllerTests.java

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,7 @@
3434
import org.springframework.test.context.junit4.SpringRunner;
3535
import org.springframework.test.web.servlet.MockMvc;
3636

37-
import static org.springframework.security.oauth2.core.oidc.IdTokenClaimNames.SUB;
38-
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.oidcLogin;
37+
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.oauth2Login;
3938
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
4039
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model;
4140

@@ -64,10 +63,10 @@ public OAuth2AuthorizedClientRepository authorizedClientRepository() {
6463

6564
@Test
6665
public void rootWhenAuthenticatedReturnsUserAndClient() throws Exception {
67-
this.mvc.perform(get("/").with(oidcLogin()))
66+
this.mvc.perform(get("/").with(oauth2Login()))
6867
.andExpect(model().attribute("userName", "test-subject"))
6968
.andExpect(model().attribute("clientName", "test"))
70-
.andExpect(model().attribute("userAttributes", Collections.singletonMap(SUB, "test-subject")));
69+
.andExpect(model().attribute("userAttributes", Collections.singletonMap("sub", "test-subject")));
7170
}
7271

7372
@Test
@@ -79,11 +78,11 @@ public void rootWhenOverridingClientRegistrationReturnsAccordingly() throws Exce
7978
.tokenUri("https://token-uri.example.org")
8079
.build();
8180

82-
this.mvc.perform(get("/").with(oidcLogin()
81+
this.mvc.perform(get("/").with(oauth2Login()
8382
.clientRegistration(clientRegistration)
84-
.idToken(i -> i.subject("spring-security"))))
83+
.attributes(a -> a.put("sub", "spring-security"))))
8584
.andExpect(model().attribute("userName", "spring-security"))
8685
.andExpect(model().attribute("clientName", "my-client-name"))
87-
.andExpect(model().attribute("userAttributes", Collections.singletonMap(SUB, "spring-security")));
86+
.andExpect(model().attribute("userAttributes", Collections.singletonMap("sub", "spring-security")));
8887
}
8988
}

test/src/main/java/org/springframework/security/test/web/servlet/request/SecurityMockMvcRequestPostProcessors.java

Lines changed: 181 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import java.util.Map;
3434
import java.util.Set;
3535
import java.util.function.Consumer;
36+
import java.util.function.Supplier;
3637
import java.util.stream.Collectors;
3738
import javax.servlet.http.HttpServletRequest;
3839
import javax.servlet.http.HttpServletResponse;
@@ -70,6 +71,9 @@
7071
import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser;
7172
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
7273
import org.springframework.security.oauth2.core.oidc.user.OidcUserAuthority;
74+
import org.springframework.security.oauth2.core.user.DefaultOAuth2User;
75+
import org.springframework.security.oauth2.core.user.OAuth2User;
76+
import org.springframework.security.oauth2.core.user.OAuth2UserAuthority;
7377
import org.springframework.security.oauth2.jwt.Jwt;
7478
import org.springframework.security.oauth2.server.resource.authentication.BearerTokenAuthentication;
7579
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
@@ -371,8 +375,38 @@ public static RequestPostProcessor httpBasic(String username, String password) {
371375
/**
372376
* Establish a {@link SecurityContext} that has a
373377
* {@link OAuth2AuthenticationToken} for the
374-
* {@link Authentication} and a {@link OAuth2AuthorizedClient} in
375-
* the session. All details are
378+
* {@link Authentication}, a {@link OAuth2User} as the principal,
379+
* and a {@link OAuth2AuthorizedClient} in the session. All details are
380+
* declarative and do not require associated tokens to be valid.
381+
*
382+
* <p>
383+
* The support works by associating the authentication to the HttpServletRequest. To associate
384+
* the request to the SecurityContextHolder you need to ensure that the
385+
* SecurityContextPersistenceFilter is associated with the MockMvc instance. A few
386+
* ways to do this are:
387+
* </p>
388+
*
389+
* <ul>
390+
* <li>Invoking apply {@link SecurityMockMvcConfigurers#springSecurity()}</li>
391+
* <li>Adding Spring Security's FilterChainProxy to MockMvc</li>
392+
* <li>Manually adding {@link SecurityContextPersistenceFilter} to the MockMvc
393+
* instance may make sense when using MockMvcBuilders standaloneSetup</li>
394+
* </ul>
395+
*
396+
* @return the {@link OidcLoginRequestPostProcessor} for additional customization
397+
* @since 5.3
398+
*/
399+
public static OAuth2LoginRequestPostProcessor oauth2Login() {
400+
OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, "access-token",
401+
null, null, Collections.singleton("user"));
402+
return new OAuth2LoginRequestPostProcessor(accessToken);
403+
}
404+
405+
/**
406+
* Establish a {@link SecurityContext} that has a
407+
* {@link OAuth2AuthenticationToken} for the
408+
* {@link Authentication}, a {@link OidcUser} as the principal,
409+
* and a {@link OAuth2AuthorizedClient} in the session. All details are
376410
* declarative and do not require associated tokens to be valid.
377411
*
378412
* <p>
@@ -1248,6 +1282,147 @@ private OAuth2AccessToken getOAuth2AccessToken(OAuth2AuthenticatedPrincipal prin
12481282
}
12491283
}
12501284

1285+
/**
1286+
* @author Josh Cummings
1287+
* @since 5.3
1288+
*/
1289+
public final static class OAuth2LoginRequestPostProcessor implements RequestPostProcessor {
1290+
private ClientRegistration clientRegistration;
1291+
private OAuth2AccessToken accessToken;
1292+
1293+
private Supplier<Collection<GrantedAuthority>> authorities = this::defaultAuthorities;
1294+
private Supplier<Map<String, Object>> attributes = this::defaultAttributes;
1295+
private String nameAttributeKey = "sub";
1296+
private Supplier<OAuth2User> oauth2User = this::defaultPrincipal;
1297+
1298+
private OAuth2LoginRequestPostProcessor(OAuth2AccessToken accessToken) {
1299+
this.accessToken = accessToken;
1300+
this.clientRegistration = clientRegistrationBuilder().build();
1301+
}
1302+
1303+
/**
1304+
* Use the provided authorities in the {@link Authentication}
1305+
*
1306+
* @param authorities the authorities to use
1307+
* @return the {@link OAuth2LoginRequestPostProcessor} for further configuration
1308+
*/
1309+
public OAuth2LoginRequestPostProcessor authorities(Collection<GrantedAuthority> authorities) {
1310+
Assert.notNull(authorities, "authorities cannot be null");
1311+
this.authorities = () -> authorities;
1312+
this.oauth2User = this::defaultPrincipal;
1313+
return this;
1314+
}
1315+
1316+
/**
1317+
* Use the provided authorities in the {@link Authentication}
1318+
*
1319+
* @param authorities the authorities to use
1320+
* @return the {@link OAuth2LoginRequestPostProcessor} for further configuration
1321+
*/
1322+
public OAuth2LoginRequestPostProcessor authorities(GrantedAuthority... authorities) {
1323+
Assert.notNull(authorities, "authorities cannot be null");
1324+
this.authorities = () -> Arrays.asList(authorities);
1325+
this.oauth2User = this::defaultPrincipal;
1326+
return this;
1327+
}
1328+
1329+
/**
1330+
* Mutate the attributes using the given {@link Consumer}
1331+
*
1332+
* @param attributesConsumer The {@link Consumer} for mutating the {@Map} of attributes
1333+
* @return the {@link OAuth2LoginRequestPostProcessor} for further configuration
1334+
*/
1335+
public OAuth2LoginRequestPostProcessor attributes(Consumer<Map<String, Object>> attributesConsumer) {
1336+
Assert.notNull(attributesConsumer, "attributesConsumer cannot be null");
1337+
this.attributes = () -> {
1338+
Map<String, Object> attrs = new HashMap<>();
1339+
attrs.put(this.nameAttributeKey, "test-subject");
1340+
attributesConsumer.accept(attrs);
1341+
return attrs;
1342+
};
1343+
this.oauth2User = this::defaultPrincipal;
1344+
return this;
1345+
}
1346+
1347+
/**
1348+
* Use the provided key for the attribute containing the principal's name
1349+
*
1350+
* @param nameAttributeKey The attribute key to use
1351+
* @return the {@link OAuth2LoginRequestPostProcessor} for further configuration
1352+
*/
1353+
public OAuth2LoginRequestPostProcessor nameAttributeKey(String nameAttributeKey) {
1354+
Assert.notNull(nameAttributeKey, "nameAttributeKey cannot be null");
1355+
this.nameAttributeKey = nameAttributeKey;
1356+
this.oauth2User = this::defaultPrincipal;
1357+
return this;
1358+
}
1359+
1360+
/**
1361+
* Use the provided {@link OAuth2User} as the authenticated user.
1362+
*
1363+
* @param oauth2User the {@link OAuth2User} to use
1364+
* @return the {@link OAuth2LoginRequestPostProcessor} for further configuration
1365+
*/
1366+
public OAuth2LoginRequestPostProcessor oauth2User(OAuth2User oauth2User) {
1367+
this.oauth2User = () -> oauth2User;
1368+
return this;
1369+
}
1370+
1371+
/**
1372+
* Use the provided {@link ClientRegistration} as the client to authorize.
1373+
*
1374+
* The supplied {@link ClientRegistration} will be registered into an
1375+
* {@link HttpSessionOAuth2AuthorizedClientRepository}. Tests relying on
1376+
* {@link org.springframework.security.oauth2.client.annotation.RegisteredOAuth2AuthorizedClient}
1377+
* annotations should register an {@link HttpSessionOAuth2AuthorizedClientRepository} bean
1378+
* to the application context.
1379+
*
1380+
* @param clientRegistration the {@link ClientRegistration} to use
1381+
* @return the {@link OAuth2LoginRequestPostProcessor} for further configuration
1382+
*/
1383+
public OAuth2LoginRequestPostProcessor clientRegistration(ClientRegistration clientRegistration) {
1384+
this.clientRegistration = clientRegistration;
1385+
return this;
1386+
}
1387+
1388+
@Override
1389+
public MockHttpServletRequest postProcessRequest(MockHttpServletRequest request) {
1390+
OAuth2User oauth2User = this.oauth2User.get();
1391+
OAuth2AuthenticationToken token = new OAuth2AuthenticationToken
1392+
(oauth2User, oauth2User.getAuthorities(), this.clientRegistration.getRegistrationId());
1393+
OAuth2AuthorizedClient client = new OAuth2AuthorizedClient
1394+
(this.clientRegistration, token.getName(), this.accessToken);
1395+
OAuth2AuthorizedClientRepository authorizedClientRepository = new HttpSessionOAuth2AuthorizedClientRepository();
1396+
authorizedClientRepository.saveAuthorizedClient(client, token, request, new MockHttpServletResponse());
1397+
1398+
return new AuthenticationRequestPostProcessor(token).postProcessRequest(request);
1399+
}
1400+
1401+
private ClientRegistration.Builder clientRegistrationBuilder() {
1402+
return ClientRegistration.withRegistrationId("test")
1403+
.authorizationGrantType(AuthorizationGrantType.PASSWORD)
1404+
.clientId("test-client")
1405+
.tokenUri("https://token-uri.example.org");
1406+
}
1407+
1408+
private Collection<GrantedAuthority> defaultAuthorities() {
1409+
Set<GrantedAuthority> authorities = new LinkedHashSet<>();
1410+
authorities.add(new OAuth2UserAuthority(this.attributes.get()));
1411+
for (String authority : this.accessToken.getScopes()) {
1412+
authorities.add(new SimpleGrantedAuthority("SCOPE_" + authority));
1413+
}
1414+
return authorities;
1415+
}
1416+
1417+
private Map<String, Object> defaultAttributes() {
1418+
return Collections.singletonMap(this.nameAttributeKey, "test-subject");
1419+
}
1420+
1421+
private OAuth2User defaultPrincipal() {
1422+
return new DefaultOAuth2User(this.authorities.get(), this.attributes.get(), this.nameAttributeKey);
1423+
}
1424+
}
1425+
12511426
/**
12521427
* @author Josh Cummings
12531428
* @since 5.3
@@ -1350,12 +1525,10 @@ public OidcLoginRequestPostProcessor clientRegistration(ClientRegistration clien
13501525
@Override
13511526
public MockHttpServletRequest postProcessRequest(MockHttpServletRequest request) {
13521527
OidcUser oidcUser = getOidcUser();
1353-
OAuth2AuthenticationToken token = new OAuth2AuthenticationToken(oidcUser, oidcUser.getAuthorities(), this.clientRegistration.getRegistrationId());
1354-
OAuth2AuthorizedClient client = new OAuth2AuthorizedClient(this.clientRegistration, token.getName(), this.accessToken);
1355-
OAuth2AuthorizedClientRepository authorizedClientRepository = new HttpSessionOAuth2AuthorizedClientRepository();
1356-
authorizedClientRepository.saveAuthorizedClient(client, token, request, new MockHttpServletResponse());
1357-
1358-
return new AuthenticationRequestPostProcessor(token).postProcessRequest(request);
1528+
return new OAuth2LoginRequestPostProcessor(this.accessToken)
1529+
.oauth2User(oidcUser)
1530+
.clientRegistration(this.clientRegistration)
1531+
.postProcessRequest(request);
13591532
}
13601533

13611534
private ClientRegistration.Builder clientRegistrationBuilder() {

0 commit comments

Comments
 (0)