Summary
This was previously raised as #6347 but it's not clear to me what the remedy is. The recommendation at the time was to use ForwardedHeaderTransformer but what's unfortunate and confusing to me is that when ForwardedHeaderTransformer isn't used everything seems to almost work but fails deep in the guts of the OAuth response validation.
Actual Behavior
Request
When operating behind a reverse proxy that applies X-Forwarded-For and X-Forwarded-Proto, the redirectURI for the OAuth request is built in DefaultServerOAuth2AuthorizationRequestResolver#expandRedirectUri as
String baseUrl = UriComponentsBuilder.fromHttpRequest(new ServerHttpRequestDecorator(request))
.replacePath(request.getPath().contextPath().value())
.replaceQuery(null)
.build()
.toUriString();
// ... replace variables
In turn, UriComponentsBuilder.fromHttpRequest explicitly uses the Forwarded headers return fromUri(request.getURI()).adaptFromForwardedHeaders(request.getHeaders());
This means the request honors X-Forwarded-* by having the IDP redirect via the HTTPS protocol and proxy host.
Response
After the login is successful and the response is processed, the redirectUri of the response is handled in ServerOAuth2AuthorizationCodeAuthenticationTokenConverter#convertResponse as
String redirectUri = UriComponentsBuilder.fromUri(exchange.getRequest().getURI())
.query(null)
.build()
.toUriString();
which simply extracts the request URI from the "response" leg of the OAuth process. In the really common scenario that TLS offload is done at the proxy, this will get the right host but the wrong protocol.
End of the Day
OIDCAuthorizationCodeReactiveAuthenticationManager#authenticate fails validation at
if (!authorizationResponse.getRedirectUri().equals(authorizationRequest.getRedirectUri())) {
OAuth2Error oauth2Error = new OAuth2Error(INVALID_REDIRECT_URI_PARAMETER_ERROR_CODE);
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
}
Expected Behavior
The process for deriving the redirectURI during request and response process should be internally consistent in the use of X-Forwarded-For headers. The response in #6347 seems to indicate that use of ForwardedHeaderTransformer is required for this to work correctly. The biggest challenge in expectations to me is that the "correct" redirectURI (the one using HTTPS and proxy host) is set during the login (the browser sets the right redirectURI parameter and the IDP sends the expected post-login redirect). This means it feels like everything is working fine, and then the Spring Security login simply fails.
It feels like this should be "all or nothing". Either X-Forwarded-* headers are ignored, or they work as expected without further configuration.
Configuration
Standard implementation of OAuth client using webflux
Nginx as a reverse proxy with a configuration similar to
server {
listen 80;
server_name gw.test.local;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
ssl_certificate /path/to/a.cert
ssl_certificate_key /path/to/a.key
location / {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $http_host;
proxy_pass http://127.0.0.1:9090;
}
}
Version
Spring Boot 2.1.3 and associated versions of other Spring dependencies.
Comment From: rwinch
Thanks for the report.
We should update DefaultServerOAuth2AuthorizationRequestResolver to use fromHttpUrl rather than fromHttpRequest so that users opt into the behavior. I created gh-6952 to address this.
When usingForwardedHeaderTransformer does everything work?
Comment From: wtatum
Yes, just adding the ForwardedHeaderTransformer with default options caused it to work as expected. Thanks for the advice. I see gh-6952 is already closed as well, so thanks for the quick response there as well. Always appreciated.