Thursday, July 27, 2017

How can I get spring security to work behind a load balancer across multiple domains?

Leave a Comment

We are moving an old java / spring app into AWS, so it is behind an AWS Application Load Balancer. Tomcat is running directly behind the load balancers on port 8080, and we are using HTTP between the load balancer and tomcat.

The problem is under this scenario the spring security module doesn't recognize that the connection is secure.

I can resolve this issue by configuring the Connection:

<Connector port="8080"            protocol="HTTP/1.1"            connectionTimeout="20000"            proxyName="single-host.example.com"            secure="true"            scheme="https"            redirectPort="443"            proxyPort="443" /> 

Which works for a single host name. However, I need this to work across multiple host names.

I have tried skipping the proxy and adding:

server.tomcat.remote_ip_header=X-Forwarded-For server.tomcat.protocol_header=X-Forwarded-Proto 

But this doesn't seem to make any difference.

Is there a way to support multiple hostnames in this scenario?

4 Answers

Answers 1

AWS LoadBalancer sends X-Forwarded-Proto header when proxying request.

On Tomcat configure RemoteIpValve to have request.secure and other request variable interpreted from those headers.

<Valve className="org.apache.catalina.valves.RemoteIpValve"/> 

You should also omit setting proxyName on Connector conifiguration since it should come automatically from valve.

Answers 2

I am got some solution procedure. So I have provided 2 suggestion. First one is step by step pictorial view to solve your issue. If not, then go to the second one.

Second one is using X-Forwarded-Proto and related configuration to solve the issue. Hope it will help you.

Suggestion#1:

Amazon cloud environment with load balance support process is pretty straight-forward. A step by step tutorial is given here:Elastic Load Balancing (ELB) with a Java Web Application + Tomcat + Session Stickiness

Suggestion#2:

phillipuniverse has given a solution.

Configuring the following valve in Tomcat will make request.isSecure() function properly with the X-Forwarded-Proto header:

<Valve className="org.apache.catalina.valves.RemoteIpValve" protocolHeader="X-Forwarded-Proto" />

This can be added to Tomcat's server.xml under the <Host> element.


And of course, after all that, there is a very, VERY simple solution that fixes this problem from the very beginning. All that really needed to happen was to modify the proto channel filters from this:

if ("https".equals(invocation.getHttpRequest().getHeader("X-Forwarded-Proto"))) {     getEntryPoint().commence(invocation.getRequest(), invocation.getResponse()); } 

to:

if (invocation.getHttpRequest().isSecure() ||          "https".equals(invocation.getHttpRequest().getHeader("X-Forwarded-Proto"))) {     getEntryPoint().commence(invocation.getRequest(), invocation.getResponse()); } 

The final configuration here should be this:

<bean class="org.broadleafcommerce.common.security.channel.ProtoChannelBeanPostProcessor">     <property name="channelProcessorOverrides">       <list>         <bean class="org.broadleafcommerce.common.security.channel.ProtoInsecureChannelProcessor" />         <bean class="org.broadleafcommerce.common.security.channel.ProtoSecureChannelProcessor" />       </list>     </property> </bean> 

After that,

Some prefer to terminate SSL at the load balancer, and to not use Apache web server. In that case, you often accept traffic at the LB on 80 / 443, and then route traffic to Tomcat on 8080.

If you are using Spring's port mapping:

<sec:port-mappings>     <sec:port-mapping http="8080" https="443"/> </sec:port-mappings> 

This will not work as it does not override the port mapping in the new Channel Processors. Here is a configuration that will work, though:

<bean class="org.broadleafcommerce.common.security.channel.ProtoChannelBeanPostProcessor">     <property name="channelProcessorOverrides">         <list>             <bean class="org.broadleafcommerce.common.security.channel.ProtoInsecureChannelProcessor" >                 <property name="entryPoint">                     <bean class="org.springframework.security.web.access.channel.RetryWithHttpEntryPoint">                         <property name="portMapper" ref="portMapper"/>                     </bean>                 </property>             </bean>             <bean class="org.broadleafcommerce.common.security.channel.ProtoSecureChannelProcessor" >                 <property name="entryPoint">                     <bean class="org.springframework.security.web.access.channel.RetryWithHttpsEntryPoint">                         <property name="portMapper" ref="portMapper"/>                     </bean>                 </property>             </bean>         </list>     </property> </bean> 

Resource Link: HTTPS/SSL/Spring Security doesn't work in both a load balancer and non-load balancer environment #424

Answers 3

You should setup HTTPS connection on the LB, then you'll have a proper TLS connection between the LB and the tomcat so spring will stop crying. You'll just have to provide a self-signed certificate to the LB and setup your spring security module with the private key that have generated this self signed certificate.

(a more complex option: setup properly the tomcat proxy, to force it to encapsulate the HTTP stream of the LB in an HTTPS stream. Setup all TLS requirements in the proxy: certificate, private key...)

Answers 4

Did you try to put LB address as proxyName? It might work on your case.

If You Enjoyed This, Take 5 Seconds To Share It

0 comments:

Post a Comment