40

We have a Spring Boot (Spring MVC) app with embedded Tomcat on a dedicated appserver behind an Apache SSL proxy.

The SSL port on the proxy server is 4433, forwarding to port 8080 on the appserver.

So the URL to the proxy server is forwarding like:

https://proxyserver:4433/appname   >>forward>>   http://appserver:8080/

When running WITHOUT proxy, the first thing that happens is that
Spring Security redirects the request, like:

http://appserver:8080/   >>redirect>>   http://appserver:8080/login

to display the login form, by extending WebSecurityConfigurerAdapter with

  ...
  httpSecurity.formLogin().loginPage("/login") ...
  ...

It works fine without the proxy, but WITH proxy the redirect needs to be changed,
so Spring should instead redirect to the corresponding proxy URL, like:

http://appserver:8080/   >>redirect>>   https://proxyserver:4433/appname/login

but no success yet.

I am trying to apply this solution: 59.8 Use Tomcat behind a front-end proxy server

We have configured mod_proxy in Apache, and verified that it sends the expected headers:

X-Forwarded-For: xxx.xxx.xxx.xxx
X-Forwarded-Host: proxyserver
X-Forwarded-Port: 4433
X-Forwarded-Proto: https

The application is started with parameters:

export ARG1='-Dserver.tomcat.protocol-header=x-forwarded-proto' 
export ARG2='-Dserver.tomcat.remote-ip-header=x-forwarded-for'
java $ARG1 $ARG2 -jar webapp.jar

Still the redirect does not work.

It will keep redirecting locally, to http://appserver:8080/login which is not available to the clients.

Is there anything else we need to do to make this scenario work?


UPDATE

Also, I am concerned about the "/appname" part in the proxy URL. On the appserver the application is rooted at "/". How should Spring be instructed that "/appname" should be included in all URLs sent back to the clients, when going thru the proxy?

Community
  • 1
  • 1
Rop
  • 3,359
  • 3
  • 38
  • 59
  • Slightly disturbingly, re the JVM properties: server.tomcat.protocol-header, server.tomcat.remote-ip-header ..... at http://docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/htmlsingle/#howto-use-tomcat-behind-a-proxy-server they are written with underscores after the last dot, while at http://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html they are written with dashes. Which is correct? I tried both variants, but did not resolve the issue above. – Rop Aug 22 '14 at 21:57
  • it should be underscore `server.tomcat.remote_ip_header=x-forwarded-for` `server.tomcat.protocol_header=x-forwarded-proto` – ikumen Aug 23 '14 at 01:35
  • 5
    Spring boot uses relaxed binding for properties, so it probably doesn't matter if they're underscores or dashes or even dots – gyoder Aug 23 '14 at 11:16

8 Answers8

40

I had the same problem the other day. After some debugging of Spring Boot 1.3 I found the following solution.

1. You have to setup the headers on your Apache proxy:

<VirtualHost *:443>
    ServerName www.myapp.org
    ProxyPass / http://127.0.0.1:8080/
    RequestHeader set X-Forwarded-Proto https
    RequestHeader set X-Forwarded-Port 443
    ProxyPreserveHost On
    ... (SSL directives omitted for readability)
</VirtualHost>

2. You have to tell your Spring Boot app to use these headers. So put the following line in your application.properties (or any other place where Spring Boots understands properties):

server.use-forward-headers=true

If you do these two things correctly, every redirect your application sends will not go to http://127.0.0.1:8080/[path] but automatically to https://www.myapp.com/[path]

Update 1. The documentation about this topic is here. You should read it at least to be aware of the property server.tomcat.internal-proxies which defines the range of IP-addresses for proxy servers that can be trusted.

Update 2021 The documentation is moved to here. The Spring Boot configuration is a litte different now.

Igor Mukhin
  • 15,014
  • 18
  • 52
  • 61
  • 3
    `server.use-forward-headers=true` works for Google Cloud Load Balancer – Jan Vladimir Mostert Jun 06 '16 at 17:41
  • Damn, You saved me. ``ProxyPreserveHost On`` is freakin' important... :) If You ommit it, Your application will redirect to localhost instead of keeping the original host with canonical address. – thorinkor Aug 01 '18 at 07:53
  • Hey @Igor Mukhin the link is broken can you update the link? I would really like to read more about this. – Norbert Aug 15 '18 at 13:45
  • 1
    @Norbert Updated, but you could actually do it yourself. – Igor Mukhin Aug 16 '18 at 15:23
  • what if I'm just using port 80 on my Apache web server for HTTP? i put the port 80 and http for the forwarded headers but nothing works – ennth Apr 12 '20 at 02:32
  • Anyone coming here: Note that `server.use-forward-headers=true` is deprecated in current versions of Spring Boot (>2.2). – das Keks Jun 18 '20 at 15:57
  • it was deprecated. Intellij recommending server.forward-headers-strategy=framework as property. But i didnt tried yet – withoutOne Oct 05 '20 at 08:42
6

Your proxy looks fine, and so does the backend app, up to a point, but it doesn't seem to be seeing the RemoteIpValve modified request. The default behaviour of the RemoteIpValve includes a pattern match for the proxy IP address (as a security check) and it only modifies requests that it thinks are from a valid proxy. The pattern defaults in Spring Boot to a well-known set of internal IP addresses like 10.*.*.* and 192.168.*.*, so if your proxy isn't on one of those you need to explicitly configure it, e.g.

server.tomcat.internal-proxies=172\\.17\\.\\d{1,3}\\.\\d{1,3}|127\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}

(using properties file format, which means you have to double escape the backslashes).

You can see the what is happening in the RemoteIpValve if you set

logging.level.org.apache.catalina.valves.RemoteIpValve=DEBUG

or set a breakpoint in it.

Dave Syer
  • 56,583
  • 10
  • 155
  • 143
2

A typical solution to this problem is to let the proxy handle any required rewrite. For example, in Apache you can use the rewrite_module and/or headers_module to correct headers. As another example, Nginx handles this and other similar cases automatically for you after configuring upstream servers.

In response to comments:

What are the remote_ip_header and protocol_header spring boot configuration values?

Let's forget Spring Boot for a moment. Tomcat, the embedded servlet container, features a valve known as the RemoteIpValve. This valve is a port of the Apache remotip_module. The primary purpose of this valve is to treat the "useragent which initiated the request as the originating useragent" for "the purposes of authorization and logging". In order for this valve to be used it needs to be configured.

Please find more information about this valve here.

Spring Boot conveniently supports configuring this valve via application.properties through the server.tomcat.remote_ip_header and server.tomcat.protocol_header properties.

Newbie
  • 7,031
  • 9
  • 60
  • 85
  • OK... but I'm rather trying to get the spring-documented support to work: http://docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/htmlsingle/#howto-use-tomcat-behind-a-proxy-server --- What are these parameters supposed to do? I don't see any effect from them, but seems like a simpler solution if I can get it to work... guess I should clarify my question, though... – Rop Aug 24 '14 at 11:36
  • Rewriting 302 code to 200 and location from http to https? No, rewrite by a proxy is not a solution. It should be handled by app.properties from spring boot but it isn't (at least for me). – 3h4x Nov 21 '14 at 11:59
  • I´m still getting the same issue. Did you fixed Rop? If so, please could you share with me? – Bruno De Freitas Barros Dec 22 '14 at 13:57
  • URL rewriting is pain in the ass. If JSP and/or some other template engine is used, rewrite module converts your live in a nightmare. One should use the tomcat connector's settings (line "sceme", "proxyName", "proxyPort" etc.) as long as it is possible, – 30thh Mar 29 '16 at 08:48
1

I had exactly the same case using haproxy as load balancer with the below configuration, which worled for me. The only thing is the client IP is in request.getRemoteAddr() and not in "X-Forwarded-For" header

frontend www
  bind *:80
  bind *:443 ssl crt crt_path
  redirect scheme https if !{ ssl_fc }
  mode http
  default_backend servers

backend servers
  mode http
  balance roundrobin
  option forwardfor
  server S1 host1:port1 check
  server S2 host2:port2 check
  http-request set-header X-Forwarded-Port %[dst_port]
  http-request add-header X-Forwarded-Proto https if { ssl_fc }

In application.properties:

 server.use-forward-headers=true
1

There are several properties that you can configure, related to this. application.yaml example:

server:
  forward-headers-strategy: native
  tomcat:
    use-relative-redirects: true
    protocol-header: x-forwarded-proto
    remote-ip-header: x-forwarded-for

Setting server.forward-headers-strategy: native is the replacement of the deprecated server.use-forward-headers:true

Mark Lagendijk
  • 6,247
  • 2
  • 36
  • 24
  • Here's a link to latest documentation. Many links to this are broken as it has been moved to a howto page. [Tomcat, Spring Boot and forwarded proxy headers](https://docs.spring.io/spring-boot/docs/current/reference/html/howto.html#howto-use-behind-a-proxy-server) – kaicarno Aug 10 '20 at 13:14
0

Have you tried setting

  server.context-path=/appname

In Spring Boot?

Piotr Gwiazda
  • 12,080
  • 13
  • 60
  • 91
0

Try setting the Rewrite rule like: https://proxyserver:4433/appname >>forward>> http://appserver:8080/appname

And then set your application context to "appname" server.context-path=/appname

So locally you can run by http://appserver:8080/appname and via Reverse Proxy you access via https://proxyserver:4433/appname

Since I am using JBOSS, changes in standalone.xm of jboss:

<http-listener name="default" socket-binding="http" redirect-socket="https" proxy-address-forwarding="true" enable-http2="true"/>

Tomcat would have similar config, to inform Tomcat (proxy-address-forwarding="true") to respect the proxy forwarding address.

Sagar Mody
  • 525
  • 5
  • 9
0

server.use-forward-headers=true did not work for me, experienced a weird issue where X-Forwarded-For header is not populated to HttpServletRequest consistently.

Ended up using ForwardedHeaderFilter: https://stackoverflow.com/a/51500554/986942.

On top of that, make sure the load balancer (proxy) provide the following headers properly:

proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto https;
Yuming Cao
  • 935
  • 1
  • 10
  • 22