Search code examples
spring-cloudnetflix-zuulhystrixspring-hateoasspring-cloud-feign

How to forward headers when using Zuul, Hystrix (and Feign) with Spring Cloud HATEOAS?


Context

My micro-services application is based on spring-cloud: a zuul gateway is configured in front of two micro-services: service-a and service-b.

One of my API requires that service-a requests service-b; I use feign for that.

Zuul send X-FORWARDED-* headers to the services, for them to rewrite the HATEOAS links correctly (when the services are configured with ForwardedHeaderFilter).

My problem is that the services communicate with each other using Feign, which relies on Hystrix. Hystrix creates a new thread for each request (we don't use the SEMAPHORE config), so the request in Spring's RequestContextHolder is lost in the Feign request from service-a to service-b, I can't enrich the feign request with an feign interceptor anymore since the original request is lost.

Some potential solutions

Forwarding authorization token is now supported directly by Spring with the parameter hystrix.shareSecurityContext: true

There isn't any "out of the box" configuration to have Hystrix shares the request between threads.

A solution could be to implement my own HystrixConcurrencyStrategy, which is a class from netflix.hystrix. My latest find is this Pull Request that has been sent to spring-cloud-netflix, but unfortunately not integrated.

I can try to copy the code of the Pull request, and create a bean, just as what "eacdy" wrote:

@Bean
public RequestAttributeHystrixConcurrencyStrategy hystrixRequestAutoConfiguration() {
    return new RequestAttributeHystrixConcurrencyStrategy();
}

Is there an easier solution to forward the headers from Zuul with Hystrix?

I suppose that what I am trying to do is very standard when using Zuul, Hystrix, and HATEOAS micro-services that communicate with each other, so maybe there is something that exists already (and that I couldn't find)?

Thanks !


Solution

  • I thought it was quite a common thing to achieve, but after a lot of research, I couldn't find a way to forward the X-FORWARDED-* headers automatically with Feign and Hystrix.

    So, I looked for another solution, which works and is quite clean:

    • In the Feign client from service-a to service-b, I declared a specific configuration "ServiceBFeignConfig", which, in addition to forward the token, also add the X-Forwarded-* headers corresponding to the gateway:
    @Configuration
    public class ServiceBFeignConfig {
    
        @Autowired
        private ApplicationProperties applicationProperties;
    
        @Bean
        public RequestInterceptor requestTokenBearerInterceptor() {
            return new RequestInterceptor() {
                @Override
                public void apply(RequestTemplate requestTemplate) {
                    OAuth2AuthenticationDetails details =
                            (OAuth2AuthenticationDetails) SecurityContextHolder.getContext().getAuthentication().getDetails();
                    requestTemplate.header("Authorization", "bearer " + details.getTokenValue());
    
                    if (applicationProperties.getFeign().getGatewayEnabled()) {
                        requestTemplate.header("X-Forwarded-Host", applicationProperties.getFeign().getGatewayHost());
                        requestTemplate.header("X-Forwarded-Port", applicationProperties.getFeign().getGatewayPort());
                        requestTemplate.header("X-Forwarded-Proto", applicationProperties.getFeign().getGatewayProtocol());
                        requestTemplate.header("X-Forwarded-Prefix", applicationProperties.getFeign().getServiceBPrefix());
                    }
                }
            };
        }
    
    }
    

    You can see that the gateway host and port is configured in the properties files (that is served by Spring Cloud Config in my case). The service-b prefix is also set in these files.

    These headers are only added if the "gatewayEnabled" property is set in the properties files.

    • You have to ignore this configuration from the component scan of Spring Boot, even if it needs the @Configuration annotation, so put it in a "ignorescan" package, and on your main Spring boot class, use:
    @ComponentScan(basePackages = { "com.myservice" }, excludeFilters = @ComponentScan.Filter(type = FilterType.REGEX, pattern = "com.myservice.ignorescan.*"))
    

    At the end, the Forward headers will be added if you have the gatewayEnabled set to true, and the API call to the gateway get the correct HATEOAS links.