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

alm*_*777 12 spring-security spring-security-oauth2 spring-cloud-feign

概述

我正在尝试编写一个访问公共 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

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

问题

这是我的第一次尝试,但不幸的是,随着新的 Spring Security 版本的出现,我似乎无法实例化OAuth2FeignRequestInterceptor,我可能遇到了包问题。然后我继续研究 Spring Security 的文档和新的 OAuth2 重写,可以在这里找到: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 以及一个 type 的 bean OAuth2AccessToken- 两者都不起作用。我的最后一次尝试看起来像这样,被视为一种万岁玛丽,一种方法:

    @Bean
    public OAuth2AccessToken apiAccessToken(
            @RegisteredOAuth2AuthorizedClient("MY_AWESOME_PROVIDER") OAuth2AuthorizedClient authorizedClient) {
        return authorizedClient.getAccessToken();
    }
Run Code Online (Sandbox Code Playgroud)

这不起作用,因为RegisteredOAuth2AuthorizedClient需要用户会话,以免发生null。我还在 Stackoverflow 上看到其他人尝试了相同的方法,但他们实际上是在Controller(=> Resolving OAuth2AuthorizedClient as a Spring bean )

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

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

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

深入的例子

我尝试提供一个OAuth2AuthorizedClientManager如本例所示的(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;
    }
Run Code Online (Sandbox Code Playgroud)

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

    @Bean
    public RequestInterceptor requestInterceptor(OAuth2AuthorizedClientManager clientManager) {
        return new OAuthRequestInterceptor(clientManager);
    }
Run Code Online (Sandbox Code Playgroud)

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

    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.");
        }
    }
Run Code Online (Sandbox Code Playgroud)

当我运行代码时,我可以在控制台中看到“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'
Run Code Online (Sandbox Code Playgroud)

小智 5

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

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


归档时间:

查看次数:

29068 次

最近记录:

4 年,9 月 前