# 测试 OAuth2.0

当涉及到 OAuth2.0 时，[前面提到的原则仍然适用。](../method.html#test-erms):最终，这取决于你的测试方法在`SecurityContextHolder`中的期望。

例如，对于如下所示的控制器：

爪哇

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

Kotlin

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

它没有特定于 OAuth2 的内容，因此你可能只需[使用`@WithMockUser`]（../method.html#test-erms）就可以了。

但是，在你的控制器绑定到 Spring Security 的 OAuth2.0 支持的某些方面的情况下，例如：

爪哇

```
@GetMapping("/endpoint")
public Mono<String> foo(@AuthenticationPrincipal OidcUser user) {
    return Mono.just(user.getIdToken().getSubject());
}
```

Kotlin

```
@GetMapping("/endpoint")
fun foo(@AuthenticationPrincipal user: OidcUser): Mono<String> {
    return Mono.just(user.idToken.subject)
}
```

然后，安全的测试支持就会派上用场。

## 测试 OIDC 登录

用`WebTestClient`测试上面的方法将需要用授权服务器模拟某种授权流。当然，这将是一项艰巨的任务，这就是为什么安全船支持删除这一样板。

例如，我们可以使用`SecurityMockServerConfigurers#mockOidcLogin`方法告诉 Spring 安全性包含一个默认的`OidcUser`，如下所示：

爪哇

```
client
    .mutateWith(mockOidcLogin()).get().uri("/endpoint").exchange();
```

Kotlin

```
client
    .mutateWith(mockOidcLogin())
    .get().uri("/endpoint")
    .exchange()
```

这样做的目的是将关联的`MockServerRequest`配置为`OidcUser`，其中包括授予权限的简单`OidcIdToken`、`OidcUserInfo`和`Collection`。

具体地说，它将包括一个`OidcIdToken`，其`sub`声明设置为`user`:

爪哇

```
assertThat(user.getIdToken().getClaim("sub")).isEqualTo("user");
```

Kotlin

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

没有设置索赔要求的`OidcUserInfo`:

爪哇

```
assertThat(user.getUserInfo().getClaims()).isEmpty();
```

Kotlin

```
assertThat(user.userInfo.claims).isEmpty()
```

而`Collection`只有一个权限的权限，`SCOPE_read`:

爪哇

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

Kotlin

```
assertThat(user.authorities).hasSize(1)
assertThat(user.authorities).containsExactly(SimpleGrantedAuthority("SCOPE_read"))
```

Spring 安全性做了必要的工作，以确保`OidcUser`实例可用于[`@AuthenticationPrincipal`注释]（.../.../ Servlet/integrations/mvc.html#mvc-authentication-principal）。

此外，它还将`OidcUser`链接到`OAuth2AuthorizedClient`的一个简单实例，该实例将其存入一个模拟`ServerOAuth2AuthorizedClientRepository`。如果你的测试[使用`@RegisteredOAuth2AuthorizedClient`注释]（#webflux-testing-oAuth2-client），这将非常方便。

### 配置权限

在许多情况下，你的方法受到过滤器或方法安全性的保护，并且需要你的`Authentication`具有特定的授权来允许请求。

在这种情况下，你可以使用`authorities()`方法提供你需要的授权：

爪哇

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

Kotlin

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

### 配置索赔

虽然在所有安全领域，授予权限是很常见的，但在 OAuth2.0 的情况下，我们也有主张。

例如，假设你有一个`user_id`声明，它指示了系统中用户的 ID。你可以像在控制器中那样访问它：

爪哇

```
@GetMapping("/endpoint")
public Mono<String> foo(@AuthenticationPrincipal OidcUser oidcUser) {
    String userId = oidcUser.getIdToken().getClaim("user_id");
    // ...
}
```

Kotlin

```
@GetMapping("/endpoint")
fun foo(@AuthenticationPrincipal oidcUser: OidcUser): Mono<String> {
    val userId = oidcUser.idToken.getClaim<String>("user_id")
    // ...
}
```

在这种情况下，你需要使用`idToken()`方法来指定该声明：

爪哇

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

Kotlin

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

由于`OidcUser`从`OidcIdToken`收集其索赔。

### 附加配置

还有其他方法可以用于进一步配置身份验证；它只是取决于控制器所期望的数据：

* `userInfo(OidcUserInfo.Builder)`-用于配置`OidcUserInfo`实例

* `clientRegistration(ClientRegistration)`-用于配置与给定的`ClientRegistration`相关联的`OAuth2AuthorizedClient`

* `oidcUser(OidcUser)`-用于配置完整的`OidcUser`实例

最后一个很方便，如果你：
1. 有你自己的`OidcUser`的实现，或者
2. 需要更改名称属性

例如，假设你的授权服务器发送`user_name`声明中的主体名称，而不是`sub`声明中的主体名称。在这种情况下，你可以手动配置`OidcUser`:

爪哇

```
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

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

## 测试 OAuth2.0 登录

与[测试 OIDC 登录](#webflux-testing-oidc-login)一样，测试 OAuth2.0Login 也会遇到类似的挑战，即模拟授予流。正因为如此， Spring Security 还具有对非 OIDC 用例的测试支持。

假设我们有一个控制器，可以将登录用户作为`OAuth2User`:

爪哇

```
@GetMapping("/endpoint")
public Mono<String> foo(@AuthenticationPrincipal OAuth2User oauth2User) {
    return Mono.just(oauth2User.getAttribute("sub"));
}
```

Kotlin

```
@GetMapping("/endpoint")
fun foo(@AuthenticationPrincipal oauth2User: OAuth2User): Mono<String> {
    return Mono.just(oauth2User.getAttribute("sub"))
}
```

在这种情况下，我们可以使用`SecurityMockServerConfigurers#mockOAuth2Login`方法告诉 Spring Security 包含一个默认的`OAuth2User`，就像这样：

爪哇

```
client
    .mutateWith(mockOAuth2Login())
    .get().uri("/endpoint").exchange();
```

Kotlin

```
client
    .mutateWith(mockOAuth2Login())
    .get().uri("/endpoint").exchange()
```

这样做的目的是将关联的`MockServerRequest`配置为`OAuth2User`，其中包括一个简单的`Map`属性和`Collection`授予权限。

具体地说，它将包括一个`Map`，其键/值对为`sub`/`user`:

爪哇

```
assertThat((String) user.getAttribute("sub")).isEqualTo("user");
```

Kotlin

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

a`Collection`只有一个权限的权限，`SCOPE_read`:

爪哇

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

Kotlin

```
assertThat(user.authorities).hasSize(1)
assertThat(user.authorities).containsExactly(SimpleGrantedAuthority("SCOPE_read"))
```

Spring 安全性做了必要的工作，以确保`OAuth2User`实例可用于[`@AuthenticationPrincipal`注释]（.../../ Servlet/integrations/mvc.html#mvc-authentication-principal）。

此外，它还将`OAuth2User`链接到它在模拟`ServerOAuth2AuthorizedClientRepository`中存放的`OAuth2AuthorizedClient`的一个简单实例。如果你的测试[使用`@RegisteredOAuth2AuthorizedClient`注释]（#webflux-testing-oAuth2-client），这将非常方便。

### 配置权限

在许多情况下，你的方法受到过滤器或方法安全性的保护，并且需要你的`Authentication`具有特定的授权来允许请求。

在这种情况下，你可以使用`authorities()`方法提供你需要的授权：

爪哇

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

Kotlin

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

### 配置索赔

虽然在所有的安全中，授予权限是很常见的，但在 OAuth2.0 的情况下，我们也有主张。

例如，假设你有一个`user_id`属性，该属性指示系统中的用户 ID。你可以像在控制器中那样访问它：

爪哇

```
@GetMapping("/endpoint")
public Mono<String> foo(@AuthenticationPrincipal OAuth2User oauth2User) {
    String userId = oauth2User.getAttribute("user_id");
    // ...
}
```

Kotlin

```
@GetMapping("/endpoint")
fun foo(@AuthenticationPrincipal oauth2User: OAuth2User): Mono<String> {
    val userId = oauth2User.getAttribute<String>("user_id")
    // ...
}
```

在这种情况下，你需要使用`attributes()`方法指定该属性：

爪哇

```
client
    .mutateWith(mockOAuth2Login()
        .attributes(attrs -> attrs.put("user_id", "1234"))
    )
    .get().uri("/endpoint").exchange();
```

Kotlin

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

### 附加配置

还有其他方法可以用于进一步配置身份验证；它只是取决于控制器所期望的数据：

* `clientRegistration(ClientRegistration)`-用于配置与给定的`ClientRegistration`相关联的`OAuth2AuthorizedClient`

* `oauth2User(OAuth2User)`-用于配置完整的`OAuth2User`实例

最后一个很方便，如果你：
1. 有你自己的`OAuth2User`的实现，或者
2. 需要更改名称属性

例如，假设你的授权服务器发送`user_name`声明中的主体名称，而不是`sub`声明中的主体名称。在这种情况下，你可以手动配置`OAuth2User`:

爪哇

```
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

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

## 测试 OAuth2.0 客户端

独立于你的用户如何进行身份验证，你可能有其他令牌和客户端注册，这些令牌和客户端注册正在为你正在测试的请求发挥作用。例如，你的控制器可能依赖于客户机凭据授权来获得一个与用户完全不相关的令牌：

爪哇

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

Kotlin

```
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()
}
```

用授权服务器模拟这种握手可能会很麻烦。相反，你可以使用`SecurityMockServerConfigurers#mockOAuth2Client`将`OAuth2AuthorizedClient`添加到模拟`ServerOAuth2AuthorizedClientRepository`中：

爪哇

```
client
    .mutateWith(mockOAuth2Client("my-app"))
    .get().uri("/endpoint").exchange();
```

Kotlin

```
client
    .mutateWith(mockOAuth2Client("my-app"))
    .get().uri("/endpoint").exchange()
```

这将创建一个`OAuth2AuthorizedClient`，它具有一个简单的`ClientRegistration`、`OAuth2AccessToken`和资源所有者名称。

具体地说，它将包括一个`ClientRegistration`，其客户端 ID 为“test-client”，客户端秘密为“test-secret”：

爪哇

```
assertThat(authorizedClient.getClientRegistration().getClientId()).isEqualTo("test-client");
assertThat(authorizedClient.getClientRegistration().getClientSecret()).isEqualTo("test-secret");
```

Kotlin

```
assertThat(authorizedClient.clientRegistration.clientId).isEqualTo("test-client")
assertThat(authorizedClient.clientRegistration.clientSecret).isEqualTo("test-secret")
```

“user”的资源所有者名称：

爪哇

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

Kotlin

```
assertThat(authorizedClient.principalName).isEqualTo("user")
```

以及只有一个作用域`OAuth2AccessToken`的`read`:

爪哇

```
assertThat(authorizedClient.getAccessToken().getScopes()).hasSize(1);
assertThat(authorizedClient.getAccessToken().getScopes()).containsExactly("read");
```

Kotlin

```
assertThat(authorizedClient.accessToken.scopes).hasSize(1)
assertThat(authorizedClient.accessToken.scopes).containsExactly("read")
```

然后在控制器方法中使用`@RegisteredOAuth2AuthorizedClient`可以正常地检索客户端。

### 配置作用域

在许多情况下，OAuth2.0 访问令牌都带有一组作用域。如果你的控制员检查了这些，请这样说：

爪哇

```
@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

```
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()
    }
    // ...
}
```

然后，你可以使用`accessToken()`方法配置范围：

爪哇

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

Kotlin

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

### 附加配置

还有其他方法可以用于进一步配置身份验证；它只是取决于控制器所期望的数据：

* `principalName(String)`-用于配置资源所有者名称

* `clientRegistration(Consumer<ClientRegistration.Builder>)`-用于配置相关的`ClientRegistration`

* `clientRegistration(ClientRegistration)`-用于配置完整的`ClientRegistration`

如果你想使用真正的`ClientRegistration`，那么最后一个就很方便了。

例如，假设你希望使用应用程序的`ClientRegistration`定义之一，如你的`application.yml`中所指定的。

在这种情况下，你的测试可以自动连接`ReactiveClientRegistrationRepository`并查找你的测试所需的一个：

爪哇

```
@Autowired
ReactiveClientRegistrationRepository clientRegistrationRepository;

// ...

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

Kotlin

```
@Autowired
lateinit var clientRegistrationRepository: ReactiveClientRegistrationRepository

// ...

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

## 测试 JWT 身份验证

为了在资源服务器上发出授权请求，你需要一个承载令牌。如果你的资源服务器是为 JWTS 配置的，那么这将意味着需要对承载令牌进行签名，然后根据 JWT 规范对其进行编码。所有这一切都可能令人望而生畏，尤其是当这不是测试的重点时。

幸运的是，有许多简单的方法可以克服这个困难，并允许你的测试专注于授权，而不是表示不记名令牌。我们现在来看看其中的两个：

###  WebTestClientConfigurer

第一种方法是通过`WebTestClientConfigurer`。其中最简单的方法是使用`SecurityMockServerConfigurers#mockJwt`方法，如下所示：

爪哇

```
client
    .mutateWith(mockJwt()).get().uri("/endpoint").exchange();
```

Kotlin

```
client
    .mutateWith(mockJwt()).get().uri("/endpoint").exchange()
```

这将创建一个模拟`Jwt`，将其正确地传递给任何身份验证 API，以便你的授权机制可以对其进行验证。

默认情况下，它创建的`JWT`具有以下特征：

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

结果`Jwt`，如果进行测试，将以以下方式通过：

爪哇

```
assertThat(jwt.getTokenValue()).isEqualTo("token");
assertThat(jwt.getHeaders().get("alg")).isEqualTo("none");
assertThat(jwt.getSubject()).isEqualTo("sub");
```

Kotlin

```
assertThat(jwt.tokenValue).isEqualTo("token")
assertThat(jwt.headers["alg"]).isEqualTo("none")
assertThat(jwt.subject).isEqualTo("sub")
```

当然，可以对这些值进行配置。

任何标题或权利要求都可以配置相应的方法：

爪哇

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

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.claims(claims -> claims.remove("scope"))))
	.get().uri("/endpoint").exchange();
```

Kotlin

```
client
    .mutateWith(mockJwt().jwt { jwt ->
        jwt.claims { claims -> claims.remove("scope") }
    })
    .get().uri("/endpoint").exchange()
```

在这里，对`scope`和`scp`声明的处理方式与在正常的无记名令牌请求中的处理方式相同。但是，只需提供测试所需的`GrantedAuthority`实例的列表，就可以覆盖此内容：

爪哇

```
client
	.mutateWith(mockJwt().authorities(new SimpleGrantedAuthority("SCOPE_messages")))
	.get().uri("/endpoint").exchange();
```

Kotlin

```
client
    .mutateWith(mockJwt().authorities(SimpleGrantedAuthority("SCOPE_messages")))
    .get().uri("/endpoint").exchange()
```

或者，如果你有一个自定义的`Jwt`到`Collection<GrantedAuthority>`转换器，那么你也可以使用它来派生权威：

爪哇

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

Kotlin

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

你还可以指定一个完整的`Jwt`，其中`[Jwt.Builder](https://docs.spring.io/spring-security/site/docs/5.6.2/api/org/springframework/security/oauth2/jwt/Jwt.Builder.html)`非常方便：

爪哇

```
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

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

### ` `WebTestClientConfigurer`

第二种方法是使用`authentication()``Mutator`。本质上，你可以实例化自己的`JwtAuthenticationToken`并在测试中提供它，如下所示：

爪哇

```
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

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

请注意，作为这些方法的替代方法，你还可以使用`ReactiveJwtDecoder` Bean 注释来模拟`@MockBean`本身。

## 测试不透明令牌身份验证

与[JWTs](#webflux-testing-jwt)类似，不透明令牌需要授权服务器来验证其有效性，这可能会使测试更加困难。 Spring 为了帮助实现这一点，Security 提供了对不透明令牌的测试支持。

假设我们有一个控制器，它以`BearerTokenAuthentication`的形式检索身份验证：

爪哇

```
@GetMapping("/endpoint")
public Mono<String> foo(BearerTokenAuthentication authentication) {
    return Mono.just((String) authentication.getTokenAttributes().get("sub"));
}
```

Kotlin

```
@GetMapping("/endpoint")
fun foo(authentication: BearerTokenAuthentication): Mono<String?> {
    return Mono.just(authentication.tokenAttributes["sub"] as String?)
}
```

在这种情况下，我们可以使用`SecurityMockServerConfigurers#mockOpaqueToken`方法告诉 Spring Security 包含一个默认的`BearerTokenAuthentication`，就像这样：

爪哇

```
client
    .mutateWith(mockOpaqueToken())
    .get().uri("/endpoint").exchange();
```

Kotlin

```
client
    .mutateWith(mockOpaqueToken())
    .get().uri("/endpoint").exchange()
```

这样做的目的是将关联的`MockHttpServletRequest`配置为`BearerTokenAuthentication`，其中包括一个简单的`OAuth2AuthenticatedPrincipal`、`Map`的属性，以及`Collection`的授予权限。

具体地说，它将包括一个`Map`，其键/值对为`sub`/`user`:

爪哇

```
assertThat((String) token.getTokenAttributes().get("sub")).isEqualTo("user");
```

Kotlin

```
assertThat(token.tokenAttributes["sub"] as String?).isEqualTo("user")
```

而`Collection`只有一个权限的权限，`SCOPE_read`:

爪哇

```
assertThat(token.getAuthorities()).hasSize(1);
assertThat(token.getAuthorities()).containsExactly(new SimpleGrantedAuthority("SCOPE_read"));
```

Kotlin

```
assertThat(token.authorities).hasSize(1)
assertThat(token.authorities).containsExactly(SimpleGrantedAuthority("SCOPE_read"))
```

Spring 安全性做了必要的工作，以确保`BearerTokenAuthentication`实例可用于你的控制器方法。

### 配置权限

在许多情况下，你的方法受到过滤器或方法安全性的保护，并且需要你的`Authentication`拥有某些授权权限来允许请求。

在这种情况下，你可以使用`authorities()`方法提供你需要的授权：

爪哇

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

Kotlin

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

### 配置索赔

虽然在所有 Spring 安全性中，授予权限是非常常见的，但在 OAuth2.0 中，我们也有属性。

例如，假设你有一个`user_id`属性，该属性指示系统中用户的 ID。你可以像在控制器中那样访问它：

爪哇

```
@GetMapping("/endpoint")
public Mono<String> foo(BearerTokenAuthentication authentication) {
    String userId = (String) authentication.getTokenAttributes().get("user_id");
    // ...
}
```

Kotlin

```
@GetMapping("/endpoint")
fun foo(authentication: BearerTokenAuthentication): Mono<String?> {
    val userId = authentication.tokenAttributes["user_id"] as String?
    // ...
}
```

在这种情况下，你需要使用`attributes()`方法指定该属性：

爪哇

```
client
    .mutateWith(mockOpaqueToken()
        .attributes(attrs -> attrs.put("user_id", "1234"))
    )
    .get().uri("/endpoint").exchange();
```

Kotlin

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

### 附加配置

还有其他方法来进一步配置身份验证；它只是取决于控制器期望的数据。

其中一个是`principal(OAuth2AuthenticatedPrincipal)`，你可以使用它来配置作为`OAuth2AuthenticatedPrincipal`实例基础的完整`BearerTokenAuthentication`实例。

如果你：
1. 有你自己的`OAuth2AuthenticatedPrincipal`的实现，或者
2. 想要指定不同的主体名称

例如，假设你的授权服务器发送`user_name`属性中的主体名称，而不是`sub`属性。在这种情况下，你可以手动配置`OAuth2AuthenticatedPrincipal`:

爪哇

```
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

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

请注意，作为使用`mockOpaqueToken()`测试支持的一种替代方法，你还可以使用`OpaqueTokenIntrospector` Bean 本身来模拟`@MockBean`注释。

[测试 CSRF ](csrf.html)[WebFlux 安全性](../../configuration/webflux.html)

