17

I have a Spring MVC application secured with Spring Security. The majority of the application uses simple HTTP to save resources, but a small part processes more confidential information and requires an HTTPS channel.

Extract from the security-config.xml :

<sec:http authentication-manager-ref="authenticationManager" ... >
    ...
    <sec:intercept-url pattern="/sec/**" requires-channel="https"/>
    <sec:intercept-url pattern="/**" requires-channel="http"/>
</sec:http>

All worked fine until we decided to migrate it to the main server, where the application servers run behind reverse proxies. And as now HTTPS is processed by the reverse proxies the application server only sees HTTP requests, and disallows access to the /sec/** hierarchy.

After some research, I found that the proxies add a X-Forwarded-Proto: https header (*), but in Spring Security HttpServletRequest.isSecure() is used to determine the channel security offered (extract from SecureChannelProcessor javadoc).

How can I tell Spring Security that a X-Forwarded-Proto: https header is enough for a secure request?

I know I could report that part on proxies configuration, but the proxies administrator really does not like that solution, because there are many application behind the proxies and the configuration could grow to a non manageable state.

I an currently using Spring Security 3.2 with XML config, but I'm ready to accept answers based on Java config and/or more recent version.

(*) Of course, the proxies remove the header if it was present in incoming request, so the application can be confident in it.

informatik01
  • 16,038
  • 10
  • 74
  • 104
Serge Ballesta
  • 143,923
  • 11
  • 122
  • 252

3 Answers3

22

Kind of a followup to NeilMcGuigan's answer that showed that the solution was servlet container side.

Tomcat is even better. There is a valve dedicated to masking the side effects of a reverse proxy. Extract from Tomcat documentation for Remote IP Valve:

Another feature of this valve is to replace the apparent scheme (http/https), server port and request.secure with the scheme presented by a proxy or a load balancer via a request header (e.g. "X-Forwarded-Proto").

Example of the valve configuration :

<Valve className="org.apache.catalina.valves.RemoteIpValve"
    internalProxies="192\.168\.0\.10|192\.168\.0\.11"
    remoteIpHeader="x-forwarded-for" proxiesHeader="x-forwarded-by"
    protocolHeader="x-forwarded-proto" />

That way with no other configuration of the application itself, the call to Request.isSecure() will return true if the request contains a header field of X-Forwarded-Proto=https.

I had thought of two other possibilities, but definitively prefere that one :

  • use a filter active before Spring Security ChannelProcessingFilter to wrap the request with a HttpServletRequestWrapper overriding isSecure() to process a X-Forwarded-Proto header - need writing and testing the filter and the wrapper
  • use a Spring BeanPostProcessor to look for a ChannelProcessingFilter and manually inject a ChannelDecisionManager able to consider the X-Forwarded-Proto header - really too low level
Serge Ballesta
  • 143,923
  • 11
  • 122
  • 252
  • 1
    I think this should be the selected answer. Definitely the best solution in my opinion. Thanks!! – Giordano Aug 03 '15 at 20:41
  • 3
    @Giordano : As I ask the question, the answer that help me to find this one was Neil's one. That's the reason why I accepted it :-) - but as it was not as complete as I wanted I added this one. – Serge Ballesta Aug 03 '15 at 22:24
  • Makes perfect sense, thanks for sharing this piece of extra info that you found ^^ – Giordano Aug 04 '15 at 11:40
  • !!! If you run a dual stack ipv4/ipv6 you must add patterns corresponding to both stacks to internalProxies. I was bitten by this since the default values of interalProxies, don't include ipv6 in my version of Tomcat. – revau.lt Jun 24 '16 at 13:58
  • Hi @SergeBallesta, would you, by any chance, know the solution if i am using IBM WebSphere? I am behind an LB where the SSL/TLS terminates, and the LB offloads http to the WS(WebServer), which then forwards the request to the AP(AppServer). My AppServer, Spring Security, is expecting https, but as it is http, i get a 302, as Spring tries to redirect it to the https, which will not be found obviously. – jumping_monkey Apr 03 '20 at 07:40
15

Spring Boot makes it dead simple (at least with embedded Tomcat).

1. Add the following lines to your application.properties:

server.forward-headers-strategy=native
server.tomcat.remote-ip-header=x-forwarded-for
server.tomcat.protocol-header=x-forwarded-proto

2. Do the following trick with your HttpSecurity configuration.

// final HttpSecurity http = ...
// Probably it will be in your `WebSecurityConfigurerAdapter.configure()`

http.requiresChannel()
            .anyRequest().requiresSecure()

Source is Spring Boot reference guide

84.3 Enable HTTPS When Running behind a Proxy Server

Please also check the answer below for a specifics related to Spring Boot 2.2

Roman Nikitchenko
  • 12,800
  • 7
  • 74
  • 110
  • 1
    Is it possible to configure this redirect to use 301 permanent instead of 302 temporary? – Niemi May 27 '18 at 02:12
  • I have no proved answer but as it comes from Tomcat I would check something like this: https://stackoverflow.com/questions/32817514/perform-301-redirect-from-http-to-https-in-apache-tomcat – Roman Nikitchenko May 28 '18 at 05:23
  • 2
    Also works for embedded Jetty. Used this for Internet facing AWS ALB HTTPS – fdaugan Apr 12 '19 at 07:40
  • Hi @RomanNikitchenko, would you, by any chance, know the solution if i am using IBM WebSphere? I am behind an LB where the SSL/TLS terminates, and the LB offloads http to the WS(WebServer), which then forwards the request to the AP(AppServer). My AppServer, Spring Security, is expecting https, but as it is http, i get a 302, as Spring tries to redirect it to the https, which will not be found obviously. – jumping_monkey Apr 03 '20 at 07:41
  • @jumping_monkey I would check it. In theory it should work - I use different LB including Cloud Foundry router, HAProxy and HAProxy + K8s ingress - they all route correctly. Spring Boot should accept that it will receive HTTP. – Roman Nikitchenko Apr 08 '20 at 09:16
8

If your site is HTTPS and you're running Apache Tomcat behind another system that's handling TLS termination, you can tell Tomcat to "pretend" that it's handling the TLS termination.

This makes request.isSecure() return true;

To do so, you need to add secure="true" to your Connector config in server.xml.

https://tomcat.apache.org/tomcat-7.0-doc/config/http.html

See also the scheme attribute.

Neil McGuigan
  • 46,580
  • 12
  • 123
  • 152