Summary
• Running behind a reverse proxy that performs SSL termination, and adds the X-Forwarded-... headers
• The OAuth2 redirect URI uses HTTP
instead of HTTPS
• This is a bummer specially considering that Facebook only accepts HTTPS
for redirect URLs.
Actual Behavior
Hitting: https://somedomain/oauth2/authorization/facebook
Redirects to Facebook sending the redirect_uri
redirect_uri=http%3A%2F%2Fsomedomain%3A443%2Flogin%2Foauth2%2Fcode%2Ffacebook
Unencoded
redirect_uri=http://somedomain:443/login/oauth2/code/facebook
Expected Behavior
Should redirect to this instead
redirect_uri=https://somedomain:443/login/oauth2/code/facebook
With the difference in the scheme
• expected: https
• but was: http
Configuration
application.yml
...
spring:
security:
oauth2:
client:
registration:
login-facebook:
provider: facebook
client-id: facebook-client-id
client-secret: facebook-client-secret
scope: public_profile, email
client-name: Login using Facebook
server:
use-forward-headers: true
...
Version
• Spring Boot 2.0.3 • Spring Security 5.0.6 • Spring Security OAuth2 Client 5.0.6
Analysis
I tracked this down to: org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestRedirectFilter
...
private String expandRedirectUri(HttpServletRequest request, ClientRegistration clientRegistration) {
int port = request.getServerPort();
if (("http".equals(request.getScheme()) && port == 80) || ("https".equals(request.getScheme()) && port == 443)) {
port = -1; // Removes the port in UriComponentsBuilder
}
...
request.getScheme()
does not provide the original scheme correctly, as it does not take into account the X-Forwarded-Proto
header
One possible fix is to use the following instead of request.getScheme()
HttpRequest httpRequest = new ServletServerHttpRequest(request);
UriComponents uriComponents = UriComponentsBuilder.fromHttpRequest(httpRequest).build();
String scheme = uriComponents.getScheme();
Comment From: rwinch
Thanks for the report!
Have you read through Proxy Server Configuration portion of the reference documentation? In short you need to ensure that your application server is properly configured for use with a proxy server.
Using X-Forwarded- headers directly within Spring Security is not preferred because it means that a malicious user can use header injection to attack an application. Using the headers directly also means that there is no way for users to opt out of this feature. Using an opt in model means that applications are required to take an explicit step to support X-Forwarded headers which means they are aware that they should prevent the headers from being sent past the first proxy server.
I'm going to go ahead and close this report, but if you have additional questions, comments, or concerns please feel free to reopen or create a new ticket.
Comment From: mragab
I read the Running Behind a Front-end Proxy Server Which states:
If the proxy adds conventional
X-Forwarded-For
andX-Forwarded-Proto
headers (most proxy servers do so), the absolute links should be rendered correctly, providedserver.use-forward-headers
is set totrue
in yourapplication.properties
.
I still find this to be a defect in Spring Security for the following reasons:
(a) As per the documentation, I did take an explicit step to enable support for X-Forwarded headers as included in the application.yml
snippet in the defect report, which follows the "an opt in model" criteria you described. Not wanting an "opt out mode" by default is one thing, and it not working in "opt in mode" when configured explicitly is another thing.
(b) The behavior of Spring Security is not consistent. The redirect URL contains: the public hostname, the public port number, but not the public scheme. It should be either all or nothing.
Comment From: rwinch
Spring Security uses whatever the HttpServletRequest reports, so if you do not see the correct scheme that means that there is something wrong in your configuration.
If the scheme is not being consumed, it sounds to me that your setup is incorrect. What X-Forwareded headers are you using? Are you using Tomcat? If so, what is the IP Address of your proxy? From the boot reference documentation:
Tomcat is also configured with a default regular expression that matches internal proxies that are to be trusted. By default, IP addresses in 10/8, 192.168/16, 169.254/16 and 127/8 are trusted. You can customize the valve’s configuration by adding an entry to application.properties, as shown in the following example:
server.tomcat.internal-proxies=192\\.168\\.\\d{1,3}\\.\\d{1,3}
You can also test to see if the ip address is the problem by setting
server.tomcat.internal-proxies=.*
Comment From: rwinch
FYI this is related https://github.com/spring-projects/spring-security/issues/3115
Comment From: mragab
Thanks for the tip, this changed the behavior
server.tomcat.internal-proxies=.*
With this configured:
• The redirect URL uses HTTPS
as expected.
• request.getScheme()
now correctly reports https
Thank you, now I understand where this is coming from, and I understand the reasoning behind it.
I am not sure I agree though - consider this as food for thought
Running behind a reverse proxy that performs SSL termination is a very common case:
• The current restriction of trusted proxy addresses only restricted the Scheme
. While the public address and port went through just fine.
• The current approach means users need to configure things differently based on which container they are using.
• Tracing the inconsistency I experienced even though it might be considered a Tomcat issue is inconvenient to say the least.
• The machinery is already there in Spring Web's UriComponentsBuilder
• The current approach is limited with regards to adding more related features. For example: limiting the acceptable forwarded hosts not based on the proxy address but based on a dynamic list of allowed forwarded hosts. It would be great if I can simply implement some interface, register a bean and I am done.
Thanks again and have a great day
Comment From: rwinch
@mragab Thanks for the feedback
Running behind a reverse proxy that performs SSL termination is a very common case:
Agreed.
The current restriction of trusted proxy addresses only restricted the Scheme. While the public address and port went through just fine.
This is a Tomcat specific implementation detail. Unfortunately, there are quite a few differences in how each of the container specific proxy support works. I have requested Spring Boot switch to ForwardedHeaderFilter to make this more consistent, but this was rejected. I'd encourage you to let the Boot team know about your problems. Perhaps if enough people complain, then we can find a change that makes everyone's lives easier and be in aligned with what the Boot team wants for the project.
The machinery is already there in Spring Web's UriComponentsBuilder
Spring Framework's preferred approach is to centralize the support using ForwardedHeaderFilter as well. See https://jira.spring.io/browse/SPR-16668
It's worth mentioning there are multiple standards (i.e. PROXY protocol) for supporting proxies and we would need the code to transparently support each mechanism (in lots of places throughout the code base). Instead, we can leverage a Filter that is applied as an aspect (very spring like) to opt into whatever proxy mode you are using.
Updating the HttpServletRequest makes a lot of sense (what ForwardedHeaderFilter does and what Tomcat's RemoteIpValve does). It is already a well defined interface, and depending on your proxy settings can report different values. Ultimately, the values on it are a fundamental contract within the Servlet APIs, so if it reports the wrong values you are going to get incorrect behavior.
For example: limiting the acceptable forwarded hosts not based on the proxy address but based on a dynamic list of allowed forwarded hosts. It would be great if I can simply implement some interface, register a bean and I am done.
Honestly, I don't think the restriction on the proxy address (or other headers) buys you much. If the user can spoof X-Forwarded-Proto, then it is quite likely that they can spoof the headers stating which proxy is being used. Tomcat is the only container that performs these checks so using a wildcard as I described puts you in the same scenario as if you were using Jetty or Undertow.
As far as a custom interface, this is a ticket you would need to log with the Boot team.
Comment From: mragab
Thanks for the insights, much appreciated. I will see how to possibly bring this forward with the Spring Boot team.
Comment From: songokudbz
Spring Boot: 2.1.3.RELEASE Spring Security OAuth2 Client: 2.1.3.RELEASE
Adding server.use-forward-headers=true and server.tomcat.internal-proxies=.* does not work :(
Comment From: rwinch
@songokudbz That doesn't provide very many details. Please create a ticket at https://github.com/spring-projects/spring-security-oauth/issues/new that provides a sample and details on how to reproduce.
Comment From: songokudbz
@rwinch I managed to make it work. I am deploying my Spring Boot 2 application as a WAR file in a Tomcat Docker Image. Inside I needed to configure server.xml with a "Valve" to support the X-Forwarded-Porto logic. Thanks
Comment From: yemanasyan
@rwinch I've tried both server.tomcat.internal-proxies=.* as well and it didn't work. I'm using Spring Boot 2.2.4.RELEASE with Spring Security OAuth2 Client. I'm using the Spring Boot default embedded Tomcat container. I've deployed my application in AWS where I have Application Load Balancer (which is proxy in this case). Like @mragab 's case the domain name is correct, but schema not.
Could you please help me?
Comment From: yemanasyan
I solved this issue by setting this property: server.forward-headers-strategy=FRAMEWORK
It's available starting from Spring Boot 2.2.X
Comment From: superzjn
For those who are NOT using Spring 2.2.x, you can add these to application.yml. Verified it works.
server:
use-forward-headers: true
tomcat:
use-relative-redirects: true
protocol-header: x-forwarded-proto
remote-ip-header: x-forwarded-for
Comment From: pawoua22
For those who are NOT using Spring 2.2.x, you can add these to application.yml. Verified it works.
server: use-forward-headers: true tomcat: use-relative-redirects: true protocol-header: x-forwarded-proto remote-ip-header: x-forwarded-for
greater or less than v 2.2.x?
Comment From: superzjn
For those who are NOT using Spring 2.2.x, you can add these to application.yml. Verified it works.
server: use-forward-headers: true tomcat: use-relative-redirects: true protocol-header: x-forwarded-proto remote-ip-header: x-forwarded-for
greater or less than v 2.2.x?
<2.2
Comment From: pawoua22
For those who are NOT using Spring 2.2.x, you can add these to application.yml. Verified it works.
server: use-forward-headers: true tomcat: use-relative-redirects: true protocol-header: x-forwarded-proto remote-ip-header: x-forwarded-for
greater or less than v 2.2.x?
<2.2
OK. I'm on 2.1.3 and it does not work.
Comment From: PatelVishalJ
For those who are NOT using Spring 2.2.x, you can add these to application.yml. Verified it works.
server: use-forward-headers: true tomcat: use-relative-redirects: true protocol-header: x-forwarded-proto remote-ip-header: x-forwarded-for
Works for me. I am no spring boot 2.1.4