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')
}
OAuth2 is an authorization framework that enables the application Web Security to access the resources from the client. To build an OAuth2 application, we need to focus on the Grant Type (Authorization code), Client ID and Client secret.
Conclusion. Spring Security and Spring Boot permit to quickly set up a complete OAuth2 authorization/authentication server in an almost declarative manner. The setup can be further shortened by configuring OAuth2 client's properties directly from application. properties/yml file, as explained in this tutorial.
Spring Security allows customizing HTTP security for features such as endpoints authorization or the authentication manager configuration by extending a WebSecurityConfigurerAdapter class. However, since recent versions, Spring deprecates this approach and encourages a component-based security configuration.
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;
}
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With