如何使用 Spring Security 为 client_credentials 工作流程向 Feign 客户端提供 OAuth2 令牌

2024-02-01

Overview

我正在尝试编写一个访问公共 REST API 的程序。为了让我能够使用它,我需要提供 OAuth2 令牌。

我的应用程序使用 Spring Boot 2.4.2 和 Spring Cloud 版本 2020.0.1。应用程序本身每 24 小时调用一次 REST API,下载数据并将其存储在数据库中。不同的微服务在其他时间点使用这些数据,并且需要每天刷新数据。

我的方法是使用 OpenFeign 声明使用 REST API 的 REST 客户端并为其提供 OAuth2 令牌。这是一个很常见的问题,所以我假设机器到机器client_credentials工作流程有详细记录。

事实上,我确实找到了一个使用 OpenFeign 执行此操作的简单示例 - 这里:https://github.com/netshoes/sample-feign-oauth2-interceptor/blob/master/src/main/java/com/sample/feign/oauth2/interceptor/OrderFeignClientConfiguration.java https://github.com/netshoes/sample-feign-oauth2-interceptor/blob/master/src/main/java/com/sample/feign/oauth2/interceptor/OrderFeignClientConfiguration.java

TL;DR:尝试编写需要 OAuth2 令牌(client_credentials 授予类型)的机器对机器微服务。

Problem

这是我的第一次尝试,但不幸的是,随着新的 Spring Security 版本的出现,我似乎无法获得OAuth2FeignRequestInterceptor实例化后,我可能遇到包问题。然后我继续研究 Spring Security 和新的 OAuth2 重写的文档,可以在这里找到:https://docs.spring.io/spring-security/site/docs/5.1.2.RELEASE/reference/htmlsingle/#oauth2client https://docs.spring.io/spring-security/site/docs/5.1.2.RELEASE/reference/htmlsingle/#oauth2client.

方法

我的方法是使用RequestInterceptor通过添加 Authorization Bearer 标头,将当前的 OAuth2 令牌注入 OpenFeign 客户端的请求中。我的假设是,我可以使用 Spring Security OAuth2 层或多或少自动地检索此信息。

使用我尝试提供的文档OAuth2RegisteredClient到我的拦截器,以及一个 bean 类型OAuth2AccessToken- 两者都不起作用。我的最后一次尝试看起来像这样,并且被视为一种冰雹玛丽,一种方法:

    @Bean
    public OAuth2AccessToken apiAccessToken(
            @RegisteredOAuth2AuthorizedClient("MY_AWESOME_PROVIDER") OAuth2AuthorizedClient authorizedClient) {
        return authorizedClient.getAccessToken();
    }

这不起作用,因为RegisteredOAuth2AuthorizedClient需要用户会话,以免null。我还在 Stackoverflow 上看到其他人尝试了相同的方法,但他们实际上是在Controller (=> 将 OAuth2AuthorizedClient 解析为 Spring bean https://stackoverflow.com/questions/57907747/resolving-oauth2authorizedclient-as-a-spring-bean)

我还尝试了一些我在这里找到的方法:

  • Feign 和 Spring Security 5 - 客户端凭证 https://stackoverflow.com/questions/63283022/feign-and-spring-security-5-client-credentials(提供的答案使用 Spring Boot 2.2.4 - 因此不再相关)
  • OAuth2FeignRequestInterceptor 的替代方案,因为它现已弃用 https://stackoverflow.com/questions/66091706/alternative-for-oauth2feignrequestinterceptor-as-it-is-deprecated-now另一位绅士正在寻找替代品OAuth2FeignRequestInterceptor
  • Spring Boot 2.3 中已弃用 OAuth2FeignRequestInterceptor 类 https://stackoverflow.com/questions/62448111/oauth2feignrequestinterceptor-class-deprecated-in-spring-boot-2-3- 这里的解决方案再次需要活动的用户会话
  • https://github.com/jgrandja/spring-security-oauth-5-2-migrate https://github.com/jgrandja/spring-security-oauth-5-2-migrate这个 Github 存储库时不时地弹出,我研究了它,但我认为它与我的问题无关 - 也许我错过了一些东西?据我了解,这个示例应用程序有多个使用多个范围的提供者 - 但仍然是一个触发登录的用户,从而通过 Spring Security 自动生成 OAuth2 令牌。 (这个问题也有特色:从 Spring Boot Oauth2 迁移到 Spring Security 5 https://stackoverflow.com/questions/60874507/migrating-from-spring-boot-oauth2-to-spring-security-5) [1]
  • https://github.com/spring-cloud/spring-cloud-openfeign/issues/417 https://github.com/spring-cloud/spring-cloud-openfeign/issues/417-> 目前还没有替代品OAuth2FeignRequestInterceptor

我的假设是我可以以某种方式使用 Spring Security 5 来解决这个问题,但我根本不知道如何实际做到这一点。在我看来,我发现的大多数教程和代码示例实际上都需要用户会话,或者对于 Spring Security 5 来说已经过时了。

看来我确实遗漏了一些东西,我希望有人能为我指出正确的方向,即有关如何实现这一目标的教程或书面文档。

深入的例子

我尝试提供一个OAuth2AuthorizedClientManager如本例所示(https://github.com/jgrandja/spring-security-oauth-5-2-migrate https://github.com/jgrandja/spring-security-oauth-5-2-migrate)。 为此,我注册了一个OAuth2AuthorizedClientManager遵循示例代码:

    @Bean
    public OAuth2AuthorizedClientManager authorizedClientManager(ClientRegistrationRepository clientRegistrationRepository,
                                                                 OAuth2AuthorizedClientRepository authorizedClientRepository) {
        OAuth2AuthorizedClientProvider authorizedClientProvider =
                OAuth2AuthorizedClientProviderBuilder.builder()
                        .authorizationCode()
                        .refreshToken()
                        .clientCredentials()
                        .password()
                        .build();
        DefaultOAuth2AuthorizedClientManager authorizedClientManager = new DefaultOAuth2AuthorizedClientManager(
                clientRegistrationRepository, authorizedClientRepository);
        authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);

        return authorizedClientManager;
    }

并为我提供了RequestInterceptor如下所示:

    @Bean
    public RequestInterceptor requestInterceptor(OAuth2AuthorizedClientManager clientManager) {
        return new OAuthRequestInterceptor(clientManager);
    }

最后我写了拦截器,如下所示:

    private String getAccessToken() {
        OAuth2AuthorizeRequest request = OAuth2AuthorizeRequest.withClientRegistrationId(appClientId)
                // .principal(appClientId) // if this is not set, I receive "principal cannot be null" (or empty)
                .build();
        return Optional.ofNullable(authorizedClientManager)
                .map(clientManager -> clientManager.authorize(request))
                .map(OAuth2AuthorizedClient::getAccessToken)
                .map(AbstractOAuth2Token::getTokenValue)
                .orElseThrow(OAuth2AccessTokenRetrievalException::failureToRetrieve);
    }

    @Override
    public void apply(RequestTemplate template) {
        log.debug("FeignClientInterceptor -> apply CALLED");
        String token = getAccessToken();
        if (token != null) {
            String bearerString = String.format("%s %s", BEARER, token);
            template.header(HttpHeaders.AUTHORIZATION, bearerString);
            log.debug("set the template header to this bearer string: {}", bearerString);
        } else {
            log.error("No bearer string.");
        }
    }

当我运行代码时,我可以在控制台中看到“FeignClientInterceptor -> apply called”输出,然后是异常:

Caused by: java.lang.IllegalArgumentException: servletRequest cannot be null

我的假设是我收到此消息,因为我没有活动的用户会话。因此,在我看来,我绝对需要一个来解决这个问题——而我在机器对机器通信中没有这个问题。

这是一个常见的用例,所以我确信我一定在某个时候犯了错误。

二手包

也许我的包裹弄错了?

    implementation 'org.springframework.boot:spring-boot-starter-amqp'
    implementation 'org.springframework.boot:spring-boot-starter-jooq'
    implementation 'org.springframework.boot:spring-boot-starter-security'
    implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.cloud:spring-cloud-starter-openfeign'

根据文档需要使用 AuthorizedClientServiceOAuth2AuthorizedClientManager 而不是 DefaultOAuth2AuthorizedClientManager

在 HttpServletRequest 上下文之外进行操作时,请改用 AuthorizedClientServiceOAuth2AuthorizedClientManager。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何使用 Spring Security 为 client_credentials 工作流程向 Feign 客户端提供 OAuth2 令牌 的相关文章

随机推荐