Feign 和 Spring Security 5 - 客户端凭据 [英] Feign and Spring Security 5 - Client Credentials

查看:93
本文介绍了Feign 和 Spring Security 5 - 客户端凭据的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试从 Feign 客户端应用程序调用一些由 client_credentials 授权类型保护的后端系统.

I am trying to invoke some backend system which is secured by a client_credentials grant type from a Feign client application.

可以使用以下 curl 结构检索来自后端系统的访问令牌(仅作为示例):

The access token from the backend system can be retrieved with the following curl structure (just as an example):

curl --location --request POST '[SERVER URL]/oauth/grant' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--header 'Cookie: WebSessionID=172.22.72.1.1558614080219404; b8d49fdc74b7190aacd4ac9b22e85db8=2f0e4c4dbf6d4269fd3349f61c151223' \
--data-raw 'grant_type=client_credentials' \
--data-raw 'client_id=[CLIENT_ID]' \
--data-raw 'client_secret=[CLIENT_SECRET]'

{"accessToken":"V29C90D1917528E9C29795EF52EC2462D091F9DC106FAFD829D0FA537B78147E20","tokenType":"Bearer","expiresSeconds":7200}

此 accessToken 然后应在标头中设置,以用于对后端系统的后续业务调用.

This accessToken should then be set in a header to subsequent business calls to the backend system.

所以现在我的问题是,如何使用 Feign 和 Spring Boot Security 5 来实现这一点.经过一番研究,我找到了这个解决方案(不起作用):

So now my question is, how to implement this using Feign and Spring Boot Security 5. After some research I come to this solution (which doesn't work):

  1. 在 application.yml 中定义我的客户端:

spring:
  security:
    oauth2:
      client:
        registration:
          backend:
            client-id:[CLIENT_ID]
            client-secret: [CLIENT_SECRET]
            authorization-grant-type: client_credentials
    
        provider:
          backend:
            token-uri: [SERVER URL]/oauth/grant

  1. 创建 OAuth2AuthorizedClientManager Bean 以授权(或重新授权)OAuth 2.0 客户端:

@Bean
public OAuth2AuthorizedClientManager authorizedClientManager(
        ClientRegistrationRepository clientRegistrationRepository,
        OAuth2AuthorizedClientRepository authorizedClientRepository) {

    DefaultOAuth2AuthorizedClientManager authorizedClientManager =
            new DefaultOAuth2AuthorizedClientManager(
                    clientRegistrationRepository, authorizedClientRepository);

    return authorizedClientManager;
}

  1. 创建一个使用 OAuth2AuthorizedClientManager 的 Feign 请求拦截器:

public class OAuthRequestInterceptor implements RequestInterceptor {

    private OAuth2AuthorizedClientManager manager;

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

    @Override
    public void apply(RequestTemplate requestTemplate) {
        OAuth2AuthorizedClient client = this.manager.authorize(OAuth2AuthorizeRequest.withClientRegistrationId("backend").principal(createPrincipal()).build());
        String accessToken = client.getAccessToken().getTokenValue();
        requestTemplate.header(HttpHeaders.AUTHORIZATION, "Bearer" + accessToken);
    }

    private Authentication createPrincipal() {
        return new Authentication() {
            @Override
            public Collection<? extends GrantedAuthority> getAuthorities() {
                return Collections.emptySet();
            }

            @Override
            public Object getCredentials() {
                return null;
            }

            @Override
            public Object getDetails() {
                return null;
            }

            @Override
            public Object getPrincipal() {
                return this;
            }

            @Override
            public boolean isAuthenticated() {
                return false;
            }

            @Override
            public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
            }

            @Override
            public String getName() {
                return "backend";
            }
        };
    }
}

  1. 创建一个使用拦截器的 FeignConfig:

public class FeignClientConfig {


    @Bean
    public OAuthRequestInterceptor repositoryClientOAuth2Interceptor(OAuth2AuthorizedClientManager manager) {
        return new OAuthRequestInterceptor(manager);
    }
}

  1. 这是我的 Feign 客户:

@FeignClient(name = "BackendRepository", configuration = FeignClientConfig.class, url = "${BACKEND_URL}")
public interface BackendRepository {

    @GetMapping(path = "/healthChecks", produces = MediaType.APPLICATION_JSON_VALUE)
    public Info healthCheck();
}

运行此代码时,出现错误:

When running this code, I get the error:

org.springframework.web.client.UnknownContentTypeException: Could not extract response: no suitable HttpMessageConverter found for response type [class org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse] and content type [text/html;charset=utf-8]

调试代码,看起来 DefaultClientCredentialsTokenResponseClient 正在使用基本身份验证请求身份验证端点.虽然我从来没有设置过.

Debugging the code it looks like the DefaultClientCredentialsTokenResponseClient is requesting the auth endpoint using Basic Authentication. Although I never set this up.

任何建议我能做什么?也许有一种完全不同的方法来做到这一点.

Any advise what I can do? Maybe there is a completely different approach to do this.

推荐答案

要与 Spring Security 5 和 Feign 配合使用,您需要具备

For this to work with Spring Security 5 and Feign you need to have

  • 一个有效的 Spring Security 配置
  • 一个 Feign 拦截器
  • 使用该拦截器的 Feign 配置
  1. 工作 Spring 安全配置

在这里,我们将为您的 oauth2 客户端凭据 注册一个通用的 internal-api 客户端.您可以在此处指定 client-secretclient-secretscopesgrant type.所有基本的 Spring Security 5 东西.这还涉及设置提供程序(这里我使用了一个名为yourprovider"的自定义 OpenID Connect 提供程序

Here we will register a generic internal-api client for your oauth2 client credentials. This is where you specify the client-secret,client-secret, scopes and grant type. All basic Spring Security 5 stuff. This also involves setting up a provider (here I am using a custom OpenID Connect provider called "yourprovider"

spring:
  security:
    oauth2:
      client:
        registration:
          internal-api:
            provider: yourprovider
            client-id: x
            client-secret: y
            scope:
              - ROLE_ADMIN
            authorization-grant-type: client_credentials
        provider:
          yourprovider:
            issuer-uri: yourprovider.issuer-uri
      resourceserver:
        jwt:
          issuer-uri: yourprovider.issuer-uri

接下来你需要你的 feign 配置.这将使用 OAuth2FeignRequestInterceptor

Next you need your feign config. This will use a OAuth2FeignRequestInterceptor

public class ServiceToServiceFeignConfiguration extends AbstractFeignConfiguration {

    @Bean
    public OAuth2FeignRequestInterceptor requestInterceptor() {
        return new OAuth2FeignRequestInterceptor(
                OAuth2AuthorizeRequest.withClientRegistrationId("internal-api")
                        .principal(new AnonymousAuthenticationToken("feignClient", "feignClient", createAuthorityList("ROLE_ANONYMOUS")))
                        .build());
    }
}

还有一个看起来像这样的 RequestInterceptor :

And a RequestInterceptor that looks like this :

OAuth2AuthorizedClientManager 是一个可以在配置中配置的 bean

The OAuth2AuthorizedClientManager is a bean that you can configure in your Configuration

public OAuth2AuthorizedClientManager authorizedClientManager(final ClientRegistrationRepository clientRegistrationRepository, final OAuth2AuthorizedClientService authorizedClientService) {
    return new AuthorizedClientServiceOAuth2AuthorizedClientManager(clientRegistrationRepository, authorizedClientService);
}

OAuth2AuthorizeRequest 由上面的 Feign 配置提供.oAuth2AuthorizedClientManager 可以授权 oAuth2AuthorizeRequest,获取访问令牌,并将其作为 Authorization 标头提供给底层服务

The OAuth2AuthorizeRequest is provided by the Feign Configuration above. The oAuth2AuthorizedClientManager can authorize the oAuth2AuthorizeRequest, get you the access token, and provide it as an Authorization header to the underlying service

public class OAuth2FeignRequestInterceptor implements RequestInterceptor {

    @Inject
    private OAuth2AuthorizedClientManager oAuth2AuthorizedClientManager;

    private OAuth2AuthorizeRequest oAuth2AuthorizeRequest;

    OAuth2FeignRequestInterceptor(OAuth2AuthorizeRequest oAuth2AuthorizeRequest) {
        this.oAuth2AuthorizeRequest = oAuth2AuthorizeRequest;
    }

    @Override
    public void apply(RequestTemplate template) {
        template.header(AUTHORIZATION,getAuthorizationToken());
    }

    private String getAuthorizationToken() {
        final OAuth2AccessToken accessToken = oAuth2AuthorizedClientManager.authorize(oAuth2AuthorizeRequest).getAccessToken();
        return String.format("%s %s", accessToken.getTokenType().getValue(), accessToken.getTokenValue());
    }

}

这篇关于Feign 和 Spring Security 5 - 客户端凭据的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆