Login loop with Spring Security requires-channel and Amazon Elastic Load Balancer - spring

I'm trying to get my spring security working on a server using Amazon Elastic Load Balancer (ELB). The ELB is configured on port 80 to forward to my app on port 8080 and on port 443 to also forward to 8080.
<security:intercept-url pattern="/login.xhtml" access="IS_AUTHENTICATED_ANONYMOUSLY" requires-channel="https" />
<security:port-mappings>
<security:port-mapping http="80" https="443" />
</security:port-mappings>
Whenever I access this page I get into a login loop. Any idea how to solve this? Not sure if Spring Security is having issues with the fact ELB is forward traffic from https port 443 to my app on port 8080.

It turns out that Spring Security uses ServletRequest.getServerPort() to determine whether it is using a secure port. My tomcat was configured using 8080 and 8443 so when the ELB forward the request from 443 to my internal tomcat on 8443 the webapp did not accept this as a secure port:
20 Jun 18:16:49,184 ["http-bio-8443"-exec-5] DEBUG org.springframework.security. web.access.channel.RetryWithHttpsEntryPoint - Redirecting to: /login.xhtml
I also tried using the proxyport but couldnt get this to work.
Also if you configure the spring security ports to use 8443 instead then it doesnt do the redirect correctly (it will redirect the app to 8443 which doesnt exist externally).
Long story short...the following settings worked:
ELB forward 80->80 and 443->443.
Setup tomcat to use 80 and 443.
Setup port mappings to use 80 and 443 on Spring Security

A redirect loop almost always happens because you have a secured URL which should not be secured. All URLs are secured by default in spring security.
Also if JavaScript, CSS or image resources are loaded with separate requests by the login page their URLs are also secured and this might be causing the loop.
Enable the debug log and you should see why you get redirected.
This will help you on debug logging (search the page for debug).

Related

Spring Security and Port Forwarding

I have a Spring Boot application running with Spring Security in a Ubuntu server. The application is listening on port 9090 and I have added a port forward from port 80 to port 9090.
However I'm having trouble serving static content via port 80 (getting 403 responses).
If I make the same request via port 9090 the request works ok.
Do I have to add something to the Spring Security config class?
EDIT: The behaviour seems to be somewhat erratic. Just restarted the webserver and it looks that is able to serve some static contents. Html, css and js are working fine. Favicon and some image files are still not working (403 response).

How to run Spring Boot on port 443

I ran into an issue with one of our Spring Boot applications. We have it running with https on port 8443 and it all works fine. Now we're building an integration with an external payment processor and they require that we have a callback endpoint in our application on port 443.
Some research tells me that deploying a Spring boot app listening to a port number below 1024 is not allowed. The threads i find on this issue usually say "use a port number above 1024" and the poster walks
off happy. I already have that and need to figure out a solution that uses port 443.
Does anyone have any recommendations? Could i solve this by building an Apache proxy for the callback endpoint?
We have web applications using Apache2 and port 443 on the same server, so the Boot application needs to coexist with that.
OK, i managed to solve this issue by myself in a pretty simple and elegant way. In the process, i also solved the issue of Spring boot applications having to be called with a port number in the URL, which has been annoying me.
I found this thread: Spring Boot with embedded Tomcat behind Apache proxy
In it, the solution is pretty much laid out. I had to activate three apache2 mods:
sudo a2enmod proxy
sudo a2enmod proxy_http
sudo a2enmod headers
I added these lines to my Apache2 vhost config file, right under ServerName in the VirtualHost tag:
SSLProxyEngine on
ProxyPass / https://127.0.0.1:8443/
ProxyPassReverse / https://127.0.0.1:8443/
RequestHeader set X-Forwarded-Proto https
RequestHeader set X-Forwarded-Port 443
ProxyPreserveHost On
8443 is the port number used by the Spring Boot application, as configured in the server.port property.
As a bonus, this also means we no longer need to open the ports used by our Boot applications in our firewall.
There several Web-Server or Reverse-Proxy solutions, which can listen on port 443 and route your requests to Port 8443 of your Spring Boot application server.
Beside Apache2 there are:
nginx : https://www.nginx.com/
haproxy : https://www.haproxy.org/
There are probably many more, but those 3 I used so far.
If your application is running in a cloud - the cloud provider offers typically also services, which can do this job, e.g. AWS ELB.

How to treat request from HTTP and HTTPS inseparately Spring Boot 2 to run Tomcat on two separate ports?

I am adding the secure port with the non-secure port already opened. I wants to separate the traffic between two and forward to different Spring boot 2 Controllers. Wonder how I can achieve that?
In most solutions I have seen so far, https / SSL is terminated infront of Tomcat or Spring Boot application, so that Tomcat / Spring Controller receives only http requests on port 8080 (for example).
Termination of SSL in front of Tomcat / Spring Boot could be done with a Reverse Proxy or Web Server, like Apache2 or nginx.
Then the communication flow looks like this:
User ==HTTP-80==> Apache2 ==HTTP-8080==> Tomcat/Spring Boot
User ==HTTPS-443==> Apache2 ==HTTP-8080==> Tomcat/Spring Boot
("HTTP-80" means HTTP protocol on port 80. "== ==>" is arrow showing communication flow.)

Is there any specific port for https redirection in Spring Boot embedded tomcat?

I already have a tomcat server running in a VM with port as 443 and redirect port as 8443. Can I configure the redirect port for spring boot application also as 8443 and run in same VM? Would I face error like port already in use? If yes, are there any specific port to be used for this purpose? I would not like to try since this is a production environment VM.
By default the https port used is 9393 in springboot.So , in your application if you need it to be 8443 , you need to configure it in the application.properties or application.yml like
application.yml
server:
port: 8443
or
application.properties
server.port=8443
Yes, you will have issues if some other application is using the same https port on the same VM, you will have to find a port that is not used by any other application and assign it for your springboot application. Check in your vm if any other application is already mapped to this port, if not you can use this port without any issues.
Please note that :
If HTTPS is enabled, it will completely replace HTTP as the protocol
over which the REST endpoints and the Data Flow Dashboard interact.
Plain HTTP requests will fail - therefore, make sure that you
configure your Shell accordingly.
Spring doc.

Spring Boot with embedded Tomcat behind Apache proxy

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?
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.
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.
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.
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
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
Have you tried setting
server.context-path=/appname
In Spring Boot?
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.
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;

Resources