Springboot 自定义 HTTPS 的几种方式

2022-04-26 09:08:12 浏览数 (1)

虽然 Springboot 提供了相关参数用来启用 HTTPS 及相关配置,但在有些场景下需要我们做些定制化才能结合实际很好的启用 HTTPS 功能。比如,在我司有专门的秘钥系统来托管ssl证书及秘钥。此时,就不能简单的使用 server.ssl.xxx 来开启 HTTPS 了,而是需要先从秘钥系统下载 ssl 证书及秘钥,然后才能打开 springboot 的 HTTPS 功能。下面针对这种情况介绍下 springboot 中定制 https 的几种方式。

方式一:ServletWebServerFactory

代码语言:java复制
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(DemoHttpsProperties.class)
@ConditionalOnExpression("${demo.ssl.enabled}")
public class TomcatHttpsConfiguration {

    @Resource
    private DemoHttpsProperties demoHttpsProperties;

    @Bean
    public ServletWebServerFactory servletContainer() throws Exception {

        // 获取 key-store 密码, 下载SSL证书并进行校验
        String keyStorePassword = getKeyStorePassword();
        String keyStoreFile = downloadKeyStore();
        verifyCertificate(keyStoreFile, keyStorePassword);

        log.info("create TomcatServletWebServerFactory");
        TomcatServletWebServerFactory factory = 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);
            }
        };

        // 添加 SSL Connector
        factory.addConnectorCustomizers(connector -> {
            connector.setPort(demoHttpsProperties.getPort());
            connector.setSecure(true);
            connector.setScheme("https");
            Http11NioProtocol protocol = (Http11NioProtocol) connector.getProtocolHandler();
            protocol.setSSLEnabled(true);
            protocol.setKeystoreFile(keyStoreFile);
            protocol.setKeystorePass(keyStorePassword);
            protocol.setKeystoreType(demoHttpsProperties.getKeyStoreType());
            protocol.setKeyAlias(demoHttpsProperties.getKeyAlias());
            protocol.setSslProtocol("TLSv1.2");
            protocol.setSslEnabledProtocols("TLSv1.2 TLSv1.3");
            protocol.setClientAuth("false");
        });
        return factory;
    }

    private void verifyCertificate(String keyStoreFile, String password) throws Exception {
        KeyStore ks = KeyStore.getInstance(demoHttpsProperties.getKeyStoreType());
        FileInputStream fis = new FileInputStream(keyStoreFile);
        ks.load(fis, password.toCharArray());
        fis.close();
        X509Certificate x509Certificate = (X509Certificate) ks.getCertificate(demoHttpsProperties.getKeyAlias());
        x509Certificate.checkValidity();
    }

    private String downloadKeyStore(KeyApi keyApi){
        ...
    }

    private String getKeyStorePassword() {
        ...
    }
}

这种方式下,如果想做成 autoconfig 需要禁止 springboot web 的自动配置,否则启动时会出现多个 tomcat 的错误,可用以下两种方式解决:

  • @SpringBootApplication(exclude = ServletWebServerFactoryAutoConfiguration.class)
  • 或者配置文件中添加:spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration

方式二:WebServerFactoryCustomizer

代码语言:java复制
public class TomcatWebServerCustomizer implements WebServerFactoryCustomizer<TomcatServletWebServerFactory> {

    public TomcatWebServerCustomizer(DemoHttpsProperties props) throws Exception {

        // 获取 key-store 密码, 下载SSL证书并进行校验
        this.keyStoreType = props.getKeyStoreType();
        this.keyAlias = props.getKeyAlias();
        this.keyStorePassword = getKeyStorePassword();
        this.keyStoreFile = downloadKeyStore();
        verifyCertificate(this.keyStoreFile, this.keyStoreType, this.keyStorePassword);
    }

    @Override
    public void customize(TomcatServletWebServerFactory factory) {

        factory.addConnectorCustomizers(connector -> {
            connector.setSecure(true);
            connector.setScheme("https");
            Http11NioProtocol protocol = (Http11NioProtocol) connector.getProtocolHandler();
            protocol.setSSLEnabled(true);
            protocol.setKeystoreFile(keyStoreFile);
            protocol.setKeystorePass(keyStorePassword);
            protocol.setKeystoreType(keyStoreType);
            protocol.setKeyAlias(keyAlias);
            protocol.setSslProtocol("TLSv1.2");
            protocol.setSslEnabledProtocols("TLSv1.2 TLSv1.3");
            protocol.setClientAuth("false");
        });
    }

    private void verifyCertificate(String keyStoreFile, String keyStoreType, String keyStorePwd) throws Exception {
        KeyStore ks = KeyStore.getInstance(keyStoreType);
        FileInputStream fis = new FileInputStream(keyStoreFile);
        ks.load(fis, keyStorePwd.toCharArray());
        fis.close();
        X509Certificate x509Certificate = (X509Certificate) ks.getCertificate(keyAlias);
        x509Certificate.checkValidity();
    }

    private String downloadKeyStore() {
        ...
    }

    private String getKeyStorePassword() {
        ...
    }
}

方式三:重定向

网上见到最多的就是这种方式,同时启用 http 和 https 端口,将 http 请求重定向到 https,具体如下:

代码语言:java复制
@Configuration
public class HttpsConfig {
    @Bean
    public TomcatServletWebServerFactory servletContainer() {

        TomcatServletWebServerFactory 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(initiateHttpConnector());
        return tomcat;
    }

    private Connector initiateHttpConnector() {
        Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol");
        connector.setScheme("http");
        connector.setPort(8080);
        connector.setSecure(true);
        connector.setRedirectPort(443);
        return connector;
    }
}

0 人点赞