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

Posted

技术标签:

【中文标题】弃用 spring-security-oauth 作为客户端【英文标题】:Deprecate spring-security-oauth as a client 【发布时间】:2020-04-13 01:20:33 【问题描述】:

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

我目前的 OAuth2RestTemplate 有点复杂,因为 oauth 服务器没有使用标准的识别方式,受 here 的启发。

这是我的 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
  

不幸的是,spring migration guide 对我没有多大帮助,因为它只是提到了 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 
[...]

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

【问题讨论】:

【参考方案1】:

OAuth 2.0 与 Spring Security 5 和 RestTemplate

Spring Security 5.2 不直接支持RestTemplate,不过它有简化工作的bean。如果可以,建议使用use 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;

您可以在此管理器 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);
    

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

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

Authentication principal = new AnonymousAuthenticationToken
    ("key", "anonymous", AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS"));

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

@Bean
public RestTemplate rest(OAuth2AuthorizedClientInterceptor interceptor) 
    RestTemplate rest = new RestTemplate();
    rest.getInterceptors().add(interceptor);
    return rest;

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;

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

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

这个setter setRequestEntityConverter 是旧项目中ClientAuthenticationHandler 的功能等价物。

【讨论】:

谢谢,我会看看 Webclient,否则看看你的 RestTemplate 示例:) 在哪里可以添加this post 中描述的代码?我想做的基本上就是那个hack。我现在明白了,我是否使用 RestTemplate 或 WebClient 并不重要,但我需要了解在什么时候我可以将我的 AuthenticationHandler 连接到新的授权机制中。你知道我该怎么做吗? @jzheaux 感谢@RuthiRuth 的澄清,我添加了一个新部分,讨论了 ClientAuthenticationHandler 的等效功能。 非常感谢@jzheaux 的帮助。我必须进行一些调整才能达到我在您的示例中所处的位置,但我现在被卡住了。我有一个问题,当我使用你的拦截器时,在OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest.withClientRegistrationId("foo-client").build(); 处出现错误“主体不能为空”。我在网络上的任何地方都可以找到一个控制器,它也获取 Authentication 对象,但是我在哪里可以在拦截器中获取它?还尝试了 webclient,但不够熟悉以适应它,它只是在那里说 401 :( 有什么想法吗? 我必须做出的调整是在经理身上。我无法访问'accessTokenResponseClient',所以不得不使用.clientCredentials configurer -&gt; configurer.accessTokenResponseClient(tokenClient) (kotlin) 也许你可以编辑你的答案 - 或者我可以在你的许可下编辑它,所以我可以接受你的答案作为其他人的工作示例? :)

以上是关于弃用 spring-security-oauth 作为客户端的主要内容,如果未能解决你的问题,请参考以下文章

spring-security-oauth2 - 从资源服务器检查ClientDetails

spring-security-oauth中的JdbcApprovalStore(ApprovalStore)有啥用?

spring-security-oauth2 替代品

spring-security-oauth2中的HttpSecurity配置问题

如何处理 spring-security-oauth2 的版本升级?

Spring-Security-Oauth2:默认登录成功 url