Feign and Spring Security 5 - Client Credentials

假如想象 提交于 2021-02-12 09:12:50

问题


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

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}

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

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. Define my client in the 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. Create a OAuth2AuthorizedClientManager Bean to be able to authorize (or re-authorize) an OAuth 2.0 client:
@Bean
public OAuth2AuthorizedClientManager authorizedClientManager(
        ClientRegistrationRepository clientRegistrationRepository,
        OAuth2AuthorizedClientRepository authorizedClientRepository) {

    DefaultOAuth2AuthorizedClientManager authorizedClientManager =
            new DefaultOAuth2AuthorizedClientManager(
                    clientRegistrationRepository, authorizedClientRepository);

    return authorizedClientManager;
}
  1. Create a Feign Request Interceptor that uses the OAuth2AuthorizedClientManager:
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. Create a FeignConfig that uses the Interceptor:
public class FeignClientConfig {


    @Bean
    public OAuthRequestInterceptor repositoryClientOAuth2Interceptor(OAuth2AuthorizedClientManager manager) {
        return new OAuthRequestInterceptor(manager);
    }
}
  1. And this is my Feign client:
@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]

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.


回答1:


I am quite experienced with Feign and OAuth2 and it took me some good hours to find how to do that. First, let's say that my app is based on latest Spring libraries, so I am using the following dependencies (managed version for spring-cloud-starter-openfeign is 3.0.0)

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-oauth2</artifactId>
        <version>2.2.4.RELEASE</version>
    </dependency>

In my application.properties I have the following

security.oauth2.client.access-token-uri=https://api.twitter.com/oauth2/token
security.oauth2.client.client-id=my-secret-twitter-id
security.oauth2.client.client-secret=my-secret-twitter-secret
security.oauth2.client.grant-type=client_credentials

And finally my configuration beans

package es.spanishkangaroo.ttanalyzer.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.openfeign.security.OAuth2FeignRequestInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.client.DefaultOAuth2ClientContext;
import org.springframework.security.oauth2.client.OAuth2RestTemplate;
import org.springframework.security.oauth2.client.token.grant.client.ClientCredentialsResourceDetails;

import feign.RequestInterceptor;

@Configuration
public class FeignClientConfiguration {
    
    @Bean
    @ConfigurationProperties(prefix = "security.oauth2.client")
    public ClientCredentialsResourceDetails clientCredentialsResourceDetails() {
        return new ClientCredentialsResourceDetails();
    }

    @Bean
    public RequestInterceptor oauth2FeignRequestInterceptor(){
        return new OAuth2FeignRequestInterceptor(new DefaultOAuth2ClientContext(), clientCredentialsResourceDetails());
    }

    @Bean
    public OAuth2RestTemplate clientCredentialsRestTemplate() {
        return new OAuth2RestTemplate(clientCredentialsResourceDetails());
    }

}

So then the Feign client is as simple as

package es.spanishkangaroo.ttanalyzer.api;

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;

import es.clovelly.ttanalyzer.model.Trends;

@FeignClient(name = "twitterClient", url = "https://api.twitter.com/1.1/")
public interface TwitterClient {
 
    @GetMapping("/trends/place.json")
    Trends[] getTrendsById(@RequestParam Long id);
    
}

As you may have noticed, the code is automatically getting a token (a bearer token) before the client call. If you are using a non-expiring bearer token you can just use something like

@Bean
public OAuth2ClientContext oAuth2ClientContext() {
    DefaultOAuth2ClientContext context = new DefaultOAuth2ClientContext();
    context.setAccessToken(bearerToken);
    return context;
}



回答2:


I tried you're approach. Unfortunatelly without success. But this one worked for me: Spring cloud Feign OAuth2 request interceptor is not working. Looks like I use a lot of depredations now, but at least it does work.



来源:https://stackoverflow.com/questions/63283022/feign-and-spring-security-5-client-credentials

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!