mirror of
				https://github.com/spring-projects/spring-security.git
				synced 2025-10-26 20:28:44 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			1275 lines
		
	
	
		
			32 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
			
		
		
	
	
			1275 lines
		
	
	
		
			32 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
| [[webflux-testing-oauth2]]
 | |
| = Testing OAuth 2.0
 | |
| 
 | |
| When it comes to OAuth 2.0, xref:reactive/test/method.adoc#test-erms[the same principles covered earlier still apply]: Ultimately, it depends on what your method under test is expecting to be in the `SecurityContextHolder`.
 | |
| 
 | |
| Consider the following example of a controller:
 | |
| 
 | |
| [tabs]
 | |
| ======
 | |
| Java::
 | |
| +
 | |
| [source,java,role="primary"]
 | |
| ----
 | |
| @GetMapping("/endpoint")
 | |
| public Mono<String> foo(Principal user) {
 | |
|     return Mono.just(user.getName());
 | |
| }
 | |
| ----
 | |
| 
 | |
| Kotlin::
 | |
| +
 | |
| [source,kotlin,role="secondary"]
 | |
| ----
 | |
| @GetMapping("/endpoint")
 | |
| fun foo(user: Principal): Mono<String> {
 | |
|     return Mono.just(user.name)
 | |
| }
 | |
| ----
 | |
| ======
 | |
| 
 | |
| Nothing about it is OAuth2-specific, so you can xref:reactive/test/method.adoc#test-erms[use `@WithMockUser`] and be fine.
 | |
| 
 | |
| However, consider a case where your controller is bound to some aspect of Spring Security's OAuth 2.0 support:
 | |
| 
 | |
| [tabs]
 | |
| ======
 | |
| Java::
 | |
| +
 | |
| [source,java,role="primary"]
 | |
| ----
 | |
| @GetMapping("/endpoint")
 | |
| public Mono<String> foo(@AuthenticationPrincipal OidcUser user) {
 | |
|     return Mono.just(user.getIdToken().getSubject());
 | |
| }
 | |
| ----
 | |
| 
 | |
| Kotlin::
 | |
| +
 | |
| [source,kotlin,role="secondary"]
 | |
| ----
 | |
| @GetMapping("/endpoint")
 | |
| fun foo(@AuthenticationPrincipal user: OidcUser): Mono<String> {
 | |
|     return Mono.just(user.idToken.subject)
 | |
| }
 | |
| ----
 | |
| ======
 | |
| 
 | |
| In that case, Spring Security's test support is handy.
 | |
| 
 | |
| [[webflux-testing-oidc-login]]
 | |
| == Testing OIDC Login
 | |
| 
 | |
| Testing the method shown in the <<webflux-testing-oauth2,preceding section>> with `WebTestClient` requires simulating some kind of grant flow with an authorization server.
 | |
| This is a daunting task, which is why Spring Security ships with support for removing this boilerplate.
 | |
| 
 | |
| For example, we can tell Spring Security to include a default `OidcUser` by using the `SecurityMockServerConfigurers#oidcLogin` method:
 | |
| 
 | |
| [tabs]
 | |
| ======
 | |
| Java::
 | |
| +
 | |
| [source,java,role="primary"]
 | |
| ----
 | |
| client
 | |
|     .mutateWith(mockOidcLogin()).get().uri("/endpoint").exchange();
 | |
| ----
 | |
| 
 | |
| Kotlin::
 | |
| +
 | |
| [source,kotlin,role="secondary"]
 | |
| ----
 | |
| client
 | |
|     .mutateWith(mockOidcLogin())
 | |
|     .get().uri("/endpoint")
 | |
|     .exchange()
 | |
| ----
 | |
| ======
 | |
| 
 | |
| That line configures the associated `MockServerRequest` with an `OidcUser` that includes a simple `OidcIdToken`, an `OidcUserInfo`, and a `Collection` of granted authorities.
 | |
| 
 | |
| Specifically, it includes an `OidcIdToken` with a `sub` claim set to `user`:
 | |
| 
 | |
| [tabs]
 | |
| ======
 | |
| Java::
 | |
| +
 | |
| [source,java,role="primary"]
 | |
| ----
 | |
| assertThat(user.getIdToken().getClaim("sub")).isEqualTo("user");
 | |
| ----
 | |
| 
 | |
| Kotlin::
 | |
| +
 | |
| [source,kotlin,role="secondary"]
 | |
| ----
 | |
| assertThat(user.idToken.getClaim<String>("sub")).isEqualTo("user")
 | |
| ----
 | |
| ======
 | |
| 
 | |
| It also includes an `OidcUserInfo` with no claims set:
 | |
| 
 | |
| [tabs]
 | |
| ======
 | |
| Java::
 | |
| +
 | |
| [source,java,role="primary"]
 | |
| ----
 | |
| assertThat(user.getUserInfo().getClaims()).isEmpty();
 | |
| ----
 | |
| 
 | |
| Kotlin::
 | |
| +
 | |
| [source,kotlin,role="secondary"]
 | |
| ----
 | |
| assertThat(user.userInfo.claims).isEmpty()
 | |
| ----
 | |
| ======
 | |
| 
 | |
| It also includes a `Collection` of authorities with just one authority, `SCOPE_read`:
 | |
| 
 | |
| [tabs]
 | |
| ======
 | |
| Java::
 | |
| +
 | |
| [source,java,role="primary"]
 | |
| ----
 | |
| assertThat(user.getAuthorities()).hasSize(1);
 | |
| assertThat(user.getAuthorities()).containsExactly(new SimpleGrantedAuthority("SCOPE_read"));
 | |
| ----
 | |
| 
 | |
| Kotlin::
 | |
| +
 | |
| [source,kotlin,role="secondary"]
 | |
| ----
 | |
| assertThat(user.authorities).hasSize(1)
 | |
| assertThat(user.authorities).containsExactly(SimpleGrantedAuthority("SCOPE_read"))
 | |
| ----
 | |
| ======
 | |
| 
 | |
| Spring Security makes sure that the `OidcUser` instance is available forxref:servlet/integrations/mvc.adoc#mvc-authentication-principal[the `@AuthenticationPrincipal` annotation].
 | |
| 
 | |
| Further, it also links the `OidcUser` to a simple instance of `OAuth2AuthorizedClient` that it deposits into a mock `ServerOAuth2AuthorizedClientRepository`.
 | |
| This can be handy if your tests <<webflux-testing-oauth2-client,use the `@RegisteredOAuth2AuthorizedClient` annotation>>..
 | |
| 
 | |
| [[webflux-testing-oidc-login-authorities]]
 | |
| === Configuring Authorities
 | |
| 
 | |
| In many circumstances, your method is protected by filter or method security and needs your `Authentication` to have certain granted authorities to allow the request.
 | |
| 
 | |
| In those cases, you can supply what granted authorities you need by using the `authorities()` method:
 | |
| 
 | |
| [tabs]
 | |
| ======
 | |
| Java::
 | |
| +
 | |
| [source,java,role="primary"]
 | |
| ----
 | |
| client
 | |
|     .mutateWith(mockOidcLogin()
 | |
|         .authorities(new SimpleGrantedAuthority("SCOPE_message:read"))
 | |
|     )
 | |
|     .get().uri("/endpoint").exchange();
 | |
| ----
 | |
| 
 | |
| Kotlin::
 | |
| +
 | |
| [source,kotlin,role="secondary"]
 | |
| ----
 | |
| client
 | |
|     .mutateWith(mockOidcLogin()
 | |
|         .authorities(SimpleGrantedAuthority("SCOPE_message:read"))
 | |
|     )
 | |
|     .get().uri("/endpoint").exchange()
 | |
| ----
 | |
| ======
 | |
| 
 | |
| [[webflux-testing-oidc-login-claims]]
 | |
| === Configuring Claims
 | |
| 
 | |
| While granted authorities are common across all of Spring Security, we also have claims in the case of OAuth 2.0.
 | |
| 
 | |
| Suppose, for example, that you have a `user_id` claim that indicates the user's ID in your system.
 | |
| You might access it as follows in a controller:
 | |
| 
 | |
| [tabs]
 | |
| ======
 | |
| Java::
 | |
| +
 | |
| [source,java,role="primary"]
 | |
| ----
 | |
| @GetMapping("/endpoint")
 | |
| public Mono<String> foo(@AuthenticationPrincipal OidcUser oidcUser) {
 | |
|     String userId = oidcUser.getIdToken().getClaim("user_id");
 | |
|     // ...
 | |
| }
 | |
| ----
 | |
| 
 | |
| Kotlin::
 | |
| +
 | |
| [source,kotlin,role="secondary"]
 | |
| ----
 | |
| @GetMapping("/endpoint")
 | |
| fun foo(@AuthenticationPrincipal oidcUser: OidcUser): Mono<String> {
 | |
|     val userId = oidcUser.idToken.getClaim<String>("user_id")
 | |
|     // ...
 | |
| }
 | |
| ----
 | |
| ======
 | |
| 
 | |
| In that case, you can specify that claim with the `idToken()` method:
 | |
| 
 | |
| [tabs]
 | |
| ======
 | |
| Java::
 | |
| +
 | |
| [source,java,role="primary"]
 | |
| ----
 | |
| client
 | |
|     .mutateWith(mockOidcLogin()
 | |
|         .idToken(token -> token.claim("user_id", "1234"))
 | |
|     )
 | |
|     .get().uri("/endpoint").exchange();
 | |
| ----
 | |
| 
 | |
| Kotlin::
 | |
| +
 | |
| [source,kotlin,role="secondary"]
 | |
| ----
 | |
| client
 | |
|     .mutateWith(mockOidcLogin()
 | |
|         .idToken { token -> token.claim("user_id", "1234") }
 | |
|     )
 | |
|     .get().uri("/endpoint").exchange()
 | |
| ----
 | |
| ======
 | |
| 
 | |
| That works because `OidcUser` collects its claims from `OidcIdToken`.
 | |
| 
 | |
| [[webflux-testing-oidc-login-user]]
 | |
| === Additional Configurations
 | |
| 
 | |
| There are additional methods, too, for further configuring the authentication, depending on what data your controller expects:
 | |
| 
 | |
| * `userInfo(OidcUserInfo.Builder)`: Configures the `OidcUserInfo` instance
 | |
| * `clientRegistration(ClientRegistration)`: Configures the associated `OAuth2AuthorizedClient` with a given `ClientRegistration`
 | |
| * `oidcUser(OidcUser)`: Configures the complete `OidcUser` instance
 | |
| 
 | |
| That last one is handy if you:
 | |
| * Have your own implementation of `OidcUser` or
 | |
| * Need to change the name attribute
 | |
| 
 | |
| For example, suppose that your authorization server sends the principal name in the `user_name` claim instead of the `sub` claim.
 | |
| In that case, you can configure an `OidcUser` by hand:
 | |
| 
 | |
| [tabs]
 | |
| ======
 | |
| Java::
 | |
| +
 | |
| [source,java,role="primary"]
 | |
| ----
 | |
| OidcUser oidcUser = new DefaultOidcUser(
 | |
|         AuthorityUtils.createAuthorityList("SCOPE_message:read"),
 | |
|         OidcIdToken.withTokenValue("id-token").claim("user_name", "foo_user").build(),
 | |
|         "user_name");
 | |
| 
 | |
| client
 | |
|     .mutateWith(mockOidcLogin().oidcUser(oidcUser))
 | |
|     .get().uri("/endpoint").exchange();
 | |
| ----
 | |
| 
 | |
| Kotlin::
 | |
| +
 | |
| [source,kotlin,role="secondary"]
 | |
| ----
 | |
| val oidcUser: OidcUser = DefaultOidcUser(
 | |
|     AuthorityUtils.createAuthorityList("SCOPE_message:read"),
 | |
|     OidcIdToken.withTokenValue("id-token").claim("user_name", "foo_user").build(),
 | |
|     "user_name"
 | |
| )
 | |
| 
 | |
| client
 | |
|     .mutateWith(mockOidcLogin().oidcUser(oidcUser))
 | |
|     .get().uri("/endpoint").exchange()
 | |
| ----
 | |
| ======
 | |
| 
 | |
| [[webflux-testing-oauth2-login]]
 | |
| == Testing OAuth 2.0 Login
 | |
| 
 | |
| As with <<webflux-testing-oidc-login,testing OIDC login>>, testing OAuth 2.0 Login presents a similar challenge: mocking a grant flow.
 | |
| Because of that, Spring Security also has test support for non-OIDC use cases.
 | |
| 
 | |
| Suppose that we have a controller that gets the logged-in user as an `OAuth2User`:
 | |
| 
 | |
| [tabs]
 | |
| ======
 | |
| Java::
 | |
| +
 | |
| [source,java,role="primary"]
 | |
| ----
 | |
| @GetMapping("/endpoint")
 | |
| public Mono<String> foo(@AuthenticationPrincipal OAuth2User oauth2User) {
 | |
|     return Mono.just(oauth2User.getAttribute("sub"));
 | |
| }
 | |
| ----
 | |
| 
 | |
| Kotlin::
 | |
| +
 | |
| [source,kotlin,role="secondary"]
 | |
| ----
 | |
| @GetMapping("/endpoint")
 | |
| fun foo(@AuthenticationPrincipal oauth2User: OAuth2User): Mono<String> {
 | |
|     return Mono.just(oauth2User.getAttribute("sub"))
 | |
| }
 | |
| ----
 | |
| ======
 | |
| 
 | |
| In that case, we can tell Spring Security to include a default `OAuth2User` by using the `SecurityMockServerConfigurers#oauth2User` method:
 | |
| 
 | |
| [tabs]
 | |
| ======
 | |
| Java::
 | |
| +
 | |
| [source,java,role="primary"]
 | |
| ----
 | |
| client
 | |
|     .mutateWith(mockOAuth2Login())
 | |
|     .get().uri("/endpoint").exchange();
 | |
| ----
 | |
| 
 | |
| Kotlin::
 | |
| +
 | |
| [source,kotlin,role="secondary"]
 | |
| ----
 | |
| client
 | |
|     .mutateWith(mockOAuth2Login())
 | |
|     .get().uri("/endpoint").exchange()
 | |
| ----
 | |
| ======
 | |
| 
 | |
| The preceding example configures the associated `MockServerRequest` with an `OAuth2User` that includes a simple `Map` of attributes and a `Collection` of granted authorities.
 | |
| 
 | |
| Specifically, it includes a `Map` with a key/value pair of `sub`/`user`:
 | |
| 
 | |
| [tabs]
 | |
| ======
 | |
| Java::
 | |
| +
 | |
| [source,java,role="primary"]
 | |
| ----
 | |
| assertThat((String) user.getAttribute("sub")).isEqualTo("user");
 | |
| ----
 | |
| 
 | |
| Kotlin::
 | |
| +
 | |
| [source,kotlin,role="secondary"]
 | |
| ----
 | |
| assertThat(user.getAttribute<String>("sub")).isEqualTo("user")
 | |
| ----
 | |
| ======
 | |
| 
 | |
| It also includes a `Collection` of authorities with just one authority, `SCOPE_read`:
 | |
| 
 | |
| [tabs]
 | |
| ======
 | |
| Java::
 | |
| +
 | |
| [source,java,role="primary"]
 | |
| ----
 | |
| assertThat(user.getAuthorities()).hasSize(1);
 | |
| assertThat(user.getAuthorities()).containsExactly(new SimpleGrantedAuthority("SCOPE_read"));
 | |
| ----
 | |
| 
 | |
| Kotlin::
 | |
| +
 | |
| [source,kotlin,role="secondary"]
 | |
| ----
 | |
| assertThat(user.authorities).hasSize(1)
 | |
| assertThat(user.authorities).containsExactly(SimpleGrantedAuthority("SCOPE_read"))
 | |
| ----
 | |
| ======
 | |
| 
 | |
| Spring Security does the necessary work to make sure that the `OAuth2User` instance is available for xref:servlet/integrations/mvc.adoc#mvc-authentication-principal[the `@AuthenticationPrincipal` annotation].
 | |
| 
 | |
| Further, it also links that `OAuth2User` to a simple instance of `OAuth2AuthorizedClient` that it deposits in a mock `ServerOAuth2AuthorizedClientRepository`.
 | |
| This can be handy if your tests <<webflux-testing-oauth2-client,use the `@RegisteredOAuth2AuthorizedClient` annotation>>.
 | |
| 
 | |
| [[webflux-testing-oauth2-login-authorities]]
 | |
| === Configuring Authorities
 | |
| 
 | |
| In many circumstances, your method is protected by filter or method security and needs your `Authentication` to have certain granted authorities to allow the request.
 | |
| 
 | |
| In this case, you can supply the granted authorities you need by using the `authorities()` method:
 | |
| 
 | |
| [tabs]
 | |
| ======
 | |
| Java::
 | |
| +
 | |
| [source,java,role="primary"]
 | |
| ----
 | |
| client
 | |
|     .mutateWith(mockOAuth2Login()
 | |
|         .authorities(new SimpleGrantedAuthority("SCOPE_message:read"))
 | |
|     )
 | |
|     .get().uri("/endpoint").exchange();
 | |
| ----
 | |
| 
 | |
| Kotlin::
 | |
| +
 | |
| [source,kotlin,role="secondary"]
 | |
| ----
 | |
| client
 | |
|     .mutateWith(mockOAuth2Login()
 | |
|         .authorities(SimpleGrantedAuthority("SCOPE_message:read"))
 | |
|     )
 | |
|     .get().uri("/endpoint").exchange()
 | |
| ----
 | |
| ======
 | |
| 
 | |
| [[webflux-testing-oauth2-login-claims]]
 | |
| === Configuring Claims
 | |
| 
 | |
| While granted authorities are quite common across all of Spring Security, we also have claims in the case of OAuth 2.0.
 | |
| 
 | |
| Suppose, for example, that you have a `user_id` attribute that indicates the user's ID in your system.
 | |
| You might access it as follows in a controller:
 | |
| 
 | |
| [tabs]
 | |
| ======
 | |
| Java::
 | |
| +
 | |
| [source,java,role="primary"]
 | |
| ----
 | |
| @GetMapping("/endpoint")
 | |
| public Mono<String> foo(@AuthenticationPrincipal OAuth2User oauth2User) {
 | |
|     String userId = oauth2User.getAttribute("user_id");
 | |
|     // ...
 | |
| }
 | |
| ----
 | |
| 
 | |
| Kotlin::
 | |
| +
 | |
| [source,kotlin,role="secondary"]
 | |
| ----
 | |
| @GetMapping("/endpoint")
 | |
| fun foo(@AuthenticationPrincipal oauth2User: OAuth2User): Mono<String> {
 | |
|     val userId = oauth2User.getAttribute<String>("user_id")
 | |
|     // ...
 | |
| }
 | |
| ----
 | |
| ======
 | |
| 
 | |
| In that case, you can specify that attribute with the `attributes()` method:
 | |
| 
 | |
| [tabs]
 | |
| ======
 | |
| Java::
 | |
| +
 | |
| [source,java,role="primary"]
 | |
| ----
 | |
| client
 | |
|     .mutateWith(mockOAuth2Login()
 | |
|         .attributes(attrs -> attrs.put("user_id", "1234"))
 | |
|     )
 | |
|     .get().uri("/endpoint").exchange();
 | |
| ----
 | |
| 
 | |
| Kotlin::
 | |
| +
 | |
| [source,kotlin,role="secondary"]
 | |
| ----
 | |
| client
 | |
|     .mutateWith(mockOAuth2Login()
 | |
|         .attributes { attrs -> attrs["user_id"] = "1234" }
 | |
|     )
 | |
|     .get().uri("/endpoint").exchange()
 | |
| ----
 | |
| ======
 | |
| 
 | |
| [[webflux-testing-oauth2-login-user]]
 | |
| === Additional Configurations
 | |
| 
 | |
| There are additional methods, too, for further configuring the authentication, depending on what data your controller expects:
 | |
| 
 | |
| * `clientRegistration(ClientRegistration)`: Configures the associated `OAuth2AuthorizedClient` with a given `ClientRegistration`
 | |
| * `oauth2User(OAuth2User)`: Configures the complete `OAuth2User` instance
 | |
| 
 | |
| That last one is handy if you:
 | |
| * Have your own implementation of `OAuth2User` or
 | |
| * Need to change the name attribute
 | |
| 
 | |
| For example, suppose that your authorization server sends the principal name in the `user_name` claim instead of the `sub` claim.
 | |
| In that case, you can configure an `OAuth2User` by hand:
 | |
| 
 | |
| [tabs]
 | |
| ======
 | |
| Java::
 | |
| +
 | |
| [source,java,role="primary"]
 | |
| ----
 | |
| OAuth2User oauth2User = new DefaultOAuth2User(
 | |
|         AuthorityUtils.createAuthorityList("SCOPE_message:read"),
 | |
|         Collections.singletonMap("user_name", "foo_user"),
 | |
|         "user_name");
 | |
| 
 | |
| client
 | |
|     .mutateWith(mockOAuth2Login().oauth2User(oauth2User))
 | |
|     .get().uri("/endpoint").exchange();
 | |
| ----
 | |
| 
 | |
| Kotlin::
 | |
| +
 | |
| [source,kotlin,role="secondary"]
 | |
| ----
 | |
| val oauth2User: OAuth2User = DefaultOAuth2User(
 | |
|     AuthorityUtils.createAuthorityList("SCOPE_message:read"),
 | |
|     mapOf(Pair("user_name", "foo_user")),
 | |
|     "user_name"
 | |
| )
 | |
| 
 | |
| client
 | |
|     .mutateWith(mockOAuth2Login().oauth2User(oauth2User))
 | |
|     .get().uri("/endpoint").exchange()
 | |
| ----
 | |
| ======
 | |
| 
 | |
| [[webflux-testing-oauth2-client]]
 | |
| == Testing OAuth 2.0 Clients
 | |
| 
 | |
| Independent of how your user authenticates, you may have other tokens and client registrations that are in play for the request you are testing.
 | |
| For example, your controller may rely on the client credentials grant to get a token that is not associated with the user at all:
 | |
| 
 | |
| [tabs]
 | |
| ======
 | |
| Java::
 | |
| +
 | |
| [source,java,role="primary"]
 | |
| ----
 | |
| @GetMapping("/endpoint")
 | |
| public Mono<String> foo(@RegisteredOAuth2AuthorizedClient("my-app") OAuth2AuthorizedClient authorizedClient) {
 | |
|     return this.webClient.get()
 | |
|         .attributes(oauth2AuthorizedClient(authorizedClient))
 | |
|         .retrieve()
 | |
|         .bodyToMono(String.class);
 | |
| }
 | |
| ----
 | |
| 
 | |
| Kotlin::
 | |
| +
 | |
| [source,kotlin,role="secondary"]
 | |
| ----
 | |
| import org.springframework.web.reactive.function.client.bodyToMono
 | |
| 
 | |
| // ...
 | |
| 
 | |
| @GetMapping("/endpoint")
 | |
| fun foo(@RegisteredOAuth2AuthorizedClient("my-app") authorizedClient: OAuth2AuthorizedClient?): Mono<String> {
 | |
|     return this.webClient.get()
 | |
|         .attributes(oauth2AuthorizedClient(authorizedClient))
 | |
|         .retrieve()
 | |
|         .bodyToMono()
 | |
| }
 | |
| ----
 | |
| ======
 | |
| 
 | |
| Simulating this handshake with the authorization server can be cumbersome.
 | |
| Instead, you can use `SecurityMockServerConfigurers#oauth2Client` to add a `OAuth2AuthorizedClient` to a mock `ServerOAuth2AuthorizedClientRepository`:
 | |
| 
 | |
| [tabs]
 | |
| ======
 | |
| Java::
 | |
| +
 | |
| [source,java,role="primary"]
 | |
| ----
 | |
| client
 | |
|     .mutateWith(mockOAuth2Client("my-app"))
 | |
|     .get().uri("/endpoint").exchange();
 | |
| ----
 | |
| 
 | |
| Kotlin::
 | |
| +
 | |
| [source,kotlin,role="secondary"]
 | |
| ----
 | |
| client
 | |
|     .mutateWith(mockOAuth2Client("my-app"))
 | |
|     .get().uri("/endpoint").exchange()
 | |
| ----
 | |
| ======
 | |
| 
 | |
| This creates an `OAuth2AuthorizedClient` that has a simple `ClientRegistration`, a `OAuth2AccessToken`, and a resource owner name.
 | |
| 
 | |
| Specifically, it includes a `ClientRegistration` with a client ID of `test-client` and a client secret of `test-secret`:
 | |
| 
 | |
| [tabs]
 | |
| ======
 | |
| Java::
 | |
| +
 | |
| [source,java,role="primary"]
 | |
| ----
 | |
| assertThat(authorizedClient.getClientRegistration().getClientId()).isEqualTo("test-client");
 | |
| assertThat(authorizedClient.getClientRegistration().getClientSecret()).isEqualTo("test-secret");
 | |
| ----
 | |
| 
 | |
| Kotlin::
 | |
| +
 | |
| [source,kotlin,role="secondary"]
 | |
| ----
 | |
| assertThat(authorizedClient.clientRegistration.clientId).isEqualTo("test-client")
 | |
| assertThat(authorizedClient.clientRegistration.clientSecret).isEqualTo("test-secret")
 | |
| ----
 | |
| ======
 | |
| 
 | |
| It also includes a resource owner name of `user`:
 | |
| 
 | |
| [tabs]
 | |
| ======
 | |
| Java::
 | |
| +
 | |
| [source,java,role="primary"]
 | |
| ----
 | |
| assertThat(authorizedClient.getPrincipalName()).isEqualTo("user");
 | |
| ----
 | |
| 
 | |
| Kotlin::
 | |
| +
 | |
| [source,kotlin,role="secondary"]
 | |
| ----
 | |
| assertThat(authorizedClient.principalName).isEqualTo("user")
 | |
| ----
 | |
| ======
 | |
| 
 | |
| It also includes an `OAuth2AccessToken` with one scope, `read`:
 | |
| 
 | |
| [tabs]
 | |
| ======
 | |
| Java::
 | |
| +
 | |
| [source,java,role="primary"]
 | |
| ----
 | |
| assertThat(authorizedClient.getAccessToken().getScopes()).hasSize(1);
 | |
| assertThat(authorizedClient.getAccessToken().getScopes()).containsExactly("read");
 | |
| ----
 | |
| 
 | |
| Kotlin::
 | |
| +
 | |
| [source,kotlin,role="secondary"]
 | |
| ----
 | |
| assertThat(authorizedClient.accessToken.scopes).hasSize(1)
 | |
| assertThat(authorizedClient.accessToken.scopes).containsExactly("read")
 | |
| ----
 | |
| ======
 | |
| 
 | |
| You can then retrieve the client as usual by using `@RegisteredOAuth2AuthorizedClient` in a controller method.
 | |
| 
 | |
| [[webflux-testing-oauth2-client-scopes]]
 | |
| === Configuring Scopes
 | |
| 
 | |
| In many circumstances, the OAuth 2.0 access token comes with a set of scopes.
 | |
| Consider the following example of how a controller can inspect the scopes:
 | |
| 
 | |
| [tabs]
 | |
| ======
 | |
| Java::
 | |
| +
 | |
| [source,java,role="primary"]
 | |
| ----
 | |
| @GetMapping("/endpoint")
 | |
| public Mono<String> foo(@RegisteredOAuth2AuthorizedClient("my-app") OAuth2AuthorizedClient authorizedClient) {
 | |
|     Set<String> scopes = authorizedClient.getAccessToken().getScopes();
 | |
|     if (scopes.contains("message:read")) {
 | |
|         return this.webClient.get()
 | |
|             .attributes(oauth2AuthorizedClient(authorizedClient))
 | |
|             .retrieve()
 | |
|             .bodyToMono(String.class);
 | |
|     }
 | |
|     // ...
 | |
| }
 | |
| ----
 | |
| 
 | |
| Kotlin::
 | |
| +
 | |
| [source,kotlin,role="secondary"]
 | |
| ----
 | |
| import org.springframework.web.reactive.function.client.bodyToMono
 | |
| 
 | |
| // ...
 | |
| 
 | |
| @GetMapping("/endpoint")
 | |
| fun foo(@RegisteredOAuth2AuthorizedClient("my-app") authorizedClient: OAuth2AuthorizedClient): Mono<String> {
 | |
|     val scopes = authorizedClient.accessToken.scopes
 | |
|     if (scopes.contains("message:read")) {
 | |
|         return webClient.get()
 | |
|             .attributes(oauth2AuthorizedClient(authorizedClient))
 | |
|             .retrieve()
 | |
|             .bodyToMono()
 | |
|     }
 | |
|     // ...
 | |
| }
 | |
| ----
 | |
| ======
 | |
| 
 | |
| Given a controller that inspects scopes, you can configure the scope by using the `accessToken()` method:
 | |
| 
 | |
| [tabs]
 | |
| ======
 | |
| Java::
 | |
| +
 | |
| [source,java,role="primary"]
 | |
| ----
 | |
| client
 | |
|     .mutateWith(mockOAuth2Client("my-app")
 | |
|         .accessToken(new OAuth2AccessToken(BEARER, "token", null, null, Collections.singleton("message:read")))
 | |
|     )
 | |
|     .get().uri("/endpoint").exchange();
 | |
| ----
 | |
| 
 | |
| Kotlin::
 | |
| +
 | |
| [source,kotlin,role="secondary"]
 | |
| ----
 | |
| client
 | |
|     .mutateWith(mockOAuth2Client("my-app")
 | |
|         .accessToken(OAuth2AccessToken(BEARER, "token", null, null, setOf("message:read")))
 | |
| )
 | |
| .get().uri("/endpoint").exchange()
 | |
| ----
 | |
| ======
 | |
| 
 | |
| [[webflux-testing-oauth2-client-registration]]
 | |
| === Additional Configurations
 | |
| 
 | |
| You can also use additional methods to further configure the authentication depending on what data your controller expects:
 | |
| 
 | |
| * `principalName(String)`; Configures the resource owner name
 | |
| * `clientRegistration(Consumer<ClientRegistration.Builder>)`: Configures the associated `ClientRegistration`
 | |
| * `clientRegistration(ClientRegistration)`: Configures the complete `ClientRegistration`
 | |
| 
 | |
| That last one is handy if you want to use a real `ClientRegistration`
 | |
| 
 | |
| For example, suppose that you want to use one of your application's `ClientRegistration` definitions, as specified in your `application.yml`.
 | |
| 
 | |
| In that case, your test can autowire the `ReactiveClientRegistrationRepository` and look up the one your test needs:
 | |
| 
 | |
| [tabs]
 | |
| ======
 | |
| Java::
 | |
| +
 | |
| [source,java,role="primary"]
 | |
| ----
 | |
| @Autowired
 | |
| ReactiveClientRegistrationRepository clientRegistrationRepository;
 | |
| 
 | |
| // ...
 | |
| 
 | |
| client
 | |
|     .mutateWith(mockOAuth2Client()
 | |
|         .clientRegistration(this.clientRegistrationRepository.findByRegistrationId("facebook").block())
 | |
|     )
 | |
|     .get().uri("/exchange").exchange();
 | |
| ----
 | |
| 
 | |
| Kotlin::
 | |
| +
 | |
| [source,kotlin,role="secondary"]
 | |
| ----
 | |
| @Autowired
 | |
| lateinit var clientRegistrationRepository: ReactiveClientRegistrationRepository
 | |
| 
 | |
| // ...
 | |
| 
 | |
| client
 | |
|     .mutateWith(mockOAuth2Client()
 | |
|         .clientRegistration(this.clientRegistrationRepository.findByRegistrationId("facebook").block())
 | |
|     )
 | |
|     .get().uri("/exchange").exchange()
 | |
| ----
 | |
| ======
 | |
| 
 | |
| [[webflux-testing-jwt]]
 | |
| == Testing JWT Authentication
 | |
| 
 | |
| To make an authorized request on a resource server, you need a bearer token.
 | |
| If your resource server is configured for JWTs, the bearer token needs to be signed and then encoded according to the JWT specification.
 | |
| All of this can be quite daunting, especially when this is not the focus of your test.
 | |
| 
 | |
| Fortunately, there are a number of simple ways in which you can overcome this difficulty and let your tests focus on authorization and not on representing bearer tokens.
 | |
| We look at two of them in the next two subsections.
 | |
| 
 | |
| === `mockJwt() WebTestClientConfigurer`
 | |
| 
 | |
| The first way is with a `WebTestClientConfigurer`.
 | |
| The simplest of these would be to use the `SecurityMockServerConfigurers#mockJwt` method like the following:
 | |
| 
 | |
| [tabs]
 | |
| ======
 | |
| Java::
 | |
| +
 | |
| [source,java,role="primary"]
 | |
| ----
 | |
| client
 | |
|     .mutateWith(mockJwt()).get().uri("/endpoint").exchange();
 | |
| ----
 | |
| 
 | |
| Kotlin::
 | |
| +
 | |
| [source,kotlin,role="secondary"]
 | |
| ----
 | |
| client
 | |
|     .mutateWith(mockJwt()).get().uri("/endpoint").exchange()
 | |
| ----
 | |
| ======
 | |
| 
 | |
| This example creates a mock `Jwt` and passes it through any authentication APIs so that it is available for your authorization mechanisms to verify.
 | |
| 
 | |
| By default, the `JWT` that it creates has the following characteristics:
 | |
| 
 | |
| [source,json]
 | |
| ----
 | |
| {
 | |
|   "headers" : { "alg" : "none" },
 | |
|   "claims" : {
 | |
|     "sub" : "user",
 | |
|     "scope" : "read"
 | |
|   }
 | |
| }
 | |
| ----
 | |
| 
 | |
| The resulting `Jwt`, were it tested, would pass in the following way:
 | |
| 
 | |
| [tabs]
 | |
| ======
 | |
| Java::
 | |
| +
 | |
| [source,java,role="primary"]
 | |
| ----
 | |
| assertThat(jwt.getTokenValue()).isEqualTo("token");
 | |
| assertThat(jwt.getHeaders().get("alg")).isEqualTo("none");
 | |
| assertThat(jwt.getSubject()).isEqualTo("sub");
 | |
| ----
 | |
| 
 | |
| Kotlin::
 | |
| +
 | |
| [source,kotlin,role="secondary"]
 | |
| ----
 | |
| assertThat(jwt.tokenValue).isEqualTo("token")
 | |
| assertThat(jwt.headers["alg"]).isEqualTo("none")
 | |
| assertThat(jwt.subject).isEqualTo("sub")
 | |
| ----
 | |
| ======
 | |
| 
 | |
| Note that you configure these values.
 | |
| 
 | |
| You can also configure any headers or claims with their corresponding methods:
 | |
| 
 | |
| [tabs]
 | |
| ======
 | |
| Java::
 | |
| +
 | |
| [source,java,role="primary"]
 | |
| ----
 | |
| client
 | |
| 	.mutateWith(mockJwt().jwt(jwt -> jwt.header("kid", "one")
 | |
| 		.claim("iss", "https://idp.example.org")))
 | |
| 	.get().uri("/endpoint").exchange();
 | |
| ----
 | |
| 
 | |
| Kotlin::
 | |
| +
 | |
| [source,kotlin,role="secondary"]
 | |
| ----
 | |
| client
 | |
|     .mutateWith(mockJwt().jwt { jwt -> jwt.header("kid", "one")
 | |
|         .claim("iss", "https://idp.example.org")
 | |
|     })
 | |
|     .get().uri("/endpoint").exchange()
 | |
| ----
 | |
| ======
 | |
| 
 | |
| [tabs]
 | |
| ======
 | |
| Java::
 | |
| +
 | |
| [source,java,role="primary"]
 | |
| ----
 | |
| client
 | |
| 	.mutateWith(mockJwt().jwt(jwt -> jwt.claims(claims -> claims.remove("scope"))))
 | |
| 	.get().uri("/endpoint").exchange();
 | |
| ----
 | |
| 
 | |
| Kotlin::
 | |
| +
 | |
| [source,kotlin,role="secondary"]
 | |
| ----
 | |
| client
 | |
|     .mutateWith(mockJwt().jwt { jwt ->
 | |
|         jwt.claims { claims -> claims.remove("scope") }
 | |
|     })
 | |
|     .get().uri("/endpoint").exchange()
 | |
| ----
 | |
| ======
 | |
| 
 | |
| The `scope` and `scp` claims are processed the same way here as they are in a normal bearer token request.
 | |
| However, this can be overridden simply by providing the list of `GrantedAuthority` instances that you need for your test:
 | |
| 
 | |
| [tabs]
 | |
| ======
 | |
| Java::
 | |
| +
 | |
| [source,java,role="primary"]
 | |
| ----
 | |
| client
 | |
| 	.mutateWith(mockJwt().authorities(new SimpleGrantedAuthority("SCOPE_messages")))
 | |
| 	.get().uri("/endpoint").exchange();
 | |
| ----
 | |
| 
 | |
| Kotlin::
 | |
| +
 | |
| [source,kotlin,role="secondary"]
 | |
| ----
 | |
| client
 | |
|     .mutateWith(mockJwt().authorities(SimpleGrantedAuthority("SCOPE_messages")))
 | |
|     .get().uri("/endpoint").exchange()
 | |
| ----
 | |
| ======
 | |
| 
 | |
| Alternatively, if you have a custom `Jwt` to `Collection<GrantedAuthority>` converter, you can also use that to derive the authorities:
 | |
| 
 | |
| [tabs]
 | |
| ======
 | |
| Java::
 | |
| +
 | |
| [source,java,role="primary"]
 | |
| ----
 | |
| client
 | |
| 	.mutateWith(mockJwt().authorities(new MyConverter()))
 | |
| 	.get().uri("/endpoint").exchange();
 | |
| ----
 | |
| 
 | |
| Kotlin::
 | |
| +
 | |
| [source,kotlin,role="secondary"]
 | |
| ----
 | |
| client
 | |
|     .mutateWith(mockJwt().authorities(MyConverter()))
 | |
|     .get().uri("/endpoint").exchange()
 | |
| ----
 | |
| ======
 | |
| 
 | |
| You can also specify a complete `Jwt`, for which javadoc:org.springframework.security.oauth2.jwt.Jwt$Builder[] is quite handy:
 | |
| 
 | |
| [tabs]
 | |
| ======
 | |
| Java::
 | |
| +
 | |
| [source,java,role="primary"]
 | |
| ----
 | |
| Jwt jwt = Jwt.withTokenValue("token")
 | |
|     .header("alg", "none")
 | |
|     .claim("sub", "user")
 | |
|     .claim("scope", "read")
 | |
|     .build();
 | |
| 
 | |
| client
 | |
| 	.mutateWith(mockJwt().jwt(jwt))
 | |
| 	.get().uri("/endpoint").exchange();
 | |
| ----
 | |
| 
 | |
| Kotlin::
 | |
| +
 | |
| [source,kotlin,role="secondary"]
 | |
| ----
 | |
| val jwt: Jwt = Jwt.withTokenValue("token")
 | |
|     .header("alg", "none")
 | |
|     .claim("sub", "user")
 | |
|     .claim("scope", "read")
 | |
|     .build()
 | |
| 
 | |
| client
 | |
|     .mutateWith(mockJwt().jwt(jwt))
 | |
|     .get().uri("/endpoint").exchange()
 | |
| ----
 | |
| ======
 | |
| 
 | |
| === `authentication()` and `WebTestClientConfigurer`
 | |
| 
 | |
| The second way is by using the `authentication()` `Mutator`.
 | |
| You can instantiate your own `JwtAuthenticationToken` and provide it in your test:
 | |
| 
 | |
| [tabs]
 | |
| ======
 | |
| Java::
 | |
| +
 | |
| [source,java,role="primary"]
 | |
| ----
 | |
| Jwt jwt = Jwt.withTokenValue("token")
 | |
|     .header("alg", "none")
 | |
|     .claim("sub", "user")
 | |
|     .build();
 | |
| Collection<GrantedAuthority> authorities = AuthorityUtils.createAuthorityList("SCOPE_read");
 | |
| JwtAuthenticationToken token = new JwtAuthenticationToken(jwt, authorities);
 | |
| 
 | |
| client
 | |
| 	.mutateWith(mockAuthentication(token))
 | |
| 	.get().uri("/endpoint").exchange();
 | |
| ----
 | |
| 
 | |
| Kotlin::
 | |
| +
 | |
| [source,kotlin,role="secondary"]
 | |
| ----
 | |
| val jwt = Jwt.withTokenValue("token")
 | |
|     .header("alg", "none")
 | |
|     .claim("sub", "user")
 | |
|     .build()
 | |
| val authorities: Collection<GrantedAuthority> = AuthorityUtils.createAuthorityList("SCOPE_read")
 | |
| val token = JwtAuthenticationToken(jwt, authorities)
 | |
| 
 | |
| client
 | |
|     .mutateWith(mockAuthentication<JwtMutator>(token))
 | |
|     .get().uri("/endpoint").exchange()
 | |
| ----
 | |
| ======
 | |
| 
 | |
| Note that, as an alternative to these, you can also mock the `ReactiveJwtDecoder` bean itself with a `@MockBean` annotation.
 | |
| 
 | |
| [[webflux-testing-opaque-token]]
 | |
| == Testing Opaque Token Authentication
 | |
| 
 | |
| Similar to <<webflux-testing-jwt,JWTs>>, opaque tokens require an authorization server in order to verify their validity, which can make testing more difficult.
 | |
| To help with that, Spring Security has test support for opaque tokens.
 | |
| 
 | |
| Suppose you have a controller that retrieves the authentication as a `BearerTokenAuthentication`:
 | |
| 
 | |
| [tabs]
 | |
| ======
 | |
| Java::
 | |
| +
 | |
| [source,java,role="primary"]
 | |
| ----
 | |
| @GetMapping("/endpoint")
 | |
| public Mono<String> foo(BearerTokenAuthentication authentication) {
 | |
|     return Mono.just((String) authentication.getTokenAttributes().get("sub"));
 | |
| }
 | |
| ----
 | |
| 
 | |
| Kotlin::
 | |
| +
 | |
| [source,kotlin,role="secondary"]
 | |
| ----
 | |
| @GetMapping("/endpoint")
 | |
| fun foo(authentication: BearerTokenAuthentication): Mono<String?> {
 | |
|     return Mono.just(authentication.tokenAttributes["sub"] as String?)
 | |
| }
 | |
| ----
 | |
| ======
 | |
| 
 | |
| In that case, you can tell Spring Security to include a default `BearerTokenAuthentication` by using the `SecurityMockServerConfigurers#opaqueToken` method:
 | |
| 
 | |
| [tabs]
 | |
| ======
 | |
| Java::
 | |
| +
 | |
| [source,java,role="primary"]
 | |
| ----
 | |
| client
 | |
|     .mutateWith(mockOpaqueToken())
 | |
|     .get().uri("/endpoint").exchange();
 | |
| ----
 | |
| 
 | |
| Kotlin::
 | |
| +
 | |
| [source,kotlin,role="secondary"]
 | |
| ----
 | |
| client
 | |
|     .mutateWith(mockOpaqueToken())
 | |
|     .get().uri("/endpoint").exchange()
 | |
| ----
 | |
| ======
 | |
| 
 | |
| This example configures the associated `MockHttpServletRequest` with a `BearerTokenAuthentication` that includes a simple `OAuth2AuthenticatedPrincipal`, a `Map` of attributes, and a `Collection` of granted authorities.
 | |
| 
 | |
| Specifically, it includes a `Map` with a key/value pair of `sub`/`user`:
 | |
| 
 | |
| [tabs]
 | |
| ======
 | |
| Java::
 | |
| +
 | |
| [source,java,role="primary"]
 | |
| ----
 | |
| assertThat((String) token.getTokenAttributes().get("sub")).isEqualTo("user");
 | |
| ----
 | |
| 
 | |
| Kotlin::
 | |
| +
 | |
| [source,kotlin,role="secondary"]
 | |
| ----
 | |
| assertThat(token.tokenAttributes["sub"] as String?).isEqualTo("user")
 | |
| ----
 | |
| ======
 | |
| 
 | |
| It also includes a `Collection` of authorities with just one authority, `SCOPE_read`:
 | |
| 
 | |
| [tabs]
 | |
| ======
 | |
| Java::
 | |
| +
 | |
| [source,java,role="primary"]
 | |
| ----
 | |
| assertThat(token.getAuthorities()).hasSize(1);
 | |
| assertThat(token.getAuthorities()).containsExactly(new SimpleGrantedAuthority("SCOPE_read"));
 | |
| ----
 | |
| 
 | |
| Kotlin::
 | |
| +
 | |
| [source,kotlin,role="secondary"]
 | |
| ----
 | |
| assertThat(token.authorities).hasSize(1)
 | |
| assertThat(token.authorities).containsExactly(SimpleGrantedAuthority("SCOPE_read"))
 | |
| ----
 | |
| ======
 | |
| 
 | |
| Spring Security does the necessary work to make sure that the `BearerTokenAuthentication` instance is available for your controller methods.
 | |
| 
 | |
| [[webflux-testing-opaque-token-authorities]]
 | |
| === Configuring Authorities
 | |
| 
 | |
| In many circumstances, your method is protected by filter or method security and needs your `Authentication` to have certain granted authorities to allow the request.
 | |
| 
 | |
| In this case, you can supply what granted authorities you need using the `authorities()` method:
 | |
| 
 | |
| [tabs]
 | |
| ======
 | |
| Java::
 | |
| +
 | |
| [source,java,role="primary"]
 | |
| ----
 | |
| client
 | |
|     .mutateWith(mockOpaqueToken()
 | |
|         .authorities(new SimpleGrantedAuthority("SCOPE_message:read"))
 | |
|     )
 | |
|     .get().uri("/endpoint").exchange();
 | |
| ----
 | |
| 
 | |
| Kotlin::
 | |
| +
 | |
| [source,kotlin,role="secondary"]
 | |
| ----
 | |
| client
 | |
|     .mutateWith(mockOpaqueToken()
 | |
|         .authorities(SimpleGrantedAuthority("SCOPE_message:read"))
 | |
|     )
 | |
|     .get().uri("/endpoint").exchange()
 | |
| ----
 | |
| ======
 | |
| 
 | |
| [[webflux-testing-opaque-token-attributes]]
 | |
| === Configuring Claims
 | |
| 
 | |
| While granted authorities are quite common across all of Spring Security, we also have attributes in the case of OAuth 2.0.
 | |
| 
 | |
| Suppose, for example, that you have a `user_id` attribute that indicates the user's ID in your system.
 | |
| You might access it as follows in a controller:
 | |
| 
 | |
| [tabs]
 | |
| ======
 | |
| Java::
 | |
| +
 | |
| [source,java,role="primary"]
 | |
| ----
 | |
| @GetMapping("/endpoint")
 | |
| public Mono<String> foo(BearerTokenAuthentication authentication) {
 | |
|     String userId = (String) authentication.getTokenAttributes().get("user_id");
 | |
|     // ...
 | |
| }
 | |
| ----
 | |
| 
 | |
| Kotlin::
 | |
| +
 | |
| [source,kotlin,role="secondary"]
 | |
| ----
 | |
| @GetMapping("/endpoint")
 | |
| fun foo(authentication: BearerTokenAuthentication): Mono<String?> {
 | |
|     val userId = authentication.tokenAttributes["user_id"] as String?
 | |
|     // ...
 | |
| }
 | |
| ----
 | |
| ======
 | |
| 
 | |
| In that case, you can specify that attribute with the `attributes()` method:
 | |
| 
 | |
| [tabs]
 | |
| ======
 | |
| Java::
 | |
| +
 | |
| [source,java,role="primary"]
 | |
| ----
 | |
| client
 | |
|     .mutateWith(mockOpaqueToken()
 | |
|         .attributes(attrs -> attrs.put("user_id", "1234"))
 | |
|     )
 | |
|     .get().uri("/endpoint").exchange();
 | |
| ----
 | |
| 
 | |
| Kotlin::
 | |
| +
 | |
| [source,kotlin,role="secondary"]
 | |
| ----
 | |
| client
 | |
|     .mutateWith(mockOpaqueToken()
 | |
|         .attributes { attrs -> attrs["user_id"] = "1234" }
 | |
|     )
 | |
|     .get().uri("/endpoint").exchange()
 | |
| ----
 | |
| ======
 | |
| 
 | |
| [[webflux-testing-opaque-token-principal]]
 | |
| === Additional Configurations
 | |
| 
 | |
| You can also use additional methods to further configure the authentication, depending on what data your controller expects.
 | |
| 
 | |
| One such method is `principal(OAuth2AuthenticatedPrincipal)`, which you can use to configure the complete `OAuth2AuthenticatedPrincipal` instance that underlies the `BearerTokenAuthentication`.
 | |
| 
 | |
| It is handy if you:
 | |
| * Have your own implementation of `OAuth2AuthenticatedPrincipal` or
 | |
| * Want to specify a different principal name
 | |
| 
 | |
| For example, suppose that your authorization server sends the principal name in the `user_name` attribute instead of the `sub` attribute.
 | |
| In that case, you can configure an `OAuth2AuthenticatedPrincipal` by hand:
 | |
| 
 | |
| [tabs]
 | |
| ======
 | |
| Java::
 | |
| +
 | |
| [source,java,role="primary"]
 | |
| ----
 | |
| Map<String, Object> attributes = Collections.singletonMap("user_name", "foo_user");
 | |
| OAuth2AuthenticatedPrincipal principal = new DefaultOAuth2AuthenticatedPrincipal(
 | |
|         (String) attributes.get("user_name"),
 | |
|         attributes,
 | |
|         AuthorityUtils.createAuthorityList("SCOPE_message:read"));
 | |
| 
 | |
| client
 | |
|     .mutateWith(mockOpaqueToken().principal(principal))
 | |
|     .get().uri("/endpoint").exchange();
 | |
| ----
 | |
| 
 | |
| Kotlin::
 | |
| +
 | |
| [source,kotlin,role="secondary"]
 | |
| ----
 | |
| val attributes: Map<String, Any> = mapOf(Pair("user_name", "foo_user"))
 | |
| val principal: OAuth2AuthenticatedPrincipal = DefaultOAuth2AuthenticatedPrincipal(
 | |
|     attributes["user_name"] as String?,
 | |
|     attributes,
 | |
|     AuthorityUtils.createAuthorityList("SCOPE_message:read")
 | |
| )
 | |
| 
 | |
| client
 | |
|     .mutateWith(mockOpaqueToken().principal(principal))
 | |
|     .get().uri("/endpoint").exchange()
 | |
| ----
 | |
| ======
 | |
| 
 | |
| Note that, as an alternative to using `mockOpaqueToken()` test support, you can also mock the `OpaqueTokenIntrospector` bean itself with a `@MockBean` annotation.
 |