LOADING

加载过慢请开启缓存 浏览器默认开启

通过环境变量传递Tomcat https证书密码

2023/8/30

1. 起因

由于最近公司要求给tomcat配置https,本来以为只是简单的塞个证书和私钥就行了:

<Connector port="8443" protocol="org.apache.coyote.http11.Http11NioProtocol" SSLEnabled="true" >
    <SSLHostConfig protocols="TLSv1.2" sslProtocol="TLS">
        <Certificate certificateKeyFile="conf/server.key"
                    certificateFile="conf/server.crt"
                    type="RSA"/>
    </SSLHostConfig>
</Connector>

结果要求用jks证书文件,彳亍:

<Connector port="8443" protocol="org.apache.coyote.http11.Http11NioProtocol" 
    sslProtocol="TLS" 
    protocols="TLSv1.2"
    SSLEnabled="true"
    keystoreFile="conf/server.keystore"
    keystorePass="xxxx"/>

本以为万事大吉,结果甲方爸爸因为密码是直接写的明文,要求我们必须把密码穿成加密的🥲。

不过还好顺带也给了我份博客:tomcat安全配置之证书密码加密存储

2. tomcat配置https

2.1 编写实现类

其实这份博客已经说的很清楚了,只需要继承Http11NioProtocol这个类就可以了

博客里用的Http11Protocol,这个类已经被标记为@Deprecated的了,所以我们直接用它的父类,效果是一样的。

但是这个博客还是不太完整,这个类怎么打jar包?Http11Protocol从哪里来?打了的jar包丢在哪里?博客里都没有说明。

这里我自己研究了一下,首先创建一个maven项目,pom.xml添加依赖:

<dependency>
    <groupId>org.apache.tomcat</groupId>
    <artifactId>tomcat-coyote</artifactId>
    <version>8.5.87</version>
    <scope>provided</scope>
</dependency>

注意scope是provided。

之后就可以直接写代码了:

import org.apache.coyote.http11.Http11NioProtocol;

public class EncryptedHttp11Protocol extends Http11NioProtocol {

    @Override
    public void init() throws Exception {
        // 进行你自己的密码获取逻辑
        setKeystorePass("xxx");
        super.init();
    }

}

写完直接用maven打包:

mvn package -DskipTests

然后丢到tomcat目录的lib文件夹里就可以了。

最后配置server.xml:

 <Connector port="8443" protocol="xxxxxxx.EncryptedHttp11Protocol" SSLEnabled="true"
    keystoreFile="conf/server.keystore"
    sslProtocol="TLS">
</Connector>

注意protocol属性要改成你自己的实现类

2.2 设置环境变量

由于jks被加密,需要提供密码,因此推荐的方法是通过系统环境变量来提供(这里直接根据自己的机器设置即可)。
在java代码里这样获取系统环境变量:

String value = System.getenv(key);

2.3 设置命令行参数

也可以通过设置tomcat命令行参数来传输秘钥,
在tomcat的bin目录下创建setenv.bat(windows) / setenv.sh (linux)文件,并且配置相关参数即可。

windows:

set "JAVA_OPTS=-DsecretKey=xxxx -DsercretKey2=xxxx"

linux:

JAVA_OPTS="-DsecretKey=xxxx -DsercretKey2=xxxx"

之后在代码中这样获取:

String value = System.getProperty(key);

3. SpringBoot内嵌tomcat配置https

你说的对,但是我是SpringBoot内嵌tomcat!

公司的众多模块中,偏偏就是有一个SpringBoot项目,这玩意用的内嵌tomcat,上面的方法都用不了👎👎👎。

首先我们要知道SpringBoot项目怎么开启https:

server:
    ssl:
        enabled: true
        key-store: classpath:server.keystore
        key-store-password: xxxxxx

我们只需要找到一个方法在配置ssl前修改配置,提供密码即可。

你别说,还真被我找到了,在启动类添加下面的代码:

@Bean
public WebServerFactoryCustomizer<UndertowServletWebServerFactory> webServerFactoryCustomizer() {
    return factory -> {
        Ssl ssl = factory.getSsl();
        if (ssl == null || !ssl.isEnabled()) {
            return;
        }
        
        // ... 获取秘钥

        ssl.setKeyStorePassword("xxxx");
    };
}

甚至你在这里还可以引用刚才为tomcat准备的jar包,直接使用里面的秘钥获取逻辑,就不用再写一遍了√。

4. 其它:由证书和私钥生成jks文件

首先执行命令生成p12文件(输入后会要求输入密码,直接填上你要的密码就行):

openssl pkcs12 -export -in server.crt -inkey server.key -out server.p12

输完后执行:

keytool -importkeystore -v -srckeystore server.p12 -srcstoretype pkcs12 -srcstorepass 上面的密码 -destkeystore server.keystore -destoretype jks -deststorepass 上面的密码

这里如果jdk版本过低会报错:

keytool 错误: java.io.IOException: parseAlgParameters failed: ObjectIdentifier() -- data isn't an object ID (tag = 48)
java.io.IOException: parseAlgParameters failed: ObjectIdentifier() -- data isn't an object ID (tag = 48)
        at sun.security.pkcs12.PKCS12KeyStore.parseAlgParameters(PKCS12KeyStore.java:816)
        at sun.security.pkcs12.PKCS12KeyStore.engineLoad(PKCS12KeyStore.java:2018)
        at java.security.KeyStore.load(KeyStore.java:1445)
        at sun.security.tools.keytool.Main.loadSourceKeyStore(Main.java:2040)
        at sun.security.tools.keytool.Main.doCommands(Main.java:1067)
        at sun.security.tools.keytool.Main.run(Main.java:366)
        at sun.security.tools.keytool.Main.main(Main.java:359)
Caused by: java.io.IOException: ObjectIdentifier() -- data isn't an object ID (tag = 48)
        at sun.security.util.ObjectIdentifier.<init>(ObjectIdentifier.java:257)
        at sun.security.util.DerInputStream.getOID(DerInputStream.java:314)
        at com.sun.crypto.provider.PBES2Parameters.engineInit(PBES2Parameters.java:267)
        at java.security.AlgorithmParameters.init(AlgorithmParameters.java:293)
        at sun.security.pkcs12.PKCS12KeyStore.parseAlgParameters(PKCS12KeyStore.java:812)
        ... 6 more

这里用的jdk1.8.0_241导致的报错,换成jdk-11.0.18可以正常执行,其它版本暂未测试。