1.背景java
如今公司系统较多,多系统间须要进行通信,已经实现了https单项认证,如今为了保证通信的安全,须要实现https双向认证,让系统间通信更安全。web
2.问题spring
当时拿到这个问题,第一时间去网上百度https双向认证的案例,试了好多种,有单项认证的实现,还有双向认证基于浏览器端的访问,对于两个系统之间的https双向认证不多,本身也浪费了好多时间没有成功,因而,下定决心,先把https双向认证的原理搞清楚,懂了原理后,解决起来问题就方便了,下面附上https单项认证和双向认证的原理图apache
经过图片可知,https单项认证是客户端对服务端的认证,双向认证是在此基础上加了服务端对客户端的认证!浏览器
3.前提准备安全
1. 多个springboot项目,以两个springboot项目为例,这里暂且叫Client项目,Server项目。springboot
2. Client项目所在的服务器,Client.p12(客户端证书库) ,Client.cer(客户端公钥);服务器
Server项目所在的服务器,Server.p12(服务端证书库),Server.cer(服务端公钥);app
4.实现步骤dom
1. 建立springboot项目,Client ,Server 此处,请自行百度建立如何建立springboot项目。
2. 使用jdk自带的keytool工具,生成Client和Server端相应的证书,步骤以下:
a. Client端证书生成步骤:
1.生成客户端 Client.p12文件
keytool -genkey -v -alias Client -keyalg RSA -storetype PKCS12 -keystore C:\D\jdk1.8.0_161\Client.p12
设置密码: lq123456
注意事项:生成证书,您的名字与姓氏一项,应该填写服务器的ip(此处应该是域名,可是没有域名,故此处填写服务器ip)
2 . 导出客户端公钥Client.cer 文件
keytool -keystore C:\D\jdk1.8.0_161\Client.p12 -export -alias Client -file C:\D\jdk1.8.0_161\Client.cer
b. Server端证书生成步骤
1. 生成服务端Server.p12文件
keytool -genkey -v -alias Server -keyalg RSA -storetype PKCS12 -keystore C:\D\jdk1.8.0_161\Server.p12
设置密码: lq123456
注意事项:生成证书,您的名字与姓氏一项,应该填写服务器的ip(此处应该是域名,可是没有域名,故此处填写服务器ip)
2. 导出服务端公钥Server.cer 文件
keytool -keystore C:\D\jdk1.8.0_161\Server.p12 -export -alias Server -file C:\D\jdk1.8.0_161\Server.cer
c. 将Client端和Server端的公钥文件(.cer文件)导入双方系统的jre运行环境的cacerts证书库
1. 将客户端公钥导入的服务端jdk信任库
keytool -import -file Client.cer -keystore C:\D\jdk1.8.0_161\jre\lib\security\cacerts –v
2. 将服务端公钥导入到客户端的jdk信任库
keytool -import -file Server.cer -keystore C:\D\jdk1.8.0_161\jre\lib\security\cacerts –v
3. 将客户端公钥导入到服务端Server.p12证书库
keytool -import -v -file C:\D\jdk1.8.0_161\Client.cer -keystore C:\D\jdk1.8.0_161\server.p12
注意事项:此处导入的密码为changeit,默认密码
至此,证书生成完成,证书库导入完成!
d. 代码实现
1.Server端
第一步:在application.properties中添加以下配置:包括本地证书库和受信任证书配置
server.port=8090 server.address=10.119.165.171 server.ssl.key-store=classpath:server.p12 server.ssl.key-store-password=lq123456 server.ssl.key-alias=server server.ssl.keyStoreType=JKS server.ssl.trust-store=classpath:server.p12 server.ssl.trust-store-password=lq123456 server.ssl.client-auth=need server.ssl.trust-store-type=JKS server.ssl.trust-store-provider=SUN
第二步:服务端接口开放
package com.example.server1.controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * @author lucasliang * @date 01/03/2019 3:21 下午 * @Description */ @RestController @RequestMapping("/server") public class ServerController { @RequestMapping("/hello") public String getUrlInfo() { return "************request https success************"; } }
2. Client端
第一步:在application.properties中添加以下配置:
server.port=8091 server.address=10.119.165.171 server.ssl.key-store=classpath:client.p12 server.ssl.key-store-password=lq123456 server.ssl.key-alias=client server.ssl.keyStoreType=JKS
第二步:单元测试:
package com.example.client; import java.io.FileInputStream; import java.security.KeyStore; import java.security.SecureRandom; import javax.net.ssl.KeyManager; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManagerFactory; import org.apache.http.HttpEntity; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.conn.ssl.SSLConnectionSocketFactory; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.http.util.EntityUtils; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; /** * @author lucasliang * @date 04/03/2019 2:13 下午 * @Description */ @SpringBootTest(classes = {Client1ApplicationTests.class}) @RunWith(SpringRunner.class) public class P12CertTest { private final static String TEST_URL = "https://10.119.165.171:8090/server/hello"; @Test public void getHKVesselTrip() throws Exception { KeyStore clientStore = KeyStore.getInstance("PKCS12"); clientStore .load(new FileInputStream("C:\\D\\jdk1.8.0_161\\shuangxiang\\client_original.p12"), "lq123456".toCharArray()); KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); kmf.init(clientStore, "lq123456".toCharArray()); KeyManager[] kms = kmf.getKeyManagers(); TrustManagerFactory tmf = TrustManagerFactory .getInstance(TrustManagerFactory.getDefaultAlgorithm()); KeyStore trustStore = KeyStore.getInstance("JKS"); trustStore.load(new FileInputStream("C:\\D\\jdk1.8.0_161\\jre\\lib\\security\\cacerts"), "changeit".toCharArray()); tmf.init(trustStore); TrustManager[] tms = tmf.getTrustManagers(); SSLContext sslContext = SSLContext.getInstance("TLS"); sslContext.init(kms, tms, new SecureRandom()); SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext, SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER); CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(sslsf).build(); try { HttpGet httpget = new HttpGet(TEST_URL); System.out.println("executing request" + httpget.getRequestLine()); CloseableHttpResponse response = httpclient.execute(httpget); try { HttpEntity entity = response.getEntity(); if (entity != null) { System.out.println(EntityUtils.toString(entity)); } } finally { response.close(); } } finally { httpclient.close(); } } }
此时,Client端发送请求到Server端,请求成功,https双向认证完成!
5. 心得
凡事搞懂原理,事半功倍!