Testing OAuth 2.0

对于 OAuth 2.0 特定场景,Spring Security 提供测试支持,简化了授权流模拟,从而避免了与授权服务器进行烦琐的交互。

例如,您可以使用 mockOidcLogin() 模拟由 OidcUser 实例表示的 OIDC 登录,该实例包含声明和已授予的权限。同样,您可以使用 mockOAuth2Login() 模拟由 OAuth2User 实例表示的 OAuth 2.0 登录,该实例包含属性和已授予的权限。

此外,Spring Security 还提供了用于模拟 OAuth 2.0 客户端的 mockOAuth2Client(),以及用于模拟 JWT 和不透明令牌身份验证的 mockJwt() 和 mockOpaqueToken()。

这些测试支持方法允许您直接配置必需的属性,例如权限、声明和主体,从而简化了测试流程并使您能够专注于授权逻辑,而无需处理授权服务器的复杂性。

对于 OAuth 2.0,the same principles covered earlier still apply:最终,这取决于你的测试方法期望在 `SecurityContextHolder`中有什么。 考虑以下控制器示例:

  • 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)
}

它没有什么是 OAuth2 特定的,因此你可以 xref:reactive/test/method.adoc#test-erms[use @WithMockUser 并可以正常运行。 但是,如果控制器绑定到 Spring Security OAuth 2.0 支持的某些方面,请考虑这种情况:

  • 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)
}

在这种情况下,Spring Security 的测试支持很方便。

Testing OIDC Login

使用 WebTestClient 测试 preceding section 中所示的方法需要使用授权服务器模拟某种类型的授权流。这是一项艰巨的任务,这就是 Spring Security 附带支持以移除此样板代码的原因。

例如,我们可以使用 SecurityMockServerConfigurers#oidcLogin 方法告诉 Spring Security 包含一个默认 OidcUser

  • Java

  • Kotlin

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

该行将关联的 MockServerRequest 配置为 OidcUser,其中包括一个简单的 OidcIdToken、一个 OidcUserInfo 和一个已授予权限的 Collection

具体来说,它包括一个 OidcIdToken,其中 sub 声明设置为 user

  • Java

  • Kotlin

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

它还包括一个没有设置声明的 OidcUserInfo

  • Java

  • Kotlin

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

它还包括一个权限 Collection,其中只有一个权限 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 确保 OidcUser 实例可用于 xref:servlet/integrations/mvc.adoc#mvc-authentication-principal[the @AuthenticationPrincipal 注释。

此外,它还将 OidcUser 链接到 OAuth2AuthorizedClient 的一个简单实例,并将其存储到模拟 ServerOAuth2AuthorizedClientRepository 中。如果您的测试 use the @RegisteredOAuth2AuthorizedClient annotation,这可能会派上用场。

Configuring Authorities

在许多情况下,您的方法受过滤器或方法安全性保护,需要您的 Authentication 拥有某些已授予的权限以允许请求。

在这些情况下,您可以使用 authorities() 方法提供您需要的已授予权限:

  • 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

虽然 Spring Security 中默认授予权限,但在 OAuth 2.0 中我们也具有声明。

例如,假设您有一个 user_id 声明,用于表示您系统中的用户 ID。您可以在控制器中像下面这样访问它:

  • 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")
    // ...
}

在这种情况下,您可以使用 idToken() 方法来指定声明:

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

之所以这样做,是因为 OidcUserOidcIdToken 中收集其声明。

Additional Configurations

另外,还有其他用于进一步配置身份验证的方法,具体取决于您的控制器期望的数据:

  • userInfo(OidcUserInfo.Builder):配置 `OidcUserInfo`实例

  • clientRegistration(ClientRegistration):使用给定的 ClientRegistration`配置关联的 `OAuth2AuthorizedClient

  • oidcUser(OidcUser):配置完整的 `OidcUser`实例

如果您具有以下情况,最后的那个将非常方便:* 自己的 OidcUser 实现,或者* 需要更改名称属性

例如,假设您的授权服务器在 user_name 声明中发送负责人姓名,而不是在 sub 声明中。在这种情况下,您可以手动配置 OidcUser

  • 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

testing OIDC login 一样,测试 OAuth 2.0 登录也会带来类似的挑战:模拟授权流。正因如此,Spring Security 还为非 OIDC 用例提供了测试支持。

假设我们有一个控制器,它以 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"))
}

在这种情况下,我们可以使用 SecurityMockServerConfigurers#oauth2User 方法告诉 Spring Security 包含一个默认 OAuth2User

  • Java

  • Kotlin

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

前面的示例会使用 OAuth2User 配置关联的 MockServerRequest,它包含 Map 个简单的属性和 Collection 个已授予的权限。

具体来说,它包含一个 Map,其键/值对为 sub/user

  • Java

  • Kotlin

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

它还包括一个权限 Collection,其中只有一个权限 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 执行必要的工作以确保 OAuth2User 实例对于 xref:servlet/integrations/mvc.adoc#mvc-authentication-principal[the @AuthenticationPrincipal 注释可用。

另外,它还可以将 OAuth2User 链接到 OAuth2AuthorizedClient 的一个简单实例,并将该实例存储在模拟 ServerOAuth2AuthorizedClientRepository 中。如果您的测试 use the @RegisteredOAuth2AuthorizedClient annotation,这可能会非常方便。

Configuring Authorities

在许多情况下,您的方法受过滤器或方法安全性保护,需要您的 Authentication 拥有某些已授予的权限以允许请求。

在这种情况下,您可以使用 authorities() 方法提供您需要的已授予权限:

  • 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

虽然 Spring Security 中默认授予权限,但在 OAuth 2.0 中我们也具有声明。

例如,假设您有一个 user_id 属性,用于表示您系统中的用户 ID。您可以在控制器中像下面这样访问它:

  • 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")
    // ...
}

在这种情况下,您可以使用 attributes() 方法来指定属性:

  • 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

另外,还有其他用于进一步配置身份验证的方法,具体取决于您的控制器期望的数据:

  • clientRegistration(ClientRegistration):使用给定的 ClientRegistration`配置关联的 `OAuth2AuthorizedClient

  • oauth2User(OAuth2User):配置完整的 `OAuth2User`实例

如果您具有以下情况,最后的那个将非常方便:* 自己的 OAuth2User 实现,或者* 需要更改名称属性

例如,假设您的授权服务器在 user_name 声明中发送负责人姓名,而不是在 sub 声明中。在这种情况下,您可以手动配置 OAuth2User

  • 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

无论您的用户如何进行身份验证,您都可能有其他令牌和客户端注册,它们适用于您正在测试的请求。例如,您的控制器可能依赖于客户端凭据授权,以获取与用户完全无关的令牌:

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

使用 Gemini 模拟与授权服务器的握手可能会很麻烦。相反,你可以使用 SecurityMockServerConfigurers#oauth2Client 将一个 OAuth2AuthorizedClient 添加到一个模拟的 ServerOAuth2AuthorizedClientRepository

  • Java

  • Kotlin

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

这会创建一个拥有一个简单 ClientRegistration、一个 OAuth2AccessToken 和一个资源所有者名称的 OAuth2AuthorizedClient

具体来说,它包含一个 ClientRegistration,其客户端 ID 为 test-client,客户端机密为 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")

它还包含一个资源所有者名称 user

  • Java

  • Kotlin

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

它还包含一个 OAuth2AccessToken,含有一个范围 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")

你之后可以使用控制器方法中的 @RegisteredOAuth2AuthorizedClient 照常检索客户端。

Configuring Scopes

在许多情况下,OAuth 2.0 访问令牌会带有一组范围。考虑一个控制器如何检查范围的示例:

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

给定一个检查范围的控制器,你可以使用 accessToken() 方法配置范围:

  • 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

你还可以使用其他方法进一步配置认证,具体取决于你的控制器所期望的数据:

  • principalName(String);配置资源所有者名称

  • clientRegistration(Consumer&lt;ClientRegistration.Builder&gt;):配置关联的 ClientRegistration

  • clientRegistration(ClientRegistration):配置完整的 ClientRegistration

如果您想使用真实的 ClientRegistration,最后一个方法非常方便。

例如,假设你想使用应用程序某个 ClientRegistration 的定义,如 application.yml 中指定的。

在这种情况下,你的测试可以自动装配 ReactiveClientRegistrationRepository 并查找你的测试所需的定义:

  • 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

要在资源服务器上进行授权请求,你需要一个持有者令牌。如果你的资源服务器针对 JWT 进行配置,则持有者令牌需要签名,然后按照 JWT 规范进行编码。这一切都可能相当令人生畏,尤其是当这不是你测试的重点时。

幸运的是,有很多简单的方法可以帮你克服这个困难,让你的测试专注于授权而不用表现为持有者令牌。我们将在接下来的两个小节中了解其中的两个。

mockJwt() WebTestClientConfigurer

第一个方法是使用 WebTestClientConfigurer。最简单的方法是像以下所示那样使用 SecurityMockServerConfigurers#mockJwt 方法:

  • Java

  • Kotlin

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

此示例创建一个模拟 Jwt,并通过任何认证 API 传递它,以便你的授权机制可以验证它。

默认情况下,它创建的 JWT 具有以下特征:

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

如果经过测试,结果 Jwt 将以下面的方式传递:

  • 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")

请注意,你可以配置这些值。

你还可以使用相应的方法来配置任何标头或声明:

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

在此,scopescp 声明和在正常的承载令牌请求中处理它们的方式相同。但是,只要提供测试所需的 GrantedAuthority 实例列表,就可以简单地覆盖它:

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

或者,如果你有一个自定义 JwtCollection<GrantedAuthority> 转换器,你也可以使用它来派生权限:

  • Java

  • Kotlin

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

您还可以指定一个完整的 Jwt, 对于它,{security-api-url}org/springframework/security/oauth2/jwt/Jwt.Builder.html[Jwt.Builder] 非常方便:

  • 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

第二种方法是使用 authentication() Mutator。你可以实例化你自己的 JwtAuthenticationToken 并将其提供给你的测试:

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

请注意,作为替代方案,你还可以用 @MockBean 注解来模拟 ReactiveJwtDecoder bean 本身。

Testing Opaque Token Authentication

JWTs 类似,不透明令牌需要一个授权服务器来验证其有效性,这使得测试变得更加困难。为了帮助解决这个问题,Spring Security 为不透明令牌提供了测试支持。

假设你有一个控制器,它将身份验证获取为 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?)
}

在那种情况下,你可以使用 SecurityMockServerConfigurers#opaqueToken 方法告诉 Spring Security 包含一个默认 BearerTokenAuthentication

  • Java

  • Kotlin

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

此示例使用 BearerTokenAuthentication 配置关联的 MockHttpServletRequest,其中包含一个简单的 OAuth2AuthenticatedPrincipal、一连串属性和一连串已授予的权限。

具体来说,它包含一个 Map,其键/值对为 sub/user

  • Java

  • Kotlin

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

它还包括一个权限 Collection,其中只有一个权限 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 会执行必要的工作以确保 BearerTokenAuthentication 实例可用于您的控制器方法。

Configuring Authorities

在许多情况下,您的方法受过滤器或方法安全性保护,需要您的 Authentication 拥有某些已授予的权限以允许请求。

在这种情况下,您可以使用 authorities() 方法提供您需要的已授予权限:

  • 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

虽然已授予的权限在所有 Spring Security 中都很常见,但我们也有在 OAuth 2.0 中的属性。

例如,假设您有一个 user_id 属性,用于表示您系统中的用户 ID。您可以在控制器中像下面这样访问它:

  • 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?
    // ...
}

在这种情况下,您可以使用 attributes() 方法来指定属性:

  • 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

你还可以使用其他方法进一步配置身份验证,具体取决于你的控制器需要的什么数据。

其中一个这样的方法是 principal(OAuth2AuthenticatedPrincipal),你可以使用它来配置构成 BearerTokenAuthentication 的完整 OAuth2AuthenticatedPrincipal 实例。

在以下情况下,它会很方便:* 你有 OAuth2AuthenticatedPrincipal 自己的实现,或者 * 你想指定不同的主体名称

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

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

请注意,作为使用 mockOpaqueToken() 测试支持的替代方案,你还可以用 @MockBean 注解来模拟 OpaqueTokenIntrospector bean 本身。