弃用 spring-security-oauth 作为客户端

Rut*_*uth 2 spring spring-security spring-boot spring-security-oauth2

我现在想从 spring-security-oauth 转移到 spring-security,但我找不到任何这样做的方法。我搜索了很多,我能找到的只是关于提供 OAuth 端点的例子。

我当前的 OAuth2RestTemplate 有点复杂,因为 oauth 服务器没有使用标准的身份识别方式,灵感来自这里的帖子。

这是我的 OAuth2RestTemplate:

fun createOAuthRestTemplate(resourceDetails: OAuth2ProtectedResourceDetails): OAuth2RestTemplate {

    val clientCredentialsAccessTokenProvider = ClientCredentialsAccessTokenProvider()
    clientCredentialsAccessTokenProvider.setAuthenticationHandler(<myClientAuthenticationHandler extends ClientAuthenticationHandler>))
    clientCredentialsAccessTokenProvider.setRequestFactory(requestFactory)

    val oauthTemplate = OAuth2RestTemplate(resourceDetails)
    oauthTemplate.setAccessTokenProvider(clientCredentialsAccessTokenProvider)
    return oauthTemplate
  }
Run Code Online (Sandbox Code Playgroud)

不幸的是,春季迁移指南对我没有多大帮助,因为它只提到了 RestTemplate,但没有详细介绍。

[...] 
A Simplified RestTemplate and WebClient
Spring Security OAuth extends RestTemplate, introducing OAuth2RestTemplate. 
This class needs to be instantiated and exposed as a @Bean.

Spring Security chooses to favor composition and instead exposes an 
OAuth2AuthorizedClientService, which is useful for creating RestTemplate interceptors 
[...]
Run Code Online (Sandbox Code Playgroud)

我现在的问题是:
如何使用 spring-security 在 rest 模板中获得相同的功能?

jzh*_*aux 12

带有 Spring Security 5 和 RestTemplate 的 OAuth 2.0

Spring Security 5.2 不直接支持RestTemplate,但它具有简化工作的 bean。如果可以,建议使用WebClient,而不是RestTemplate

但是,如果您需要使用RestTemplate,那么您首先要创建一个OAuth2AuthorizedClientManager

@Bean
OAuth2AuthorizedClientManager authorizeClientManager(
    ClientRegistrationRepository clients,
    OAuth2AuthorizedClientRepository authorizedClients) {

    DefaultOAuth2AuthorizedClientManager manager =
        new DefaultOAuth2AuthorizedClientManager(clients, authorizedClients);

    OAuth2AuthorizedClientProvider authorizedClientProvider =
            OAuth2AuthorizedClientProviderBuilder.builder()
                .clientCredentials()
                .build();
    manager.setAuthorizedClientProvider(authorizedClientProvider);
    return manager;
}
Run Code Online (Sandbox Code Playgroud)

这个管理器 bean 是您决定希望拦截器为您协商的授权类型的地方。它与ClientCredentialsAccessTokenProvider您的帖子中的类似。

其次,您需要创建一个RestTemplate拦截器。它将要求管理器获取一个令牌,然后将该令牌添加到Authorization标头中:

@Component
public class OAuth2AuthorizedClientInterceptor implements ClientHttpRequestInterceptor {
    OAuth2AuthorizedClientManager manager;

    public OAuth2AuthorizedClientInterceptor(OAuth2AuthorizedClientManager manager) {
        this.manager = manager;
    }

    public ClientHttpResponse intercept(
        HttpRequest request, byte[] body, ClientHttpRequestExecution execution) 
        throws IOException {

        Authentication principal = // ...

        OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest
             .withClientRegistrationId("foo-client")
             .principal(principal)
             .build();
        OAuth2AuthorizedClient authorizedClient = 
            this.manager.authorize(authorizedRequest);

        HttpHeaders headers = httpRequest.getHeaders();
        headers.setBearerAuth(authorizedClient.getAccessToken().getValue());

        return execution.execute(request, body);
    }
}
Run Code Online (Sandbox Code Playgroud)

在 Spring Security 5 中,每个客户端都由一个注册 ID 表示。注册 ID 代替了foo-client.

principal将取决于您的情况,但通常使用SecurityContextHolder.getContext().getAuthentication(). 如果上下文中没有用户,您可以考虑AnonymousAuthenticationToken,例如

Authentication principal = new AnonymousAuthenticationToken
    ("key", "anonymous", AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS"));
Run Code Online (Sandbox Code Playgroud)

最后,您可以将拦截器添加到您的RestTemplate

@Bean
public RestTemplate rest(OAuth2AuthorizedClientInterceptor interceptor) {
    RestTemplate rest = new RestTemplate();
    rest.getInterceptors().add(interceptor);
    return rest;
}
Run Code Online (Sandbox Code Playgroud)

JWT 客户端认证

至于您提到的帖子,它正在为客户端身份验证进行 JWT。对于此支持,您需要查看配置DefaultClientCredentialsTokenResponseClient. 这将是您在第一步中构建的管理器的一部分:

@Bean
OAuth2AuthorizedClientManager authorizeClientManager(
    ClientRegistrationRepository clients,
    OAuth2AuthorizedClientRepository authorizedClients) {

    DefaultOAuth2AuthorizedClientManager manager =
        new DefaultOAuth2AuthorizedClientManager(clients, authorizedClients);

    DefaultClientCredentialsTokenResponseClient tokenClient =
            new DefaultClientCredentialsTokenResponseClient();
    tokenClient.setRequestEntityConverter(fooConverter);

    OAuth2AuthorizedClientProvider authorizedClientProvider =
            OAuth2AuthorizedClientProviderBuilder.builder()
                .clientCredentials(cc -> cc
                        .accessTokenResponseClient(tokenClient))
                .build();
    manager.setAuthorizedClientProvider(authorizedClientProvider);
    return manager;
}
Run Code Online (Sandbox Code Playgroud)

fooConverter是您为创建适当的RequestEntity实例而指定的任何转换器。就像是:

// ...
tokenClient.setRequestEntityConverter(grantRequest -> {
    ClientRegistration client = grantRequest.getClientRegistration();
    // ... formulate JWT
    // ... create `RequestEntity`, including `Authorization` header
    // that includes JWT as the bearer token
});
Run Code Online (Sandbox Code Playgroud)

这个 settersetRequestEntityConverterClientAuthenticationHandler遗留项目中的功能等价物。