Reputation: 930
Using Spring Boot OAuth 2 on IBM Cloud CF Java Buildpack...
https://github.com/ericis/oauth-cf-https-issue
*I have tried every combination of the below.
With this configuration, the application is stuck in an endless loop of redirects, where the OAuth redirect strategy sends it to http
and then this configuration sends it to https
.
http.requiresChannel().anyRequest().requiresSecure()
Without this configuration, users can login via http (undesired).
Full config:
http.
requiresChannel().anyRequest().requiresSecure().
authorizeRequests().
// allow access to...
antMatchers("favicon.ico", "/login", "/loginFailure", "/oauth2/authorization/ghe")
.permitAll().anyRequest().authenticated().and().oauth2Login().
// Codify "spring.security.oauth2.client.registration/.provider"
clientRegistrationRepository(this.clientRegistrationRepository()).
// setup OAuth2 client service to use clientRegistrationRepository
authorizedClientService(this.authorizedClientService()).
successHandler(this.successHandler()).
// customize login pages
loginPage("/login").failureUrl("/loginFailure").
userInfoEndpoint().
// customize the principal
userService(this.userService());
I've also tried:
Server configuration to use https
server:
useForwardHeaders: true
tomcat:
protocolHeader: x-forwarded-proto
Servlet filter
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class HttpToHttpsFilter implements Filter {
private static final Logger log = LoggerFactory.getLogger(HttpToHttpsFilter.class);
private static final String HTTP = "http";
private static final String SCHEME_HTTP = "http://";
private static final String SCHEME_HTTPS = "https://";
private static final String LOCAL_ID = "0:0:0:0:0:0:0:1";
private static final String LOCALHOST = "localhost";
@Value("${local.ip}")
private String localIp;
public HttpToHttpsFilter() {
// Sonar
}
@Override
public void doFilter(final ServletRequest req, final ServletResponse res, final FilterChain chain)
throws IOException, ServletException {
final HttpServletRequest request = (HttpServletRequest) req;
final HttpServletResponse response = (HttpServletResponse) res;
// http, not localhost, not localhost ipv6, not local IP
if (HTTP.equals(request.getScheme()) &&
!LOCALHOST.equals(request.getRemoteHost()) &&
!LOCAL_ID.equals(request.getRemoteHost()) &&
(this.localIp != null && !this.localIp.equals(request.getRemoteHost()))) {
final String query = request.getQueryString();
String oldLocation = request.getRequestURL().toString();
if (query != null) {
oldLocation += "?" + query;
}
final String newLocation = oldLocation.replaceFirst(SCHEME_HTTP, SCHEME_HTTPS);
try {
log.info("HTTP redirect from {} to {} ", oldLocation, newLocation);
response.sendRedirect(newLocation);
} catch (IOException e) {
log.error("Cannot redirect to {} {} ", newLocation, e);
}
} else {
chain.doFilter(req, res);
}
}
@Override
public void destroy() {
// Sonar
}
@Override
public void init(FilterConfig arg0) throws ServletException {
// Sonar
}
}
Dependencies
dependencies {
//
// BASICS
// health and monitoring
// compile('org.springframework.boot:spring-boot-starter-actuator')
// security
compile('org.springframework.boot:spring-boot-starter-security')
// configuration
compile('org.springframework.boot:spring-boot-configuration-processor')
//
// WEB
// web
compile('org.springframework.boot:spring-boot-starter-web')
// thymeleaf view render
compile('org.springframework.boot:spring-boot-starter-thymeleaf')
// thymeleaf security extras
compile('org.thymeleaf.extras:thymeleaf-extras-springsecurity4')
//
// OAUTH
// OAuth client
compile('org.springframework.security:spring-security-oauth2-client')
// OAuth lib
compile('org.springframework.security:spring-security-oauth2-jose')
// OAuth config
compile('org.springframework.security.oauth.boot:spring-security-oauth2-autoconfigure:2.0.0.RELEASE')
//
// CLOUD
// cloud connectors (e.g. vcaps)
compile('org.springframework.boot:spring-boot-starter-cloud-connectors')
//
// TOOLS
runtime('org.springframework.boot:spring-boot-devtools')
//
// TEST
// test
testCompile('org.springframework.boot:spring-boot-starter-test')
// security test
testCompile('org.springframework.security:spring-security-test')
}
Upvotes: 6
Views: 5272
Reputation: 802
Because of using Spring 5, decided to go with next solution (security configuration):
http
.addFilterBefore(new ForwardedHeaderFilter(), OAuth2AuthorizationRequestRedirectFilter.class)
x-forwarded-* header parameters handled correct now.
Upvotes: 1
Reputation: 174
The application is likely always stuck in an infinite loop in this environment with the current settings, even without the OAuth behavior involved.
While you can tell the server to use forward headers,
server:
useForwardHeaders: true
Tomcat will not trust the x-forwarded-*
headers from all sources. Certain IP addresses are considered to be internal by default (RemoteIpValve#internalProxies).
However, in the environment you're using, the reported proxy IP address is likely not in this range. You can configure all IP addresses to be allowed with the following:
server:
tomcat:
internal-proxies: .*
This allows all proxies, but may be acceptable for your needs if there is no way to access the application directly.
Upvotes: 2
Reputation: 930
This was resolved. Details on the issue related to this can be found at: https://github.com/spring-projects/spring-security/issues/5535#issuecomment-407413944
Example project that is now working: https://github.com/ericis/oauth-cf-https-issue
The short answer:
The application needs to be explicitly configured to be aware of the proxy headers. I had tried configuration, but ultimately had to use an instance of the ForwardedHeaderFilter
class that was added fairly recently to Spring.
@Bean
FilterRegistrationBean<ForwardedHeaderFilter> forwardedHeaderFilter() {
final FilterRegistrationBean<ForwardedHeaderFilter> filterRegistrationBean = new FilterRegistrationBean<ForwardedHeaderFilter>();
filterRegistrationBean.setFilter(new ForwardedHeaderFilter());
filterRegistrationBean.setOrder(Ordered.HIGHEST_PRECEDENCE);
return filterRegistrationBean;
}
Upvotes: 19