Home > OS >  How to use Tomcat RemoteIpFilter in Spring Boot
How to use Tomcat RemoteIpFilter in Spring Boot

Time:10-16

Objective: get the remote address of users (i.e. request.getRemoteAddr()) in Spring Boot web applications.

Introduction: As known, the getRemoteAddr method returns the address of the immediate caller which might be the proxy server or any other servers in between the original user and the final destination server (if there's any). To find the real address of the user, one needs to look into other headers (e.g. X-Forwarded-For). However I found that Tomcat (which is our web container) has a filter called RemoteIpFilter that takes care of this issue by copying the original address, stored in another header (e.g. X-Forwarded-For) back so that calling request.getRemoteAddr() gives you the originl address.

I added the filter to the code and everything works just fine:

@Bean
FilterRegistrationBean<RemoteIpFilter> remoteIpFilter() {
    val filter = new FilterRegistrationBean<>(new RemoteIpFilter());
    filter.addUrlPatterns("/*");
    return filter;
}

However, there's an issue; when the request is a multipart one, Spring rejects it with:

The request was rejected because the header value "multipart/mixed; boundary="----=_Part_0_blahblah"; charset=utf-8" is not allowed.

I can see that the exception is thrown because FilterChainProxy filter of Spring wraps the request in StrictFirewalledRequest that in turn, has some checks regarding what is allowed and what is not (e.g. the above content-type is not allowed).

I couldn't find a proper solution for the above issue, other than putting the RemoteIpFilter filter before FilterChainProxy filter:

// right before the tracing filter!
filter.setOrder(SleuthWebProperties.TRACING_FILTER_ORDER - 1);

Questions

  1. Is using RemoteIpFilter a good idea?
  2. Is there a better way to fix the above issue?
  3. What's the best order number for RemoteIpFilter?

Thanks.

CodePudding user response:

Thanks to Piotr P. Karwasz's comment I figured out how to do this. It was not straightforward since it depends on the environment and other components (e.g. LB, reverse proxy, etc). But this is how you would go about it:

  1. Don't use the RemoteIpFilter if you're using recent versions of Spring Boot Tomcat. Spring Boot's got you covered by creating a RemoteIpValue (in TomcatWebServerFactoryCustomizer), if it feels the need.
  2. If you're deploying on some cloud platform, chances are you don't need to do anything, because if Spring detects the platform as a cloud one, it automatically creates the value. No need to tell it explicitly. (See what CloudPlatform.getActive(env) returns.)
  3. If you're not deploying on a cloud platform, probably you need to set at least one of the following properties:
    • ServerProperties#forwardHeadersStrategy = NATIVE (as Piotr suggested)
    • ServerProperties.Tomcat.Remoteip#protocolHeader (to anything e.g. X-Forwarded-Proto)
    • ServerProperties.Tomcat.Remoteip#remoteIpHeader (to anything e.g. X-Forwarded-For)
  4. To see if it really worked, enabled debug log for org.apache.catalina.valves.RemoteIpValve and you should be able to see its logs about changing host, addresses, etc.

In our case, I didn't need to do anything on Spring, since we deploy our app to K8S on which Spring creates the valve. Then why didn't I get the correct remote address? It turns out that our reverse proxy, Nginx has the default configuration:

proxy_set_header X-Forwarded-For        $remote_addr;

that essentially overwrites the original value, that is to be used by the RemoteIpValve to set back the remote address. In other words, the valve correctly reads X-Forwarded-For header but the header doesn't contain the correct value (the address of the user). Note that $remote_addr is not necessarily the address of the user. It might be the address of some other intermediary proxy/LB.

So you've got two options to fix this:

  1. Set ServerProperties.Tomcat.Remoteip#remoteIpHeader to X-Original-Forwarded-For because that's where Nginx backs up the original value of X-Forwarded-For before overwriting with $remote_addr. (Seems like a hack to me!)
  2. Configure Nginx to not touch X-Forwarded-For header at all. (Seems like a hack to me!)
  3. Configure Nginx to append X-Forwarded-For instead of overwriting it.

To configure Nginx to append, you've got two options:

  1. Work on something like this proxy_set_header X-Forwarded-For "$remote_addr, $server_addr" and configure Nginx to append instead of overwrite. (See this for how to do it on K8S. Though I couldn't get it working; Nginx complained about the number of arguments for proxy-set-header or something like that. Also I'm not sure if it's a bulletproof solution)
  2. Configure your Helm chart installation values:
config:
  "use-forwarded-headers": "true" # not true
  "compute-full-forwarded-for": "true" # not true

Thanks Piotr for the hint :)

PS: There's also some other notes on the new fancy header Forwarded that is supposed to solve the whole mess, but apparently Nginx doesn't fully support it yet.

PSS: I didn't spend time figuring out why the exception is thrown or how to prevent it. That's probably for another day :)

  • Related