title: Spring Boot OAuth Always redirecting to HTTP (IBM Cloud CF + Spring Boot 2)
body: <p>Using Spring Boot OAuth 2 on IBM Cloud CF Java Buildpack...</p>
  
  <p><a href="https://github.com/ericis/oauth-cf-https-issue" rel="noreferrer">https://github.com/ericis/oauth-cf-https-issue</a></p>
  
  <p>*I have tried every combination of the below.</p>
  
  <p><strong>With</strong> this configuration, the application is stuck in an endless loop of redirects, where the OAuth redirect strategy sends it to <code>http</code> and then this configuration sends it to <code>https</code>.</p>
  
  <p><code>http.requiresChannel().anyRequest().requiresSecure()</code></p>
  
  <p><strong>Without</strong> this configuration, users can login via http (undesired).</p>
  
  <p>Full config:</p>
  
  <pre><code>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());
  </code></pre>
  
  <p>I've also tried:</p>
  
  <ol>
  <li><p>Server configuration to use <code>https</code></p>
  
  <pre><code>server:
    useForwardHeaders: true
    tomcat:
      protocolHeader: x-forwarded-proto
  </code></pre></li>
  <li><p>Servlet filter</p>
  
  <pre><code>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()) &amp;&amp; 
          !LOCALHOST.equals(request.getRemoteHost()) &amp;&amp; 
          !LOCAL_ID.equals(request.getRemoteHost()) &amp;&amp; 
          (this.localIp != null &amp;&amp; !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
    }
  }
  </code></pre></li>
  </ol>
  
  <p><strong>Dependencies</strong></p>
  
  <pre><code>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')
  }
  </code></pre>