Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                

Testing OAuth 2.0

When it comes to OAuth 2.0, 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:

  • Java

  • Kotlin

@GetMapping("/endpoint")
public Mono<String> foo(Principal user) {
    return Mono.just(user.getName());
}
@GetMapping("/endpoint")
fun foo(user: Principal): Mono<String> {
    return Mono.just(user.name)
}

Nothing about it is OAuth2-specific, so you can 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:

  • Java

  • Kotlin

@GetMapping("/endpoint")
public Mono<String> foo(@AuthenticationPrincipal OidcUser user) {
    return Mono.just(user.getIdToken().getSubject());
}
@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.

Testing OIDC Login

Testing the method shown in the 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:

  • Java

  • Kotlin

client
    .mutateWith(mockOidcLogin()).get().uri("/endpoint").exchange();
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:

  • Java

  • Kotlin

assertThat(user.getIdToken().getClaim("sub")).isEqualTo("user");
assertThat(user.idToken.getClaim<String>("sub")).isEqualTo("user")

It also includes an OidcUserInfo with no claims set:

  • Java

  • Kotlin

assertThat(user.getUserInfo().getClaims()).isEmpty();
assertThat(user.userInfo.claims).isEmpty()

It also includes a Collection of authorities with just one authority, SCOPE_read:

  • Java

  • Kotlin

assertThat(user.getAuthorities()).hasSize(1);
assertThat(user.getAuthorities()).containsExactly(new SimpleGrantedAuthority("SCOPE_read"));
assertThat(user.authorities).hasSize(1)
assertThat(user.authorities).containsExactly(SimpleGrantedAuthority("SCOPE_read"))

Spring Security makes sure that the OidcUser instance is available forthe @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 use the @RegisteredOAuth2AuthorizedClient annotation..

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:

  • Java

  • Kotlin

client
    .mutateWith(mockOidcLogin()
        .authorities(new SimpleGrantedAuthority("SCOPE_message:read"))
    )
    .get().uri("/endpoint").exchange();
client
    .mutateWith(mockOidcLogin()
        .authorities(SimpleGrantedAuthority("SCOPE_message:read"))
    )
    .get().uri("/endpoint").exchange()

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:

  • Java

  • Kotlin

@GetMapping("/endpoint")
public Mono<String> foo(@AuthenticationPrincipal OidcUser oidcUser) {
    String userId = oidcUser.getIdToken().getClaim("user_id");
    // ...
}
@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:

  • Java

  • Kotlin

client
    .mutateWith(mockOidcLogin()
        .idToken(token -> token.claim("user_id", "1234"))
    )
    .get().uri("/endpoint").exchange();
client
    .mutateWith(mockOidcLogin()
        .idToken { token -> token.claim("user_id", "1234") }
    )
    .get().uri("/endpoint").exchange()

That works because OidcUser collects its claims from OidcIdToken.

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:

  • Java

  • Kotlin

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();
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()

Testing OAuth 2.0 Login

As with 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:

  • Java

  • Kotlin

@GetMapping("/endpoint")
public Mono<String> foo(@AuthenticationPrincipal OAuth2User oauth2User) {
    return Mono.just(oauth2User.getAttribute("sub"));
}
@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:

  • Java

  • Kotlin

client
    .mutateWith(mockOAuth2Login())
    .get().uri("/endpoint").exchange();
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:

  • Java

  • Kotlin

assertThat((String) user.getAttribute("sub")).isEqualTo("user");
assertThat(user.getAttribute<String>("sub")).isEqualTo("user")

It also includes a Collection of authorities with just one authority, SCOPE_read:

  • Java

  • Kotlin

assertThat(user.getAuthorities()).hasSize(1);
assertThat(user.getAuthorities()).containsExactly(new SimpleGrantedAuthority("SCOPE_read"));
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 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 use the @RegisteredOAuth2AuthorizedClient annotation.

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:

  • Java

  • Kotlin

client
    .mutateWith(mockOAuth2Login()
        .authorities(new SimpleGrantedAuthority("SCOPE_message:read"))
    )
    .get().uri("/endpoint").exchange();
client
    .mutateWith(mockOAuth2Login()
        .authorities(SimpleGrantedAuthority("SCOPE_message:read"))
    )
    .get().uri("/endpoint").exchange()

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:

  • Java

  • Kotlin

@GetMapping("/endpoint")
public Mono<String> foo(@AuthenticationPrincipal OAuth2User oauth2User) {
    String userId = oauth2User.getAttribute("user_id");
    // ...
}
@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:

  • Java

  • Kotlin

client
    .mutateWith(mockOAuth2Login()
        .attributes(attrs -> attrs.put("user_id", "1234"))
    )
    .get().uri("/endpoint").exchange();
client
    .mutateWith(mockOAuth2Login()
        .attributes { attrs -> attrs["user_id"] = "1234" }
    )
    .get().uri("/endpoint").exchange()

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:

  • Java

  • Kotlin

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();
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()

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:

  • Java

  • Kotlin

@GetMapping("/endpoint")
public Mono<String> foo(@RegisteredOAuth2AuthorizedClient("my-app") OAuth2AuthorizedClient authorizedClient) {
    return this.webClient.get()
        .attributes(oauth2AuthorizedClient(authorizedClient))
        .retrieve()
        .bodyToMono(String.class);
}
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:

  • Java

  • Kotlin

client
    .mutateWith(mockOAuth2Client("my-app"))
    .get().uri("/endpoint").exchange();
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:

  • Java

  • Kotlin

assertThat(authorizedClient.getClientRegistration().getClientId()).isEqualTo("test-client");
assertThat(authorizedClient.getClientRegistration().getClientSecret()).isEqualTo("test-secret");
assertThat(authorizedClient.clientRegistration.clientId).isEqualTo("test-client")
assertThat(authorizedClient.clientRegistration.clientSecret).isEqualTo("test-secret")

It also includes a resource owner name of user:

  • Java

  • Kotlin

assertThat(authorizedClient.getPrincipalName()).isEqualTo("user");
assertThat(authorizedClient.principalName).isEqualTo("user")

It also includes an OAuth2AccessToken with one scope, read:

  • Java

  • Kotlin

assertThat(authorizedClient.getAccessToken().getScopes()).hasSize(1);
assertThat(authorizedClient.getAccessToken().getScopes()).containsExactly("read");
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.

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:

  • Java

  • Kotlin

@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);
    }
    // ...
}
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:

  • Java

  • Kotlin

client
    .mutateWith(mockOAuth2Client("my-app")
        .accessToken(new OAuth2AccessToken(BEARER, "token", null, null, Collections.singleton("message:read")))
    )
    .get().uri("/endpoint").exchange();
client
    .mutateWith(mockOAuth2Client("my-app")
        .accessToken(OAuth2AccessToken(BEARER, "token", null, null, setOf("message:read")))
)
.get().uri("/endpoint").exchange()

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:

  • Java

  • Kotlin

@Autowired
ReactiveClientRegistrationRepository clientRegistrationRepository;

// ...

client
    .mutateWith(mockOAuth2Client()
        .clientRegistration(this.clientRegistrationRepository.findByRegistrationId("facebook").block())
    )
    .get().uri("/exchange").exchange();
@Autowired
lateinit var clientRegistrationRepository: ReactiveClientRegistrationRepository

// ...

client
    .mutateWith(mockOAuth2Client()
        .clientRegistration(this.clientRegistrationRepository.findByRegistrationId("facebook").block())
    )
    .get().uri("/exchange").exchange()

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:

  • Java

  • Kotlin

client
    .mutateWith(mockJwt()).get().uri("/endpoint").exchange();
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:

{
  "headers" : { "alg" : "none" },
  "claims" : {
    "sub" : "user",
    "scope" : "read"
  }
}

The resulting Jwt, were it tested, would pass in the following way:

  • Java

  • Kotlin

assertThat(jwt.getTokenValue()).isEqualTo("token");
assertThat(jwt.getHeaders().get("alg")).isEqualTo("none");
assertThat(jwt.getSubject()).isEqualTo("sub");
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:

  • Java

  • Kotlin

client
	.mutateWith(mockJwt().jwt(jwt -> jwt.header("kid", "one")
		.claim("iss", "https://idp.example.org")))
	.get().uri("/endpoint").exchange();
client
    .mutateWith(mockJwt().jwt { jwt -> jwt.header("kid", "one")
        .claim("iss", "https://idp.example.org")
    })
    .get().uri("/endpoint").exchange()
  • Java

  • Kotlin

client
	.mutateWith(mockJwt().jwt(jwt -> jwt.claims(claims -> claims.remove("scope"))))
	.get().uri("/endpoint").exchange();
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:

  • Java

  • Kotlin

client
	.mutateWith(mockJwt().authorities(new SimpleGrantedAuthority("SCOPE_messages")))
	.get().uri("/endpoint").exchange();
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:

  • Java

  • Kotlin

client
	.mutateWith(mockJwt().authorities(new MyConverter()))
	.get().uri("/endpoint").exchange();
client
    .mutateWith(mockJwt().authorities(MyConverter()))
    .get().uri("/endpoint").exchange()

You can also specify a complete Jwt, for which Jwt.Builder is quite handy:

  • Java

  • Kotlin

Jwt jwt = Jwt.withTokenValue("token")
    .header("alg", "none")
    .claim("sub", "user")
    .claim("scope", "read")
    .build();

client
	.mutateWith(mockJwt().jwt(jwt))
	.get().uri("/endpoint").exchange();
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:

  • Java

  • Kotlin

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();
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.

Testing Opaque Token Authentication

Similar to 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:

  • Java

  • Kotlin

@GetMapping("/endpoint")
public Mono<String> foo(BearerTokenAuthentication authentication) {
    return Mono.just((String) authentication.getTokenAttributes().get("sub"));
}
@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:

  • Java

  • Kotlin

client
    .mutateWith(mockOpaqueToken())
    .get().uri("/endpoint").exchange();
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:

  • Java

  • Kotlin

assertThat((String) token.getTokenAttributes().get("sub")).isEqualTo("user");
assertThat(token.tokenAttributes["sub"] as String?).isEqualTo("user")

It also includes a Collection of authorities with just one authority, SCOPE_read:

  • Java

  • Kotlin

assertThat(token.getAuthorities()).hasSize(1);
assertThat(token.getAuthorities()).containsExactly(new SimpleGrantedAuthority("SCOPE_read"));
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.

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:

  • Java

  • Kotlin

client
    .mutateWith(mockOpaqueToken()
        .authorities(new SimpleGrantedAuthority("SCOPE_message:read"))
    )
    .get().uri("/endpoint").exchange();
client
    .mutateWith(mockOpaqueToken()
        .authorities(SimpleGrantedAuthority("SCOPE_message:read"))
    )
    .get().uri("/endpoint").exchange()

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:

  • Java

  • Kotlin

@GetMapping("/endpoint")
public Mono<String> foo(BearerTokenAuthentication authentication) {
    String userId = (String) authentication.getTokenAttributes().get("user_id");
    // ...
}
@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:

  • Java

  • Kotlin

client
    .mutateWith(mockOpaqueToken()
        .attributes(attrs -> attrs.put("user_id", "1234"))
    )
    .get().uri("/endpoint").exchange();
client
    .mutateWith(mockOpaqueToken()
        .attributes { attrs -> attrs["user_id"] = "1234" }
    )
    .get().uri("/endpoint").exchange()

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:

  • Java

  • Kotlin

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();
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.