# 测试OAuth2.0 当涉及到OAuth2.0时,前面介绍的相同原则仍然适用:最终,这取决于你的测试方法在`SecurityContextHolder`中的期望。 例如,对于如下所示的控制器: Java ``` @GetMapping("/endpoint") public String foo(Principal user) { return user.getName(); } ``` Kotlin ``` @GetMapping("/endpoint") fun foo(user: Principal): String { return user.name } ``` 它没有特定于OAuth2的内容,因此你可能只需[使用`@WithMockUser`](../method.html#test-method-withmockuser)就可以了。 但是,在你的控制器绑定到 Spring Security的OAuth2.0支持的某些方面的情况下,例如: Java ``` @GetMapping("/endpoint") public String foo(@AuthenticationPrincipal OidcUser user) { return user.getIdToken().getSubject(); } ``` Kotlin ``` @GetMapping("/endpoint") fun foo(@AuthenticationPrincipal user: OidcUser): String { return user.idToken.subject } ``` 然后,安全的测试支持就会派上用场。 ## 测试OIDC登录 用 Spring MVC测试来测试上面的方法将需要用授权服务器来模拟某种授予流。当然,这将是一项艰巨的任务,这就是为什么 Spring 安全船支持删除这一样板。 例如,我们可以使用`oidcLogin`[`requestpostprocessor’](request-post-processors.html)告诉 Spring 安全性包含一个默认的`OidcUser`,就像这样: Java ``` mvc .perform(get("/endpoint").with(oidcLogin())); ``` Kotlin ``` mvc.get("/endpoint") { with(oidcLogin()) } ``` 这样做的目的是将关联的`MockHttpServletRequest`配置为`OidcUser`,其中包括授予权限的简单`OidcIdToken`、`OidcUserInfo`和`Collection`。 具体地说,它将包括一个`OidcIdToken`,其`sub`声明设置为`user`: Java ``` assertThat(user.getIdToken().getClaim("sub")).isEqualTo("user"); ``` Kotlin ``` assertThat(user.idToken.getClaim("sub")).isEqualTo("user") ``` 没有设置索赔要求的`OidcUserInfo`: Java ``` assertThat(user.getUserInfo().getClaims()).isEmpty(); ``` Kotlin ``` assertThat(user.userInfo.claims).isEmpty() ``` 而`Collection`只有一个权限的权限,`SCOPE_read`: Java ``` 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`注释]。 此外,它还将`OidcUser`链接到`OAuth2AuthorizedClient`的一个简单实例,该实例将其存入一个模拟`OAuth2AuthorizedClientRepository`。如果你的测试[使用`@RegisteredOAuth2AuthorizedClient`注释](#testing-oAuth2-client),这将非常方便。 ## 配置权限 在许多情况下,你的方法受到过滤器或方法安全性的保护,并且需要你的`Authentication`具有特定的授权来允许请求。 在这种情况下,你可以使用`authorities()`方法提供你需要的授权: Java ``` mvc .perform(get("/endpoint") .with(oidcLogin() .authorities(new SimpleGrantedAuthority("SCOPE_message:read")) ) ); ``` Kotlin ``` mvc.get("/endpoint") { with(oidcLogin() .authorities(SimpleGrantedAuthority("SCOPE_message:read")) ) } ``` ## 配置索赔 虽然在所有 Spring 安全中,授予权限是很常见的,但在OAuth2.0的情况下,我们也有主张。 例如,假设你有一个`user_id`声明,它指示了系统中用户的ID。你可以像在控制器中那样访问它: Java ``` @GetMapping("/endpoint") public String foo(@AuthenticationPrincipal OidcUser oidcUser) { String userId = oidcUser.getIdToken().getClaim("user_id"); // ... } ``` Kotlin ``` @GetMapping("/endpoint") fun foo(@AuthenticationPrincipal oidcUser: OidcUser): String { val userId = oidcUser.idToken.getClaim("user_id") // ... } ``` 在这种情况下,你需要使用`idToken()`方法来指定该声明: Java ``` mvc .perform(get("/endpoint") .with(oidcLogin() .idToken(token -> token.claim("user_id", "1234")) ) ); ``` Kotlin ``` mvc.get("/endpoint") { with(oidcLogin() .idToken { it.claim("user_id", "1234") } ) } ``` 由于`OidcUser`从`OidcIdToken`收集其索赔。 ## 附加配置 还有其他方法可以用于进一步配置身份验证;它只是取决于控制器所期望的数据: * `userInfo(OidcUserInfo.Builder)`-用于配置`OidcUserInfo`实例 * `clientRegistration(ClientRegistration)`-用于配置与给定的`ClientRegistration`相关联的`OAuth2AuthorizedClient` * `oidcUser(OidcUser)`-用于配置完整的`OidcUser`实例 最后一个很方便,如果你: 1. 有你自己的`OidcUser`的实现,或者 2. 需要更改名称属性 例如,假设你的授权服务器发送`user_name`声明中的主体名称,而不是`sub`声明中的主体名称。在这种情况下,你可以手动配置`OidcUser`: Java ``` OidcUser oidcUser = new DefaultOidcUser( AuthorityUtils.createAuthorityList("SCOPE_message:read"), OidcIdToken.withTokenValue("id-token").claim("user_name", "foo_user").build(), "user_name"); mvc .perform(get("/endpoint") .with(oidcLogin().oidcUser(oidcUser)) ); ``` Kotlin ``` val oidcUser: OidcUser = DefaultOidcUser( AuthorityUtils.createAuthorityList("SCOPE_message:read"), OidcIdToken.withTokenValue("id-token").claim("user_name", "foo_user").build(), "user_name" ) mvc.get("/endpoint") { with(oidcLogin().oidcUser(oidcUser)) } ``` ## 测试OAuth2.0登录 与[测试OIDC登录](#testing-oidc-login)一样,测试OAuth2.0Login也会遇到类似的挑战,即模拟授予流。正因为如此, Spring Security还具有对非OIDC用例的测试支持。 假设我们有一个控制器,可以将登录用户作为`OAuth2User`: Java ``` @GetMapping("/endpoint") public String foo(@AuthenticationPrincipal OAuth2User oauth2User) { return oauth2User.getAttribute("sub"); } ``` Kotlin ``` @GetMapping("/endpoint") fun foo(@AuthenticationPrincipal oauth2User: OAuth2User): String? { return oauth2User.getAttribute("sub") } ``` 在这种情况下,我们可以使用`oauth2User`[`RequestPostProcessor`](request-post-processors.html)告诉 Spring 安全性包含一个默认的`OAuth2User`,就像这样: Java ``` mvc .perform(get("/endpoint").with(oauth2Login())); ``` Kotlin ``` mvc.get("/endpoint") { with(oauth2Login()) } ``` 这样做的目的是将关联的`MockHttpServletRequest`配置为`OAuth2User`,其中包括一个简单的`Map`属性和`Collection`授予权限。 具体地说,它将包括一个`Map`,其键/值对为`sub`/`user`: Java ``` assertThat((String) user.getAttribute("sub")).isEqualTo("user"); ``` Kotlin ``` assertThat(user.getAttribute("sub")).isEqualTo("user") ``` a`Collection`只有一个权限的权限,`SCOPE_read`: Java ``` 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`注释]。 此外,它还将`OAuth2User`链接到它在模拟`OAuth2AuthorizedClientRepository`中存放的`OAuth2AuthorizedClient`的一个简单实例。如果你的测试[使用`@RegisteredOAuth2AuthorizedClient`注释](#testing-oAuth2-client),这可能会很方便。 ## 配置权限 在许多情况下,你的方法受到过滤器或方法安全性的保护,并且需要你的`Authentication`具有特定的授权来允许请求。 在这种情况下,你可以使用`authorities()`方法提供你需要的授权: Java ``` mvc .perform(get("/endpoint") .with(oauth2Login() .authorities(new SimpleGrantedAuthority("SCOPE_message:read")) ) ); ``` Kotlin ``` mvc.get("/endpoint") { with(oauth2Login() .authorities(SimpleGrantedAuthority("SCOPE_message:read")) ) } ``` ## 配置索赔 虽然在所有 Spring 安全中,授予权限是很常见的,但在OAuth2.0的情况下,我们也有主张。 例如,假设你有一个`user_id`属性,该属性指示系统中的用户ID。你可以像在控制器中那样访问它: Java ``` @GetMapping("/endpoint") public String foo(@AuthenticationPrincipal OAuth2User oauth2User) { String userId = oauth2User.getAttribute("user_id"); // ... } ``` Kotlin ``` @GetMapping("/endpoint") fun foo(@AuthenticationPrincipal oauth2User: OAuth2User): String { val userId = oauth2User.getAttribute("user_id") // ... } ``` 在这种情况下,你需要使用`attributes()`方法指定该属性: Java ``` mvc .perform(get("/endpoint") .with(oauth2Login() .attributes(attrs -> attrs.put("user_id", "1234")) ) ); ``` Kotlin ``` mvc.get("/endpoint") { with(oauth2Login() .attributes { attrs -> attrs["user_id"] = "1234" } ) } ``` ## 附加配置 还有其他方法可以用于进一步配置身份验证;它只是取决于控制器所期望的数据: * `clientRegistration(ClientRegistration)`-用于配置与给定的`ClientRegistration`相关联的`OAuth2AuthorizedClient` * `oauth2User(OAuth2User)`-用于配置完整的`OAuth2User`实例 最后一个很方便,如果你: 1. 有你自己的`OAuth2User`的实现,或者 2. 需要更改名称属性 例如,假设你的授权服务器发送`user_name`声明中的主体名称,而不是`sub`声明中的主体名称。在这种情况下,你可以手动配置`OAuth2User`: Java ``` OAuth2User oauth2User = new DefaultOAuth2User( AuthorityUtils.createAuthorityList("SCOPE_message:read"), Collections.singletonMap("user_name", "foo_user"), "user_name"); mvc .perform(get("/endpoint") .with(oauth2Login().oauth2User(oauth2User)) ); ``` Kotlin ``` val oauth2User: OAuth2User = DefaultOAuth2User( AuthorityUtils.createAuthorityList("SCOPE_message:read"), mapOf(Pair("user_name", "foo_user")), "user_name" ) mvc.get("/endpoint") { with(oauth2Login().oauth2User(oauth2User)) } ``` ## 测试OAuth2.0客户端 独立于你的用户如何进行身份验证,你可能有其他令牌和客户端注册,这些令牌和客户端注册正在为你正在测试的请求发挥作用。例如,你的控制器可能依赖于客户机凭据授权来获得一个与用户完全不相关的令牌: Java ``` @GetMapping("/endpoint") public String foo(@RegisteredOAuth2AuthorizedClient("my-app") OAuth2AuthorizedClient authorizedClient) { return this.webClient.get() .attributes(oauth2AuthorizedClient(authorizedClient)) .retrieve() .bodyToMono(String.class) .block(); } ``` Kotlin ``` @GetMapping("/endpoint") fun foo(@RegisteredOAuth2AuthorizedClient("my-app") authorizedClient: OAuth2AuthorizedClient?): String? { return this.webClient.get() .attributes(oauth2AuthorizedClient(authorizedClient)) .retrieve() .bodyToMono(String::class.java) .block() } ``` 用授权服务器模拟这种握手可能会很麻烦。相反,你可以使用`oauth2Client`[`RequestPostProcessor`](request-post-processors.html)将`OAuth2AuthorizedClient`添加到模拟`OAuth2AuthorizedClientRepository`中: Java ``` mvc .perform(get("/endpoint").with(oauth2Client("my-app"))); ``` Kotlin ``` mvc.get("/endpoint") { with( oauth2Client("my-app") ) } ``` 这将创建一个`OAuth2AuthorizedClient`,它具有一个简单的`ClientRegistration`、`OAuth2AccessToken`和资源所有者名称。 具体地说,它将包括一个`ClientRegistration`,其客户端ID为“test-client”,客户端秘密为“test-secret”: Java ``` 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”的资源所有者名称: Java ``` assertThat(authorizedClient.getPrincipalName()).isEqualTo("user"); ``` Kotlin ``` assertThat(authorizedClient.principalName).isEqualTo("user") ``` 以及只有一个作用域`OAuth2AccessToken`的`read`: Java ``` 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访问令牌都带有一组作用域。如果你的控制员检查了这些,请这样说: Java ``` @GetMapping("/endpoint") public String foo(@RegisteredOAuth2AuthorizedClient("my-app") OAuth2AuthorizedClient authorizedClient) { Set scopes = authorizedClient.getAccessToken().getScopes(); if (scopes.contains("message:read")) { return this.webClient.get() .attributes(oauth2AuthorizedClient(authorizedClient)) .retrieve() .bodyToMono(String.class) .block(); } // ... } ``` Kotlin ``` @GetMapping("/endpoint") fun foo(@RegisteredOAuth2AuthorizedClient("my-app") authorizedClient: OAuth2AuthorizedClient): String? { val scopes = authorizedClient.accessToken.scopes if (scopes.contains("message:read")) { return webClient.get() .attributes(oauth2AuthorizedClient(authorizedClient)) .retrieve() .bodyToMono(String::class.java) .block() } // ... } ``` 然后,你可以使用`accessToken()`方法配置范围: Java ``` mvc .perform(get("/endpoint") .with(oauth2Client("my-app") .accessToken(new OAuth2AccessToken(BEARER, "token", null, null, Collections.singleton("message:read")))) ) ); ``` Kotlin ``` mvc.get("/endpoint") { with(oauth2Client("my-app") .accessToken(OAuth2AccessToken(BEARER, "token", null, null, Collections.singleton("message:read"))) ) } ``` ## 附加配置 还有其他方法可以用于进一步配置身份验证;它只是取决于控制器所期望的数据: * `principalName(String)`-用于配置资源所有者名称 * `clientRegistration(Consumer)`-用于配置相关的`ClientRegistration` * `clientRegistration(ClientRegistration)`-用于配置完整的`ClientRegistration` 如果你想使用真正的`ClientRegistration`,那么最后一个就很方便了。 例如,假设你希望使用你的应用程序的`ClientRegistration`定义之一,如你的`application.yml`中所指定的。 在这种情况下,你的测试可以自动连接`ClientRegistrationRepository`并查找你的测试所需的一个: Java ``` @Autowired ClientRegistrationRepository clientRegistrationRepository; // ... mvc .perform(get("/endpoint") .with(oauth2Client() .clientRegistration(this.clientRegistrationRepository.findByRegistrationId("facebook")))); ``` Kotlin ``` @Autowired lateinit var clientRegistrationRepository: ClientRegistrationRepository // ... mvc.get("/endpoint") { with(oauth2Client("my-app") .clientRegistration(clientRegistrationRepository.findByRegistrationId("facebook")) ) } ``` ## 测试JWT身份验证 为了在资源服务器上发出授权请求,你需要一个承载令牌。 如果你的资源服务器是为JWTS配置的,那么这将意味着需要对承载令牌进行签名,然后根据JWT规范对其进行编码。所有这一切都可能令人望而生畏,尤其是当这不是测试的重点时。 幸运的是,有许多简单的方法可以克服这个困难,并允许你的测试专注于授权,而不是表示不记名令牌。我们现在来看看其中的两个: ## RequestPostProcessor` 第一种方法是通过`jwt`[`RequestPostProcessor`](request-post-processors.html)。其中最简单的例子是这样的: Java ``` mvc .perform(get("/endpoint").with(jwt())); ``` Kotlin ``` mvc.get("/endpoint") { with(jwt()) } ``` 这将创建一个模拟`Jwt`,将其正确地传递给任何身份验证API,以便你的授权机制可以对其进行验证。 默认情况下,它创建的`JWT`具有以下特征: ``` { "headers" : { "alg" : "none" }, "claims" : { "sub" : "user", "scope" : "read" } } ``` 结果`Jwt`,如果进行测试,将以以下方式通过: Java ``` 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") ``` 当然,可以对这些值进行配置。 任何标题或权利要求都可以配置相应的方法: Java ``` mvc .perform(get("/endpoint") .with(jwt().jwt(jwt -> jwt.header("kid", "one").claim("iss", "https://idp.example.org")))); ``` Kotlin ``` mvc.get("/endpoint") { with( jwt().jwt { jwt -> jwt.header("kid", "one").claim("iss", "https://idp.example.org") } ) } ``` Java ``` mvc .perform(get("/endpoint") .with(jwt().jwt(jwt -> jwt.claims(claims -> claims.remove("scope"))))); ``` Kotlin ``` mvc.get("/endpoint") { with( jwt().jwt { jwt -> jwt.claims { claims -> claims.remove("scope") } } ) } ``` 在这里,对`scope`和`scp`声明的处理方式与在正常的无记名令牌请求中的处理方式相同。但是,只需提供测试所需的`GrantedAuthority`实例的列表,就可以覆盖此内容: Java ``` mvc .perform(get("/endpoint") .with(jwt().authorities(new SimpleGrantedAuthority("SCOPE_messages")))); ``` Kotlin ``` mvc.get("/endpoint") { with( jwt().authorities(SimpleGrantedAuthority("SCOPE_messages")) ) } ``` 或者,如果你有一个自定义的`Jwt`到`Collection`转换器,那么你也可以使用它来派生权威: Java ``` mvc .perform(get("/endpoint") .with(jwt().authorities(new MyConverter()))); ``` Kotlin ``` mvc.get("/endpoint") { with( jwt().authorities(MyConverter()) ) } ``` 你还可以指定一个完整的`Jwt`,其中`[Jwt.Builder](https://docs.spring.io/spring-security/site/docs/5.6.2/api/org/springframework/security/oauth2/jwt/Jwt.Builder.html)`非常方便: Java ``` Jwt jwt = Jwt.withTokenValue("token") .header("alg", "none") .claim("sub", "user") .claim("scope", "read") .build(); mvc .perform(get("/endpoint") .with(jwt().jwt(jwt))); ``` Kotlin ``` val jwt: Jwt = Jwt.withTokenValue("token") .header("alg", "none") .claim("sub", "user") .claim("scope", "read") .build() mvc.get("/endpoint") { with( jwt().jwt(jwt) ) } ``` ## ` `RequestPostProcessor` 第二种方法是使用`authentication()`[`RequestPostProcessor`](request-post-processors.html)。本质上,你可以实例化自己的`JwtAuthenticationToken`并在测试中提供它,如下所示: Java ``` Jwt jwt = Jwt.withTokenValue("token") .header("alg", "none") .claim("sub", "user") .build(); Collection authorities = AuthorityUtils.createAuthorityList("SCOPE_read"); JwtAuthenticationToken token = new JwtAuthenticationToken(jwt, authorities); mvc .perform(get("/endpoint") .with(authentication(token))); ``` Kotlin ``` val jwt = Jwt.withTokenValue("token") .header("alg", "none") .claim("sub", "user") .build() val authorities: Collection = AuthorityUtils.createAuthorityList("SCOPE_read") val token = JwtAuthenticationToken(jwt, authorities) mvc.get("/endpoint") { with( authentication(token) ) } ``` 请注意,作为这些方法的替代方法,你还可以使用`JwtDecoder` Bean 注释来模拟`@MockBean`本身。 ## 测试不透明令牌身份验证 与[JWTs](#testing-jwt)类似,不透明令牌需要授权服务器来验证其有效性,这可能会使测试更加困难。 Spring 为了帮助实现这一点,Security提供了对不透明令牌的测试支持。 假设我们有一个控制器,它以`BearerTokenAuthentication`的形式检索身份验证: Java ``` @GetMapping("/endpoint") public String foo(BearerTokenAuthentication authentication) { return (String) authentication.getTokenAttributes().get("sub"); } ``` Kotlin ``` @GetMapping("/endpoint") fun foo(authentication: BearerTokenAuthentication): String { return authentication.tokenAttributes["sub"] as String } ``` 在这种情况下,我们可以使用`opaqueToken`[`RequestPostProcessor`](request-post-processors.html)方法告诉 Spring 安全性包含一个默认的`BearerTokenAuthentication`,就像这样: Java ``` mvc .perform(get("/endpoint").with(opaqueToken())); ``` Kotlin ``` mvc.get("/endpoint") { with(opaqueToken()) } ``` 这样做的目的是将关联的`MockHttpServletRequest`配置为`BearerTokenAuthentication`,其中包括一个简单的`OAuth2AuthenticatedPrincipal`、`Map`的属性,以及`Collection`的授予权限。 具体地说,它将包括一个`Map`,其键/值对为`sub`/`user`: Java ``` assertThat((String) token.getTokenAttributes().get("sub")).isEqualTo("user"); ``` Kotlin ``` assertThat(token.tokenAttributes["sub"] as String).isEqualTo("user") ``` a`Collection`只有一个权限的权限,`SCOPE_read`: Java ``` 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()`方法提供你需要的授权: Java ``` mvc .perform(get("/endpoint") .with(opaqueToken() .authorities(new SimpleGrantedAuthority("SCOPE_message:read")) ) ); ``` Kotlin ``` mvc.get("/endpoint") { with(opaqueToken() .authorities(SimpleGrantedAuthority("SCOPE_message:read")) ) } ``` ## 配置索赔 虽然在所有 Spring 安全性中,授予权限是非常常见的,但在OAuth2.0的情况下,我们也有属性。 举个例子,你有一个`user_id`属性,它指示系统中用户的ID。你可以像在控制器中那样访问它: Java ``` @GetMapping("/endpoint") public String foo(BearerTokenAuthentication authentication) { String userId = (String) authentication.getTokenAttributes().get("user_id"); // ... } ``` Kotlin ``` @GetMapping("/endpoint") fun foo(authentication: BearerTokenAuthentication): String { val userId = authentication.tokenAttributes["user_id"] as String // ... } ``` 在这种情况下,你需要使用`attributes()`方法指定该属性: Java ``` mvc .perform(get("/endpoint") .with(opaqueToken() .attributes(attrs -> attrs.put("user_id", "1234")) ) ); ``` Kotlin ``` mvc.get("/endpoint") { with(opaqueToken() .attributes { attrs -> attrs["user_id"] = "1234" } ) } ``` ## 附加配置 还有其他方法来进一步配置身份验证;它只是取决于控制器期望的数据。 其中一个是`principal(OAuth2AuthenticatedPrincipal)`,你可以使用它来配置作为`OAuth2AuthenticatedPrincipal`实例基础的完整`BearerTokenAuthentication`实例。 如果你: 1. 有你自己的`OAuth2AuthenticatedPrincipal`的实现,或者 2. 想要指定不同的主体名称 例如,假设你的授权服务器发送`user_name`属性中的主体名称,而不是`sub`属性中的主体名称。在这种情况下,你可以手动配置`OAuth2AuthenticatedPrincipal`: Java ``` Map attributes = Collections.singletonMap("user_name", "foo_user"); OAuth2AuthenticatedPrincipal principal = new DefaultOAuth2AuthenticatedPrincipal( (String) attributes.get("user_name"), attributes, AuthorityUtils.createAuthorityList("SCOPE_message:read")); mvc .perform(get("/endpoint") .with(opaqueToken().principal(principal)) ); ``` Kotlin ``` val attributes: Map = Collections.singletonMap("user_name", "foo_user") val principal: OAuth2AuthenticatedPrincipal = DefaultOAuth2AuthenticatedPrincipal( attributes["user_name"] as String?, attributes, AuthorityUtils.createAuthorityList("SCOPE_message:read") ) mvc.get("/endpoint") { with(opaqueToken().principal(principal)) } ``` 请注意,作为使用`opaqueToken()`测试支持的替代方法,你还可以使用`OpaqueTokenIntrospector` Bean 注释来模拟`OpaqueTokenIntrospector`本身。 [模拟HTTP BASIC ](http-basic.html)[模拟注销](logout.html)