openplanning

Cấu hình Spring Boot chuyển hướng HTTP sang HTTPS

Xem thêm các chuyên mục:

1- Sử dụng cả hai HTTP và HTTPS

Theo mặc định ứng dụng Spring Boot chỉ sử dụng một trong hai giao thức HTTP hoặc HTTPS. Câu hỏi đặt ra là làm thế nào để sử dụng đồng thời cả hai giao thức này.
Mở file application.properties và thêm vào property server.http.port để định nghĩa một cổng dành cho HTTP, và property server.port định nghĩa một cổng cho HTTPS.
Chú ý: server.http.port là một property do bạn định nghĩa, và nó không sẵn có trong 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
Tiếp theo, tạo một lớp HttpHttpsConfigV1 và cấu hình cho phép Spring Boot sử dụng đồng thời cả 2 giao thức httphttps.
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)

Mục đích chính của việc cấu hình Spring Boot cho nó hỗ trợ cả hai giao thức HTTPHTTPS là làm cho ứng dụng có thể nhận được các yêu cầu (request) gửi tới thông qua HTTP và tự động chuyển hướng (redirect) nó tới 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
Bây giờ chúng ta sẽ tạo ra một phiên bản thứ hai, lớp HttpHttpsConfigV2 thay thế cho lớp HttpHttpsConfigV1, nó cho phép ứng dụng Spring Boot của bạn có thể sử dụng cả hai giao thức HTTPHTTPS nhưng tất cả các request với giao thức HTTP sẽ tự động được chuyển hướng (redirect) tới 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)

Trong một số trường hợp bạn muốn Spring Boot hỗ trợ cả hai giao thức HTTPHTTPS, và chỉ tự động chuyển hướng từ HTTP đến HTTPS với các đường dẫn (path) được chỉ định, điều này có thể làm được với 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
 
Lớp HttpHttpsConfigV3 cho phép ứng dụng Spring Boot sử dụng đồng thời cả hai giao thức HTTPHTTPS:
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 là một lớp nằm giữa người dùng và Controller, nó có thể từ chối, sửa đổi, hoặc chuyển hướng request của người dùng. Dựa trên khả năng này của Interceptor bạn có thể sử dụng nó để phát hiện các HTTP Request và chuyển hướng chúng sang 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;
    }

}
Cuối cùng bạn cần đăng ký lớp HttpHttpsInterceptor với Spring Boot và chỉ rõ các đường dẫn (path) nào sẽ phải đi qua Interceptor này, nghĩa là chúng sẽ bị chuyển hướng tới HTTPS, các đường dẫn khác sẽ sử dụng cả hai giao thức HTTPHTTPS.
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 ...

}

Xem thêm các chuyên mục: