Spring @FeignClient with OAuth2FeignRequestInterceptor not working

13,071

Solution 1

I have found out that the problem is that Hystrix forces code execution in another thread and so you have no access to request / session scoped beans. I was using @FeignClient with Hystrix enabled. When I disable Hystrix using feign.hystrix.enabled: false the call from Microservice A to Microservice B relaying the token (using OAuth2FeignRequestInterceptor) works fine.

However, it would be desirable to be able to keep Hystrix enabled. I have seen there is a new module that improves Hystrix - Feign (feign-hystrix module) in this regard in this post:

Does Spring Cloud Feign client call execute inside hystrix command?

However, I don't see how to properly do the setup using feign-hystrix and I was not able to find an example. Please, could you help with this or provide an example using feign-hystrix?

Thanks so much!

Solution 2

I am not exactly sure if I understood you correctly but the following worked for me.

See https://jfconavarrete.wordpress.com/2014/09/15/make-spring-security-context-available-inside-a-hystrix-command/

Basically the tutorial shows how to setup / augment hystrix with an additional "plugin" so the security context is made available inside hystrix wrapped calls via a threadlocal variable

With this setup all you need to do is define a feign request interceptor like so:

@Bean
public RequestInterceptor requestTokenBearerInterceptor() {

    return new RequestInterceptor() {
        @Override
        public void apply(RequestTemplate requestTemplate) {
            Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
            OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails) authentication.getDetails();
            requestTemplate.header("Authorization", "Bearer " + details.getTokenValue());                   
        }
    };
}

With this setup the token contained in the request is made available to the feign request interceptor so you can set the Authorization header on the feign request with the token from your authenticated user.

Also note that with this approach you can keep your SessionManagementStrategy "STATELESS" as no data has to be "stored" on the server side

Share:
13,071
miguelfgar
Author by

miguelfgar

Updated on June 04, 2022

Comments

  • miguelfgar
    miguelfgar over 1 year

    I'm trying to set FeignClient with OAuth2 to implement "Relay Token". I just want FeignClient to relay / propagate the OAuth2 Token that comes from ZuulProxy (SSO Enabled). I use Spring 1.3.1-RELEASE and Spring Cloud Brixton.M4.

    I have added an interceptor in a custom @FeignClient configuration:

    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.cloud.security.oauth2.client.feign.OAuth2FeignRequestInterceptor;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.security.oauth2.client.OAuth2ClientContext;
    import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails;
    import org.springframework.security.oauth2.client.token.grant.code.AuthorizationCodeResourceDetails;
    
    import feign.RequestInterceptor;
    
    @Configuration
    public class FeignClientConfiguration {
    
    @Value("${security.oauth2.client.userAuthorizationUri}")
    private String authorizeUrl;
    
    @Value("${security.oauth2.client.accessTokenUri}")
    private String tokenUrl;
    
    @Value("${security.oauth2.client.client-id}")
    private String clientId;
    
    
    // See https://github.com/spring-cloud/spring-cloud-netflix/issues/675
    @Bean
    public RequestInterceptor oauth2FeignRequestInterceptor(OAuth2ClientContext oauth2ClientContext){
        return new OAuth2FeignRequestInterceptor(oauth2ClientContext, resource());
    }
    
    @Bean
    protected OAuth2ProtectedResourceDetails resource() {
        AuthorizationCodeResourceDetails resource = new AuthorizationCodeResourceDetails();
        resource.setAccessTokenUri(tokenUrl);
        resource.setUserAuthorizationUri(authorizeUrl);
        resource.setClientId(clientId);
        // TODO: Remove this harcode 
        resource.setClientSecret("secret");
        return resource;
    }   
    }
    

    And I add the configuration to my @FeignClient like that:

    @FeignClient(name = "car-service", configuration =     FeignClientConfiguration.class)
    interface CarClient {               
        @RequestMapping(value = "car-service/api/car", method = GET)
        List<CarVO> getAllCars();
    }   
    

    The application starts but when I use the Feign Client from my service I get:

    2016-01-08 13:14:29.757 ERROR 3308 --- [nio-9081-exec-1] o.a.c.c.C.[.[.[.    [dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in 
    
        context with path [/user-service] threw exception [Request processing failed; nested exception is com.netflix.hystrix.exception.HystrixRuntimeException: getAllCars failed and no fallback available.] with root cause
    
        java.lang.IllegalStateException: No thread-bound request found: Are you referring to request attributes outside of an actual web request, or processing a request outside of the originally receiving thread? If you are actually operating within a web request and still receive this message, your code is probably running outside of DispatcherServlet/DispatcherPortlet: In this case, use RequestContextListener or RequestContextFilter to expose the current request.
    at org.springframework.web.context.request.RequestContextHolder.currentRequestAttributes(RequestContextHolder.java:131) ~[spring-web-4.2.4.RELEASE.jar:4.2.4.RELEASE]
    at org.springframework.web.context.request.AbstractRequestAttributesScope.get(AbstractRequestAttributesScope.java:41) ~[spring-web-4.2.4.RELEASE.jar:4.2.4.RELEASE]
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:340) ~[spring-beans-4.2.4.RELEASE.jar:4.2.4.RELEASE]
    

    I want my application / microservice (the one that uses the @FeingClient to call the other application / microservice) to be STATELESS. However, I have tried both, with security.sessions=STATELESS (SpringBoot default) and security.sessions=ALWAYS (just to try). In both cases I got the same exception.

    Having a look at the code I have seen that the OAuth2ClientContext is saved in Session (Session scoped bean). How does it work when you want to implement a STATELESS OAuth2 enabled application / microservice? Precisely this is one of the big advantages of using OAuth2 in my current scenario. However, as I said, the result was the same enabling sessions.

    Can someone help with this, please?

    Thanks so much! :-)