也来聊下Spring Boot开启SSL

这部分的内容在官方文档中有提到。链接在这儿:Configure SSL 。

网络上关于Spring Boot开启SSL访问的文章有很多。希望这篇文章能带来一点不一样的。

生成证书

首先,开启SSL访问得有证书,因为是本地访问,那么我们就使用jdk自带的keytool生成一个。

PS C:\Program Files\Java\jdk1.8.0_241\bin> .\keytool -genkey -keyalg RSA -keysize 2048 -keystore D:\keystore.jks
输入密钥库口令:
再次输入新口令:
您的名字与姓氏是什么?
  [Unknown]:  hanbin
您的组织单位名称是什么?
  [Unknown]:  home
您的组织名称是什么?
  [Unknown]:  hanbin-pc
您所在的城市或区域名称是什么?
  [Unknown]:  Xi'an
您所在的省/市/自治区名称是什么?
  [Unknown]:  Shaanxi
该单位的双字母国家/地区代码是什么?
  [Unknown]:  China
CN=hanbin, OU=home, O=hanbin-pc, L=Xi'an, ST=Shaanxi, C=China是否正确?
  [否]:  y

输入 <mykey> 的密钥口令
        (如果和密钥库口令相同, 按回车):

Warning:
JKS 密钥库使用专用格式。建议使用 "keytool -importkeystore -srckeystore D:\keystore.jks -destkeystore D:\keystore.jks -deststoretype pkcs12" 迁移到行业标准格式 PKCS12。

基础配置

本文示例项目使用 https://blog.hanbinit.com.cn/archives/146 中的demo application。将D盘下面生成的keystore.jks复制到项目的resources目录下。

按照官方文档中的示例在application.properties中配置如下:

server.port=8443
server.ssl.key-store=classpath:keystore.jks
server.ssl.key-store-password=123456
server.ssl.key-password=123456

启动的时候可以看到如图1所示日志。

图1

可以看到,系统启动的时候只启动了8443端口,且是https协议的。

访问https://localhost:8443/get_name,结果如图2所示。

图2

server.ssl.enabled默认就是true,配置了证书信息后,SSL就算是被激活了。这个时候server.port配置的就不是http的端口了。官方有如下说明:

  • Using configuration such as the preceding example means the application no longer supports a plain HTTP connector at port 8080. Spring Boot does not support the configuration of both an HTTP connector and an HTTPS connector through application.properties. If you want to have both, you need to configure one of them programmatically. We recommend using application.properties to configure HTTPS, as the HTTP connector is the easier of the two to configure programmatically.

如果只配置了上面的信息,就相当于开启了https,禁用了http。

支持https后http去哪儿了

如果还需要支持http,需要通过代码实现。官方文档在这儿说明了如何开启多个Connector。

  • You can add an org.apache.catalina.connector.Connector to the TomcatServletWebServerFactory, which can allow multiple connectors, including HTTP and HTTPS connectors, as shown in the following example:
@Bean
public ServletWebServerFactory servletContainer() {
    TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory();
    tomcat.addAdditionalTomcatConnectors(createSslConnector());
    return tomcat;
}

private Connector createSslConnector() {
    Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol");
    Http11NioProtocol protocol = (Http11NioProtocol) connector.getProtocolHandler();
    try {
        File keystore = new ClassPathResource("keystore").getFile();
        File truststore = new ClassPathResource("keystore").getFile();
        connector.setScheme("https");
        connector.setSecure(true);
        connector.setPort(8443);
        protocol.setSSLEnabled(true);
        protocol.setKeystoreFile(keystore.getAbsolutePath());
        protocol.setKeystorePass("changeit");
        protocol.setTruststoreFile(truststore.getAbsolutePath());
        protocol.setTruststorePass("changeit");
        protocol.setKeyAlias("apitester");
        return connector;
    }
    catch (IOException ex) {
        throw new IllegalStateException("can't access keystore: [" + keystore
                + "] or truststore: [" + truststore + "]", ex);
    }
}

上面是官方提供的示例代码,它开启了一个SSL Connector。我们要在默认开启https的基础上至此http访问。应该对上面的代码进行改造。在启动类中添加如下代码:

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

    @Bean
    public ServletWebServerFactory servletContainer() {
        TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory();
        tomcat.addAdditionalTomcatConnectors(createHttpConnector());
        return tomcat;
    }

    private Connector createHttpConnector() {
        Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol");
        connector.setScheme("http");
        connector.setSecure(false);
        connector.setPort(httpPort);
        return connector;
    }

在application.properties中添加配置项: server.http.port=8080

重新启动应用。可以看到日志和刚才不一样了,如图3。
图3
通过启动日志可以看到多出了8080的http支持。这个时候使用http://localhost:8080/get_name访问也能正常拿到结果了。

最后,说说http转发到https, 直接在代码中添加下面的代码是不会自动跳转的。

@Value("${server.port}")
private int httpsPort;
...
connector.setRedirectPort(httpsPort);
http的redirectPort只有在所使用的接口一定要使用https访问的时候才会跳转。所以还要再添加一些代码,指定默认Https Connector的拦截url。修改前面的servletContainer方法

@Bean
public ServletWebServerFactory 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(createHttpConnector());
    return tomcat;
}

接下来访问http://localhost:8080/get_name 会直接跳转到 https://localhost:8443/get_name

已有 2 条评论
  1. 大佬您好,请问可以换一个友情链接吗

    1. 可以。你加好了通知我。

添加新评论