SpringBoot结合keytool配置ssl双向认证通信

发布于:2022-11-10 ⋅ 阅读:(1345) ⋅ 点赞:(1)


【关键词】:keytool、SpringBoot、restTemplate、ssl、双向认证、https、keystore、jks

一、需求

各对等机构各自部署一套服务平台(称作节点peer),要求在各机构平台之间内部通信使用ssl加密。支持自签名方式或ca认证方式。

二、环境

win10、jdk1.8.0_40。

三、技术储备

  • jdk自带的keytool命令;
  • openssl命令;多种证书类型和转换命令;
  • server.ssl服务端配置;
  • restTemplate客户端集成ssl配置;
  • 支持https的同时也支持http,便于调测;
  • 打开ssl握手日志-Djavax.net.debug=ssl:handshake,便于分析ssl。

四、项目实现和测试

4.0、大体思路

  1. 首先准备一个简单的SpringBoot项目,peer1写个/api/doSomething接口,内部再用restTemplate调用peer2的https接口https://peer2/interact/reply/hello
  2. keytool为peer1和peer2分别生成秘钥库以及信任秘钥库,并配置进ssl服务端和客户端;
  3. 同时开启http端口,便于postman直接调用peer1的http接口http://peer1/api/doSomething
  4. 测试自签名的peer1和peer2互认证书,以及同一CA签发的peer1和peer2证书,是否可以调通。

4.1、项目准备

完整项目直接点这里:
https://gitee.com/songzehao/ssl-peer

  • http外部接口:com.szh.peer.ctrl.SystemCtrl.doSomething()
  • https内部通信接口:com.szh.peer.ctrl.InteractCtrl.replyHello()

4.2、keytool生成证书并配置

4.2.1、自签名peer1/peer2

peer1和peer2两个机构各自生成自己秘钥库,并将对方配置在自己的信任秘钥库中,从而达到互认互信加密通信。信任秘钥库可以合并在秘钥库,也可以是独立于秘钥库的另一个文件中。

4.2.1.1、信任密钥库合并在密钥库

## 信任密钥库合并在密钥库

一、peer1生成密钥库以及导出公钥证书
1、生成peer1的密钥库peer1.jks
keytool -genkeypair -alias peer1 -keystore peer1.jks -storepass passwd1 -dname CN=peer1,OU=peer1,O=peer1,L=peer1,C=CN
2、查看密钥库详情
keytool -list -keystore peer1.jks -storepass passwd1 -v
3、peer1导出公钥证书
keytool -export -alias peer1 -file peer1.cer -keystore peer1.jks -storepass passwd1

二、peer2生成密钥库以及导出公钥证书
1、生成peer2的密钥库peer2.jks
keytool -genkeypair -alias peer2 -keystore peer2.jks -storepass passwd2 -dname CN=peer2,OU=peer2,O=peer2,L=peer2,C=CN
2、查看密钥库详情
keytool -list -keystore peer2.jks -storepass passwd2 -v
3、peer2导出公钥证书
keytool -export -alias peer2 -file peer2.cer -keystore peer2.jks -storepass passwd2

三、peer1/peer2互相导入对方密钥库,建立信任
1、peer2的证书导入peer1密钥库
keytool -import -alias peer2 -file peer2.cer -keystore peer1.jks -storepass passwd1
2、peer1的证书导入peer2密钥库
keytool -import -alias peer1 -file peer1.cer -keystore peer2.jks -storepass passwd2

四、检验peer1/peer2是否具有自己的privateKey和对方的cert
1、检验peer1是否具有自己的privateKey和peer2的cert
keytool -list -keystore peer1.jks -storepass passwd1
2、检验peer2是否具有自己的privateKey和peer1的cert
keytool -list -keystore peer2.jks -storepass passwd2

五、转换JKS格式为P12(便于在postman单向测试证书)
1、转换peer2.jks->peer2.p12
keytool -importkeystore -srckeystore peer2.jks -destkeystore peer2.p12 -srcstoretype JKS -deststoretype PKCS12 -srcstorepass passwd2 -deststorepass passwd2 -srckeypass passwd2 -destkeypass passwd2 -srcalias peer2 -destalias peer2 -noprompt
2、转换peer1.jks->peer1.p12
keytool -importkeystore -srckeystore peer1.jks -destkeystore peer1.p12 -srcstoretype JKS -deststoretype PKCS12 -srcstorepass passwd1 -deststorepass passwd1 -srckeypass passwd1 -destkeypass passwd1 -srcalias peer1 -destalias peer1 -noprompt

4.2.1.2、信任密钥库独立于密钥库

## 信任密钥库独立于密钥库

一、peer1生成密钥库以及导出公钥证书
1、生成peer1的密钥库peer1.jks
keytool -genkeypair -alias peer1 -keystore peer1.jks -storepass passwd1 -dname CN=peer1,OU=peer1,O=peer1,L=peer1,C=CN
2、查看密钥库详情
keytool -list -keystore peer1.jks -storepass passwd1 -v
3、peer1导出公钥证书
keytool -export -alias peer1 -file peer1.cer -keystore peer1.jks -storepass passwd1

二、peer2生成密钥库以及导出公钥证书
1、生成peer2的密钥库peer2.jks
keytool -genkeypair -alias peer2 -keystore peer2.jks -storepass passwd2 -dname CN=peer2,OU=peer2,O=peer2,L=peer2,C=CN
2、查看密钥库详情
keytool -list -keystore peer2.jks -storepass passwd2 -v
3、peer2导出公钥证书
keytool -export -alias peer2 -file peer2.cer -keystore peer2.jks -storepass passwd2

三、peer1/peer2互相导入对方信任密钥库
1、peer2的证书导入peer1信任密钥库
keytool -import -alias peer1Trust -file peer2.cer -keystore peer1Trust.jks -storepass passwd1
2、peer1的证书导入peer2信任密钥库
keytool -import -alias peer2Trust -file peer1.cer -keystore peer2Trust.jks -storepass passwd2

四、检验peer1/peer2是否具有对方的cert
1、检验peer1的信任密钥库是否有peer2的cert
keytool -list -keystore peer1Trust.jks -storepass passwd1
2、检验peer2的信任密钥库是否有peer1的cert
keytool -list -keystore peer2Trust.jks -storepass passwd2

4.2.2、CA签发peer1/peer2

peer1和peer2两个机构各自生成自己秘钥库jks,再生成csr证书请求文件,再通过同一个CA签发生成cer证书。再将ca和自身证书配置在自己的秘钥库和信任秘钥库中,从而达到互认互信加密通信。信任秘钥库可以合并在秘钥库,也可以是独立于秘钥库的另一个文件中。

CA的方式更灵活一些,不像自签名的方式,一旦加入了peer3,那么peer1和peer2都要再把peer3的证书导入到自己信任秘钥库中,一般需要重启服务;而CA方式避免了这个问题。

4.2.2.1、信任密钥库合并在密钥库

## 信任密钥库合并在密钥库

一、CA
1、生成CA的密钥库ca.jks
keytool -genkeypair -alias ca -keystore ca.jks -storepass passwdca -dname CN=ca,OU=ca,O=ca,L=ca,C=CN
2、CA先要生成自己的自签名证书文件
keytool -exportcert -alias ca -keystore ca.jks -storepass passwdca -file ca.cer
3、CA证书转为pem(便于postman导入CA.pem证书),这一步不用执行
openssl x509 -in ca.cer -inform DER -out ca.pem -outform PEM

二、peer1
1、生成peer1的密钥库peer1.jks
keytool -genkeypair -alias peer1 -keystore peer1.jks -storepass passwd1 -dname CN=peer1,OU=peer1,O=peer1,L=peer1,C=CN
2、查看密钥库详情
keytool -list -keystore peer1.jks -storepass passwd1 -v
3、peer1生成证书签名请求文件
keytool -certreq -alias peer1 -keystore peer1.jks -storepass passwd1 -file peer1.csr
4、委托CA对证书请求文件进行签发
keytool -gencert -alias ca -keystore ca.jks -storepass passwdca -infile peer1.csr -outfile peer1.cer
5、peer1需要将ca.cer导入到自己的密钥库中
keytool -importcert -alias ca -keystore peer1.jks -storepass passwd1 -file ca.cer
6、peer1需要将cer证书导入到自己的密钥库中
keytool -importcert -alias peer1 -keystore peer1.jks -storepass passwd1 -file peer1.cer
7、再次查看peer1密钥库证书链长度为2
keytool -list -keystore peer1.jks -storepass passwd1 -v

三、peer2
1、生成peer2的密钥库peer2.jks
keytool -genkeypair -alias peer2 -keystore peer2.jks -storepass passwd2 -dname CN=peer2,OU=peer2,O=peer2,L=peer2,C=CN
2、查看密钥库详情
keytool -list -keystore peer2.jks -storepass passwd2 -v
3、peer2生成证书签名请求文件
keytool -certreq -alias peer2 -keystore peer2.jks -storepass passwd2 -file peer2.csr
4、委托CA对证书请求文件进行签发
keytool -gencert -alias ca -keystore ca.jks -storepass passwdca -infile peer2.csr -outfile peer2.cer
5、peer2需要将ca.cer导入到自己的密钥库中
keytool -importcert -alias ca -keystore peer2.jks -storepass passwd2 -file ca.cer
6、peer2需要将cer证书导入到自己的密钥库中
keytool -importcert -alias peer2 -keystore peer2.jks -storepass passwd2 -file peer2.cer
7、再次查看peer2密钥库证书链长度为2
keytool -list -keystore peer2.jks -storepass passwd2 -v

4.2.2.2、信任密钥库独立于密钥库

## 信任密钥库独立于密钥库(额外操作第7、8步)

一、CA
1、生成CA的密钥库ca.jks
keytool -genkeypair -alias ca -keystore ca.jks -storepass passwdca -dname CN=ca,OU=ca,O=ca,L=ca,C=CN
2、CA先要生成自己的自签名证书文件
keytool -exportcert -alias ca -keystore ca.jks -storepass passwdca -file ca.cer

二、peer1
1、生成peer1的密钥库peer1.jks
keytool -genkeypair -alias peer1 -keystore peer1.jks -storepass passwd1 -dname CN=peer1,OU=peer1,O=peer1,L=peer1,C=CN
2、查看密钥库详情
keytool -list -keystore peer1.jks -storepass passwd1 -v
3、peer1生成证书签名请求文件
keytool -certreq -alias peer1 -keystore peer1.jks -storepass passwd1 -file peer1.csr
4、委托CA对证书请求文件进行签发
keytool -gencert -alias ca -keystore ca.jks -storepass passwdca -infile peer1.csr -outfile peer1.cer
5、peer1需要将ca.cer导入到自己的密钥库中
keytool -importcert -alias ca -keystore peer1.jks -storepass passwd1 -file ca.cer
6、peer1需要将cer证书导入到自己的密钥库中
keytool -importcert -alias peer1 -keystore peer1.jks -storepass passwd1 -file peer1.cer
7、peer1需要将ca.cer导入到自己的信任密钥库中
keytool -importcert -alias ca -keystore peer1Trust.jks -storepass passwd1 -file ca.cer
8、peer1需要将cer证书导入到自己的信任密钥库中
keytool -importcert -alias peer1 -keystore peer1Trust.jks -storepass passwd1 -file peer1.cer
9、查看peer1信任密钥条目为2
keytool -list -keystore peer1Trust.jks -storepass passwd1 -v

三、peer2
1、生成peer2的密钥库peer2.jks
keytool -genkeypair -alias peer2 -keystore peer2.jks -storepass passwd2 -dname CN=peer2,OU=peer2,O=peer2,L=peer2,C=CN
2、查看密钥库详情
keytool -list -keystore peer2.jks -storepass passwd2 -v
3、peer2生成证书签名请求文件
keytool -certreq -alias peer2 -keystore peer2.jks -storepass passwd2 -file peer2.csr
4、委托CA对证书请求文件进行签发
keytool -gencert -alias ca -keystore ca.jks -storepass passwdca -infile peer2.csr -outfile peer2.cer
5、peer2需要将ca.cer导入到自己的密钥库中
keytool -importcert -alias ca -keystore peer2.jks -storepass passwd2 -file ca.cer
6、peer2需要将cer证书导入到自己的密钥库中
keytool -importcert -alias peer2 -keystore peer2.jks -storepass passwd2 -file peer2.cer
7、peer2需要将ca.cer导入到自己的信任密钥库中
keytool -importcert -alias ca -keystore peer2Trust.jks -storepass passwd2 -file ca.cer
8、peer2需要将cer证书导入到自己的信任密钥库中
keytool -importcert -alias peer2 -keystore peer2Trust.jks -storepass passwd2 -file peer2.cer
9、查看peer2信任密钥条目为2
keytool -list -keystore peer2Trust.jks -storepass passwd2 -v

4.2.3、ssl服务端配置

## ssl start
server.ssl.pure-key-store=peer-ca/peer1.jks
server.ssl.pure-trust-store=peer-ca/peer1Trust.jks
# 私钥库
server.ssl.enabled=true
server.ssl.key-store-type=JKS
server.ssl.key-store=classpath:${server.ssl.pure-key-store}
server.ssl.key-store-password=passwd1
server.ssl.key-alias=peer1
# 受信任密钥库
server.ssl.trust-store=classpath:${server.ssl.pure-trust-store}
server.ssl.trust-store-password=passwd1
server.ssl.trust-store-provider=SUN
server.ssl.trust-store-type=JKS
server.ssl.client-auth=need
## ssl end

4.2.4、ssl客户端配置

客户端restTemplate底层实现选用httpClient,

<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
    <version>4.5.5</version>
</dependency>

构造httpClient的sslContext,

@Configuration
@Slf4j
public class RestTemplateConfig {

    @Value("${server.ssl.pure-key-store}")
    public String keyStore;
    @Value("${server.ssl.key-store-password}")
    public String keyStorePassword;
    @Value("${server.ssl.pure-trust-store}")
    public String trustStore;
    @Value("${server.ssl.trust-store-password}")
    public String trustStorePassword;

    @Bean()
    public RestTemplate restTemplate(ClientHttpRequestFactory httpComponentsClientHttpRequestFactory) {
        RestTemplate restTemplate = new RestTemplate(httpComponentsClientHttpRequestFactory);
        restTemplate.getMessageConverters().set(1, new StringHttpMessageConverter(StandardCharsets.UTF_8));
        log.info("loading restTemplate");
        return restTemplate;
    }

    @Bean("httpComponentsClientHttpRequestFactory")
    public ClientHttpRequestFactory httpComponentsClientHttpRequestFactory() throws IOException, UnrecoverableKeyException,
            CertificateException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException {
        SSLContext sslContext = SSLContextBuilder
                .create()
                // 作为client,load自己密钥库;理论上当server的server.ssl.client-auth=need时,通过它可以获取client的公钥证书传递给server来验证
                .loadKeyMaterial(new ClassPathResource(keyStore).getURL(),
                        trustStorePassword.toCharArray(), keyStorePassword.toCharArray())
                // 作为client,load自己的信任库;在请求server之前,client先通过它判断server是否受信
                .loadTrustMaterial(new ClassPathResource(trustStore).getURL(), trustStorePassword.toCharArray())
                .build();
        HttpClient client = HttpClients.custom()
                .setSSLContext(sslContext)
                .setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE) // 不需要主机验证
                .build();
        HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(client);
        return requestFactory;
    }
}

4.3、同时开启http

自定义一个http端口,

server.http-port=8288

配置web容器也开启http端口,

@Component
public class TomcatServerCustomer implements WebServerFactoryCustomizer<TomcatServletWebServerFactory> {
    @Value("${server.http-port}")
    public Integer httpPort;

    @Override
    public void customize(TomcatServletWebServerFactory factory) {
        Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol");
        connector.setScheme("http");
        connector.setPort(httpPort);
        factory.addAdditionalTomcatConnectors(connector);
    }
}

服务启动后会打印https和http端口信息,

Tomcat started on port(s): 28288 (https) 8288 (http) with context path ''

4.4、测试

http调用peer,触发内部https调用另一peer,成功返回,

curl http://127.0.0.1:8688/api/doSomething
hello, peer2
curl http://127.0.0.1:8288/api/doSomething
hello, peer1

除了上面的keytool命令,在postman中测peer2->peer1的单向通信,可以把peer2的jks格式秘钥库转换为p12导入,

keytool -importkeystore -srckeystore peer2.jks -destkeystore peer2.p12 -srcstoretype JKS -deststoretype PKCS12 -srcstorepass passwd2 -deststorepass passwd2 -srckeypass passwd2 -destkeypass passwd2 -srcalias peer2 -destalias peer2 -noprompt

除此之外,还有其他命令,

查看帮助命令:

keytool -command_name -help
keytool -genkeypair -help

查看密钥库里面的信息:

keytool -list -keystore peer1.jks -v

查看指定证书文件的信息:

keytool -printcert -file peer1.cer -v

cer证书转为pem:

openssl x509 -in ca.cer -inform DER -out ca.pem -outform PEM

五、其他

5.1、多级CA

除了一级根CA,还需要多中间一级CA,再用中间CA签发peer的证书,

一、根CA
1、rootCa 生成根CA密钥库
keytool -genkeypair -alias rootCa -keystore rootCa.jks -storepass passwdca -dname CN=ca,OU=ca,O=ca,L=ca,C=CN
2、导出 rootCa 的根证书
keytool -export -alias rootCa -keystore rootCa.jks -storepass passwdca -file rootCa.cer

二、中间二级CA
1、midCa 生成中间二级CA密钥库
keytool -genkeypair -alias midCa -keystore midCa.jks -storepass passwdmidca -dname CN=midCa,OU=midCa,O=midCa,L=midCa,C=CN
2、给midCa生成csr
keytool -certreq -alias midCa -keystore midCa.jks -storepass passwdmidca -file midCa.csr
3、委托根ca签发二级CA的cer证书
keytool -gencert -alias rootCa -keystore rootCa.jks -storepass passwdca -infile midCa.csr -outfile midCa.cer
4、导入根CA的cer证书到midCa密钥库(这步必需,否则报错“keytool 错误: java.lang.Exception: 无法从回复中建立链”)
keytool -importcert -alias rootCa -keystore midCa.jks -storepass passwdmidca -file rootCa.cer
5、导入二级CA的cer证书到midCa密钥库
keytool -importcert -alias midCa -keystore midCa.jks -storepass passwdmidca -file midCa.cer

三、peer1
1、peer1 生成密钥库
keytool -genkeypair -alias peer1 -dname CN=peer1,OU=peer1,O=peer1,L=peer1,C=CN -storepass passwd1 -keystore peer1.jks
2、将 rootCa.cer 导入peer1密钥库
keytool -import -alias rootCa -keystore peer1.jks -storepass passwd1 -file rootCa.cer -noprompt -trustcacerts
3、给peer1生成csr
keytool -certreq -alias peer1 -keystore peer1.jks -storepass passwd1 -file peer1.csr
4、委托二级midCa签发cer
keytool -gencert -alias midCa -keystore midCa.jks -storepass passwdmidca -infile peer1.csr -outfile peer1.cer
5、导入peer1的cer证书到密钥库
keytool -importcert -alias peer1 -keystore peer1.jks -storepass passwd1 -noprompt -trustcacerts -file peer1.cer
6、您可以选择从密钥库中删除 rootCa 根证书(我们只需要它来链接)
keytool -delete -alias rootCa -keystore peer1.jks -storepass passwd1
keytool -list -v -keystore peer1.jks -storepass passwd1

四、peer2
1、peer2 生成密钥库
keytool -genkeypair -alias peer2 -dname CN=peer2,OU=peer2,O=peer2,L=peer2,C=CN -storepass passwd2 -keystore peer2.jks
2、将 rootCa.cer 导入peer2密钥库
keytool -import -alias rootCa -keystore peer2.jks -storepass passwd2 -file rootCa.cer -noprompt -trustcacerts
3、给peer2生成csr
keytool -certreq -alias peer2 -keystore peer2.jks -storepass passwd2 -file peer2.csr
4、委托二级midCa签发cer
keytool -gencert -alias midCa -keystore midCa.jks -storepass passwdmidca -infile peer2.csr -outfile peer2.cer
5、导入peer2的cer证书到密钥库
keytool -importcert -alias peer2 -keystore peer2.jks -storepass passwd2 -noprompt -trustcacerts -file peer2.cer
6、您可以选择从密钥库中删除 rootCa 根证书(我们只需要它来链接)
keytool -delete -alias rootCa -keystore peer2.jks -storepass passwd2
keytool -list -v -keystore peer2.jks -storepass passwd2

五、peer1/peer2需要单独信任密钥库,importcert到peerTrust.jks即可

5.2、注意点

保证运行项目的jdk和生成秘钥的keytool的jdk都是1.8.0_40。

本文含有隐藏内容,请 开通VIP 后查看