|
33 | 33 | import java.util.Map;
|
34 | 34 | import java.util.Set;
|
35 | 35 | import java.util.function.Consumer;
|
| 36 | +import java.util.function.Supplier; |
36 | 37 | import java.util.stream.Collectors;
|
37 | 38 | import javax.servlet.http.HttpServletRequest;
|
38 | 39 | import javax.servlet.http.HttpServletResponse;
|
|
70 | 71 | import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser;
|
71 | 72 | import org.springframework.security.oauth2.core.oidc.user.OidcUser;
|
72 | 73 | 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; |
73 | 77 | import org.springframework.security.oauth2.jwt.Jwt;
|
74 | 78 | import org.springframework.security.oauth2.server.resource.authentication.BearerTokenAuthentication;
|
75 | 79 | import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
|
@@ -371,8 +375,38 @@ public static RequestPostProcessor httpBasic(String username, String password) {
|
371 | 375 | /**
|
372 | 376 | * Establish a {@link SecurityContext} that has a
|
373 | 377 | * {@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 |
376 | 410 | * declarative and do not require associated tokens to be valid.
|
377 | 411 | *
|
378 | 412 | * <p>
|
@@ -1248,6 +1282,147 @@ private OAuth2AccessToken getOAuth2AccessToken(OAuth2AuthenticatedPrincipal prin
|
1248 | 1282 | }
|
1249 | 1283 | }
|
1250 | 1284 |
|
| 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 | + |
1251 | 1426 | /**
|
1252 | 1427 | * @author Josh Cummings
|
1253 | 1428 | * @since 5.3
|
@@ -1350,12 +1525,10 @@ public OidcLoginRequestPostProcessor clientRegistration(ClientRegistration clien
|
1350 | 1525 | @Override
|
1351 | 1526 | public MockHttpServletRequest postProcessRequest(MockHttpServletRequest request) {
|
1352 | 1527 | 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); |
1359 | 1532 | }
|
1360 | 1533 |
|
1361 | 1534 | private ClientRegistration.Builder clientRegistrationBuilder() {
|
|
0 commit comments