Spring-cloud-gateway: ProxyExchange - ability to override sensitive headers

Created on 26 Jun 2020  ·  10Comments  ·  Source: spring-cloud/spring-cloud-gateway

Problem
If we use webmvc version of gateway we have ProxyExchange object that forwards the requests to downstream. It removes two headers "cookie" and "authorization" as default. If we compare this behavior with simple yaml routes config - nothing is removed and all sent to downstream as-is.
I have a downstream service that needs basic authentication, and intentionally I don't want (in this case) to do anything in gateway and trying to simply delegate request to downstream. But I want to use webmvc way of gateway because I need to manipulate a bit on response.

Describe the solution you'd like
At the moment ProxyExchange.DEFAULT_SENSITIVES is public. That means you do expect people to play with it. I made it work by removing the "authorization" from the map.
But it looks ugly. public static access outside (not nice).

I was expecting ProxyExchange defaults to be overridden by config. Like ZUUL we can set our own defined list of sensitive headers. Spring Cloud Gateway just adds sensitive headers from config on top of existing defaults.

Describe alternatives you've considered
I have used public access...
But thinking to configure other way and use filters to play with response.

Additional context
IMO - Both ways we should have same behavior. If there is a need to remove default sensitives then why just ProxyExchange?

bug help wanted

Most helpful comment

I used proxy with spring-cloud-gateway-mvc for a long time and decided to refactor it to spring-cloud-gateway-webflux. The first thing that stops working is "sensitive" headers manipulation. If compare the source code of ProxyExchange from "mvc" and "webflux" packages the reason can be found quickly.

Working "MVC" class has:

public class ProxyExchange<T> {
    public static Set<String> DEFAULT_SENSITIVE = new HashSet(Arrays.asList("cookie", "authorization"));
     ...
    private Set<String> sensitive;
    ...

    public ProxyExchange(RestTemplate rest, NativeWebRequest webRequest, ModelAndViewContainer mavContainer, WebDataBinderFactory binderFactory, Type type) {
        this.responseType = type;
        this.rest = rest;
        this.webRequest = webRequest;
        this.mavContainer = mavContainer;
        this.binderFactory = binderFactory;
        this.delegate = new RequestResponseBodyMethodProcessor(rest.getMessageConverters());
    }

Non-working "webflux" code is:

public class ProxyExchange<T> {
    public static Set<String> DEFAULT_SENSITIVE = new HashSet(Arrays.asList("cookie", "authorization"));
    ...
    private Set<String> sensitive;
    ...

    public ProxyExchange(WebClient rest, ServerWebExchange exchange, BindingContext bindingContext, Type type) {
        this.exchange = exchange;
        this.bindingContext = bindingContext;
        this.responseType = type;
        this.rest = rest;
        this.sensitive = new HashSet(DEFAULT_SENSITIVE.size());
        this.sensitive.addAll(DEFAULT_SENSITIVE);
        this.httpMethod = exchange.getRequest().getMethod();
    }

As you can see here default sensitive list is created in the constructor, thus hard-coded and can only be expanded later by adding more headers. You are not able to remove "cookie" or "authentication" from the default list.
In compare to working "mvc" proxy headers are initialized later at the builder:

    private BodyBuilder headers(BodyBuilder builder) {
        Set<String> sensitive = this.sensitive;
        if (sensitive == null) {
            sensitive = DEFAULT_SENSITIVE;
        }

       ...

        return builder;
    }

thus if user doesn't provide sensitive header list - the default ("cookie" and "authorization") is used. If user wants to customize sensitive list he is able to do it. For example by setting spring.cloud.gateway.proxy.sensitive application configuration property
Webflux proxy has similar piece of code to manipulate sensitive headers list:

 public ProxyExchange<T> sensitive(String... names) {
        if (this.sensitive == null) {
            this.sensitive = new HashSet();
        }

}

... but since sensitive variable is already initialized in the constructor and is never null this predicate never works.

I think this is a bug in PoxyExchange class at org.springframework.cloud.gateway.webflux package. User must have control of the proxy headers. My use case - is proxy for user web session that is implemented using JWT token on backend. I have to "convert" one authentication to another and must replace "cookie" with new "authentication" and pass Bearer token down to web-service. Unfortunately it is not possible with ProxyExchange and Webflux.
Quick workaround at the moment is to use ProxyExchange from MVC package.

All 10 comments

There is also ProxyExchange.sensitive().

Yes but that is additive on top of defaults.
Using this I cannot say that "authorization" is not sensitive... because that is default sensitive and it will stay sensitive unless I change the default static sensitive list by using public access.

spring.cloud.gateway.proxy.sensitive

I looked at that... it is also using the same method underneath.
ProxyExchange.sensitive
Technically same as calling that method to add more sensitive headers on top of defaults.

I also thought this property should be to define your list of sensitive headers... but this is to add your list of sensitives on top of defaults.

I might be wrong but I can check again the source code of gateway that handles this property...

Bumping this issue

ProxyExchange is created with "sensitive" set to DEFAULT_SENSITIVE (cookie and auth), then there is only a method to add more headers to the list, but no way to remove any

This is exactly I explained to start with.

There should be a way to define own set of sensitive headers like ZUUL we have.

I used proxy with spring-cloud-gateway-mvc for a long time and decided to refactor it to spring-cloud-gateway-webflux. The first thing that stops working is "sensitive" headers manipulation. If compare the source code of ProxyExchange from "mvc" and "webflux" packages the reason can be found quickly.

Working "MVC" class has:

public class ProxyExchange<T> {
    public static Set<String> DEFAULT_SENSITIVE = new HashSet(Arrays.asList("cookie", "authorization"));
     ...
    private Set<String> sensitive;
    ...

    public ProxyExchange(RestTemplate rest, NativeWebRequest webRequest, ModelAndViewContainer mavContainer, WebDataBinderFactory binderFactory, Type type) {
        this.responseType = type;
        this.rest = rest;
        this.webRequest = webRequest;
        this.mavContainer = mavContainer;
        this.binderFactory = binderFactory;
        this.delegate = new RequestResponseBodyMethodProcessor(rest.getMessageConverters());
    }

Non-working "webflux" code is:

public class ProxyExchange<T> {
    public static Set<String> DEFAULT_SENSITIVE = new HashSet(Arrays.asList("cookie", "authorization"));
    ...
    private Set<String> sensitive;
    ...

    public ProxyExchange(WebClient rest, ServerWebExchange exchange, BindingContext bindingContext, Type type) {
        this.exchange = exchange;
        this.bindingContext = bindingContext;
        this.responseType = type;
        this.rest = rest;
        this.sensitive = new HashSet(DEFAULT_SENSITIVE.size());
        this.sensitive.addAll(DEFAULT_SENSITIVE);
        this.httpMethod = exchange.getRequest().getMethod();
    }

As you can see here default sensitive list is created in the constructor, thus hard-coded and can only be expanded later by adding more headers. You are not able to remove "cookie" or "authentication" from the default list.
In compare to working "mvc" proxy headers are initialized later at the builder:

    private BodyBuilder headers(BodyBuilder builder) {
        Set<String> sensitive = this.sensitive;
        if (sensitive == null) {
            sensitive = DEFAULT_SENSITIVE;
        }

       ...

        return builder;
    }

thus if user doesn't provide sensitive header list - the default ("cookie" and "authorization") is used. If user wants to customize sensitive list he is able to do it. For example by setting spring.cloud.gateway.proxy.sensitive application configuration property
Webflux proxy has similar piece of code to manipulate sensitive headers list:

 public ProxyExchange<T> sensitive(String... names) {
        if (this.sensitive == null) {
            this.sensitive = new HashSet();
        }

}

... but since sensitive variable is already initialized in the constructor and is never null this predicate never works.

I think this is a bug in PoxyExchange class at org.springframework.cloud.gateway.webflux package. User must have control of the proxy headers. My use case - is proxy for user web session that is implemented using JWT token on backend. I have to "convert" one authentication to another and must replace "cookie" with new "authentication" and pass Bearer token down to web-service. Unfortunately it is not possible with ProxyExchange and Webflux.
Quick workaround at the moment is to use ProxyExchange from MVC package.

Exactly, what I raised to start with. I think I am going to contribute to fix that... More often people finding same issue now, but this is still open ticket.

Thank you, @ilyasjaan for contribution. Let me know if I can help. I am going to fix it locally, but will be waiting for official release to make it available at production

Hello. When this issue be merged with main code? I don't understand where it jammed.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

zjengjie picture zjengjie  ·  6Comments

pravinkumarb84 picture pravinkumarb84  ·  7Comments

xiaozhiliaoo picture xiaozhiliaoo  ·  4Comments

xfworld picture xfworld  ·  3Comments

esacaceres picture esacaceres  ·  7Comments