o7planning

Configure Spring Boot to redirect HTTP to HTTPS

View more Tutorials:

1- Use both HTTP and HTTPS

By default, Spring Boot application uses either HTTP or HTTPS protocol. The question is how to use these two protocols at the same time.
First, open the application.properties file and add server.http.port property to define a port for HTTP, and server.port property for HTTPS.
Note: server.http.port is a property you define and not available in SpringBoot.
application.properties (*)
# (User-defined Property)
# Port for HTTP and read by Spring Boot via @Value("${server.http.port:80}")
server.http.port=8080

# Port for HTTPS and read by Spring Boot via @Value("${server.port:443}")
server.port=8443


server.ssl.key-store=file:/home/tran/SSL/o7planning.org/o7planning_org.p12
server.ssl.key-store-password=P@ssword
server.ssl.key-alias=o7planning
Next, create a HttpHttpsConfigV1 class and configure Spring Boot to use both http and https protocols at the same time.
HttpHttpsConfigV1.java
import org.apache.catalina.connector.Connector;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.servlet.server.ServletWebServerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class HttpHttpsConfigV1 {

    // (User-defined Property)
    @Value("${server.http.port:80}")
    private int httpPort;

    @Bean
    public ServletWebServerFactory servletContainer() {
        Connector connector = new Connector(TomcatServletWebServerFactory.DEFAULT_PROTOCOL);
        connector.setPort(this.httpPort);

        TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory();
        tomcat.addAdditionalTomcatConnectors(connector);
        return tomcat;
    }
}

2- Redirect HTTP to HTTPS (Way 2)

The main purpose of configuring Spring Boot for it to support both HTTP and HTTPS protocols is to entitle the application to receive incoming requests via HTTP and automatically redirect them to HTTPS.
application.properties (*)
# (User-defined Property)
# Port for HTTP and read by Spring Boot via @Value("${server.http.port:80}")
server.http.port=8080

# Port for HTTPS and read by Spring Boot via @Value("${server.port:443}")
server.port=8443

server.ssl.key-store=file:/home/tran/SSL/o7planning.org/o7planning_org.p12
server.ssl.key-store-password=P@ssword
server.ssl.key-alias=o7planning
Now let's create a second version, HttpHttpsConfigV2 class replaces HttpHttpsConfigV1, which allows your Spring Boot application to use both HTTP and HTTPS protocols. However, all requests via HTTP protocol will be automatically redirected to HTTPS:
HttpHttpsConfigV2.java
import org.apache.catalina.Context;
import org.apache.catalina.connector.Connector;
import org.apache.tomcat.util.descriptor.web.SecurityCollection;
import org.apache.tomcat.util.descriptor.web.SecurityConstraint;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.servlet.server.ServletWebServerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class HttpHttpsConfigV2 {

    // IMPORTANT!!!
    // If this parameter is empty then do not redirect HTTP to HTTPS
    //
    // Defined in application.properties file
    @Value(value = "${server.ssl.key-store:}")
    private String sslKeyStore;

    // Defined in application.properties file
    // (User-defined Property)
    @Value(value = "${server.http.port:80}")
    private int httpPort;

    // Defined in application.properties file
    @Value("${server.port:443}")
    int httpsPort;

    @Bean
    public ServletWebServerFactory servletContainer() {
        boolean needRedirectToHttps = sslKeyStore != null && !sslKeyStore.isEmpty();

        TomcatServletWebServerFactory tomcat = null;

        if (!needRedirectToHttps) {
            tomcat = new TomcatServletWebServerFactory();
            return tomcat;
        }

        tomcat = new TomcatServletWebServerFactory() {

            @Override
            protected void postProcessContext(Context context) {
                SecurityConstraint securityConstraint = new SecurityConstraint();
                securityConstraint.setUserConstraint("CONFIDENTIAL");
                SecurityCollection collection = new SecurityCollection();
                collection.addPattern("/*");
                securityConstraint.addCollection(collection);
                context.addConstraint(securityConstraint);
            }
        };
        tomcat.addAdditionalTomcatConnectors(redirectConnector());
        return tomcat;
    }

    private Connector redirectConnector() {
        Connector connector = new Connector(TomcatServletWebServerFactory.DEFAULT_PROTOCOL);
        connector.setScheme("http");
        connector.setPort(httpPort);
        connector.setSecure(false);
        connector.setRedirectPort(httpsPort);
        return connector;
    }
}

3- Redirect HTTP to HTTPS (Way 3)

In some cases you want Spring Boot to support both HTTP and HTTPS protocols, and only automatically redirect from HTTP to HTTPS with the specified paths. It is absolutely doable with Interceptor.
application.properties (*)
# (User-defined Property)
# Port for HTTP and read by Spring Boot via @Value("${server.http.port:80}")
server.http.port=8080

# Port for HTTPS and read by Spring Boot via @Value("${server.port:443}")
server.port=8443

server.ssl.key-store=file:/home/tran/SSL/o7planning.org/o7planning_org.p12
server.ssl.key-store-password=P@ssword
server.ssl.key-alias=o7planning
 
The HttpHttpsConfigV3 class allows Spring Boot application to use both HTTP and HTTPS protocols simultaneously:
HttpHttpsConfigV3.java
import org.apache.catalina.connector.Connector;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.servlet.server.ServletWebServerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

// @see HttpHttpsInterceptor

@Configuration
public class HttpHttpsConfigV3 {

    @Value("${server.http.port:80}")
    private int httpPort;

    @Bean
    public ServletWebServerFactory servletContainer() {
        Connector connector = new Connector(TomcatServletWebServerFactory.DEFAULT_PROTOCOL);
        connector.setPort(this.httpPort);

        TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory();
        tomcat.addAdditionalTomcatConnectors(connector);
        return tomcat;
    }
}
Interceptor is a middle layer between the user and the Controller. It can deny, modify, or redirect the user's requests. Based on this feature of Interceptor, you can use it to detect HTTP Request(s) and redirect them to HTTPS.
HttpHttpsInterceptor.java
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

@Component
public class HttpHttpsInterceptor implements HandlerInterceptor {

    // Defined in application.properties file
    @Value(value = "${server.ssl.key-store:}")
    private String sslKeyStore;

    // Defined in application.properties file
    @Value(value = "${server.http.port:80}")
    private int httpPort;

    // Defined in application.properties file
    @Value("${server.port:443}")
    int httpsPort;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {

        // @return http or https
        String schema = request.getScheme();
        
        // System.out.println("Schema: " + schema);
        
        if("https".equals(schema)) {
            return true;
        }
        
        if(sslKeyStore == null || sslKeyStore.isEmpty()) {
            return true;
        }

        String serverName = request.getServerName();
        // System.out.println("Server Name: " + serverName);
        
        boolean isIP = this.isIP(serverName);
        // System.out.println("isIP: " + isIP);
        if (isIP) {
            // System.out.println("No Redirect isIP = "+ isIP);
            return true;
        }
        
        int requestedPort = request.getServerPort();
        // System.out.println("requestedPort: " + requestedPort);

        if (requestedPort == httpPort) { // This will still allow requests on :8080
            // System.out.println("Redirect to https");

            String queryString = request.getQueryString();
            if (queryString == null || queryString.isEmpty()) {
                if (httpsPort == 443) {
                    response.sendRedirect(
                            "https://" + request.getServerName() + request.getRequestURI());
                } else {
                    response.sendRedirect(
                            "https://" + request.getServerName() + ":" + httpsPort + request.getRequestURI());
                }
            } else {
                if (httpsPort == 443) {
                    response.sendRedirect(
                            "https://" + request.getServerName() + request.getRequestURI() + "?" + queryString);
                } else {
                    response.sendRedirect(
                            "https://" + request.getServerName()  + ":" + httpsPort + request.getRequestURI() + "?" + queryString);
                }
            }
            return false;
        }
        return true;
    }

    private boolean isIP(String remoteHost) {
        String s = remoteHost.replaceAll("\\.", "");
        // System.out.println("isIP? " + s);
        try {
            Long.parseLong(s);
        } catch (Exception e) {
            // e.printStackTrace();
            return false;
        }
        return true;
    }

}
Finally, you need to register the HttpHttpsInterceptor class with Spring Boot and specify which paths have to go through this Interceptor. That means they will be redirected to HTTPS, and the other paths will use both HTTP and HTTPS protocols.
WebMvcConfig.java
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
 

@Configuration
@EnableWebMvc
@Transactional
public class WebMvcConfig implements WebMvcConfigurer {
    
    @Autowired
    private HttpHttpsInterceptor httpHttpsInterceptor;  

    //
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        
        registry.addInterceptor(httpHttpsInterceptor);

    
        registry.addInterceptor(httpHttpsInterceptor)//
                .addPathPatterns("/path01", "path02/**");
    }
    
    // Other configs ...

}

View more Tutorials: