devstory

Configurer Spring Boot pour rediriger HTTP vers HTTPS

1- Utiliser HTTP et HTTPS en même temps

Par défaut, l'application Spring Boot utilise le protocole HTTP ou HTTPS. La problématique est de savoir comment utiliser ces deux protocoles en même temps.
Tout d'abord, ouvrir le fichier application.properties et ajoutez la propriété server.http.port pour définir un port pour HTTP et la propriété server.port pour HTTPS.
Remarque: server.http.port est une propriété que vous définissez et non disponible dans Spring Boot.
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
Ensuite, créer une classe HttpHttpsConfigV1 et configurer Spring Boot pour utiliser les protocoles http et https en même temps.
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)

L'objectif principal de la configuration de Spring Boot pour qu'il soutienne les protocoles HTTP et HTTPS est d'autoriser l'application à recevoir des demandes entrantes via HTTP et de les rediriger automatiquement vers 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
Maintenant, créer une deuxième version, la classe HttpHttpsConfigV2 remplace celle de HttpHttpsConfigV1, qui autorise votre application Spring Boot à utiliser les deux protocoles HTTP et HTTPS en même temps. Cependant, toutes les demandes via le protocole HTTP seront automatiquement redirigées vers 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)

Dans certains cas, vous souhaitez que Spring Boot soutienne les protocoles HTTP et HTTPS et ne redirige automatiquement que HTTP vers HTTPS avec les chemins spécifiés. C'est absolument réalisable avec 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
 
La classe HttpHttpsConfigV3 autorise l'application Spring Boot à utiliser les deux protocoles HTTP et HTTPS simultanément:
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 est une couche intermédiaire entre l'utilisateur et le Controller. Il peut refuser, modifier ou rediriger les demandes de l'utilisateur. En se basant sur cette fonctionnalité d'Interceptor, vous pouvez l'utiliser pour détecter les requêtes HTTP et les rediriger vers 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;
    }

}
Enfin, il faut enregistrer la classe HttpHttpsInterceptor avec Spring Boot et spécifier les chemins qui doivent passer par cet Interceptor. Cela signifie qu'ils seront redirigés vers HTTPS et que les autres chemins utiliseront les protocoles HTTP et HTTPS.
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 ...

}