Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring Boot OAuth Always redirecting to HTTP (IBM Cloud CF + Spring Boot 2)

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:

  1. Server configuration to use https

    server:
      useForwardHeaders: true
      tomcat:
        protocolHeader: x-forwarded-proto
    
  2. 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')
}
like image 315
Eric Swanson Avatar asked Jul 18 '18 14:07

Eric Swanson


People also ask

What is OAuth 2.0 in spring boot?

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.

Does spring boot support OAuth2?

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.

Is Spring cloud security deprecated?

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.


2 Answers

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;
}
like image 126
Eric Swanson Avatar answered Nov 15 '22 04:11

Eric Swanson


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.

like image 25
James Justinic Avatar answered Nov 15 '22 06:11

James Justinic