springboot :2.1.3.RELEASE
tomcat:springboot内嵌tomcat
1、客户端向服务端发送SSL协议版本号、加密算法种类、随机数等信息。
2、服务端给客户端返回SSL协议版本号、加密算法种类、随机数等信息,同时也返回服务器端的证书,即公钥证书
3、客户端使用服务端返回的信息验证服务器的合法性,包括:
证书是否过期
发型服务器证书的CA是否可靠
返回的公钥是否能正确解开返回证书中的数字签名
服务器证书上的域名是否和服务器的实际域名相匹配
验证通过后,将继续进行通信,否则,终止通信
4、客户端向服务端发送自己所能支持的对称加密方案,供服务器端进行选择
5、服务器端在客户端提供的加密方案中选择加密程度最高的加密方式。
6、服务器将选择好的加密方案通过明文方式返回给客户端
7、客户端接收到服务端返回的加密方式后,使用该加密方式生成产生随机码,用作通信过程中对称加密的密钥,使用服务端返回的公钥进行加密,将加密后的随机码发送至服务器
8、服务器收到客户端返回的加密信息后,使用自己的私钥进行解密,获取对称加密密钥。 在接下来的会话中,服务器和客户端将会使用该密码进行对称加密,保证通信过程中信息的安全。
1、客户端向服务端发送SSL协议版本号、加密算法种类、随机数等信息。
2、服务端给客户端返回SSL协议版本号、加密算法种类、随机数等信息,同时也返回服务器端的证书,即公钥证书
3、客户端使用服务端返回的信息验证服务器的合法性,包括:
证书是否过期
发型服务器证书的CA是否可靠
返回的公钥是否能正确解开返回证书中的数字签名
服务器证书上的域名是否和服务器的实际域名相匹配
验证通过后,将继续进行通信,否则,终止通信
4、服务端要求客户端发送客户端的证书,客户端会将自己的证书发送至服务端
5、验证客户端的证书,通过验证后,会获得客户端的公钥
6、客户端向服务端发送自己所能支持的对称加密方案,供服务器端进行选择
7、服务器端在客户端提供的加密方案中选择加密程度最高的加密方式
8、将加密方案通过使用之前获取到的公钥进行加密,返回给客户端
9、客户端收到服务端返回的加密方案密文后,使用自己的私钥进行解密,获取具体加密方式,而后,产生该加密方式的随机码,用作加密过程中的密钥,使用之前从服务端证书中获取到的公钥进行加密后,发送给服务端
10、服务端收到客户端发送的消息后,使用自己的私钥进行解密,获取对称加密的密钥,在接下来的会话中,服务器和客户端将会使用该密码进行对称加密,保证通信过程中信息的安全。
此处使用java自带的keytool工具生产自签的证书。
cmd:keytool -genkey -alias tomcat -storetype PKCS12 -keyalg RSA -keysize 2048 -keystore keystore127.p12 -validity 3650
具体参数含义自行百度。
ps:在输入您的名字与姓氏是什么?时,输入相关域名或ip。否则在使用httpclient访问https接口时 如果不绕过证书校验会报域名不一致错误。
server: port: 8080 ssl: key-store: classpath:keystore127.p12 key-store-password: 123456 key-store-type: PKCS12 key-alias: tomcat
完成此步,springboot项目已经支持https单向认证接口。可以访问https://127.0.0.1:8080/xxx
ps:调试时发现key-store-type: JKS 也不会报错。暂时不知道什么原因 先记录下来。
cmd:keytool -genkey -alias tomcat -storetype PKCS12 -keyalg RSA -keysize 2048 -keystore keystore127.p12 -validity 3650
具体参数含义自行百度。
ps:在输入您的名字与姓氏是什么?时,输入相关域名或ip。否则在使用httpclient访问https接口时 如果不绕过证书校验会报域名不一致错误。
cmd:keytool -genkey -v -alias test -keyalg RSA -storetype PKCS12 -keystore client.p12
个人理解:此处应该是为了在上图双向认证流程中的第5步验证客户端证书
p12类型证书可以包含多个证书但不可以包含p12类型的证书。所以需要把客户端证书导出成cer,再把cer导入到服务端的p12.
cmd:keytool -export -alias test -keystore client.p12 -storetype PKCS12 -rfc -file client.cer
cmd:keytool -import -v -file client.cer -keystore keystore127.p12
server: port: 8080 ssl: key-store: classpath:keystore127.p12 key-store-password: 123456 key-store-type: PKCS12 key-alias: tomcat trust-store: classpath:keystore127.p12 trust-store-password: 123456 trust-store-type: JKS trust-store-provider: SUN client-auth: need
完成此步,springboot项目已经支持https双向认证接口。可以访问https://127.0.0.1:8080/xxx。此时浏览器(chrome)应该会出现访问错误,如果是window系统可以双击刚才生成的client.p12导入到系统中,即可访问。此文章重点讲述server2server情况。
四、httpclient调用https接口工具
public static void main(String[] args) {
try {
String url = "https://127.0.0.1:8080/trapi/index";
SSLContext sc = SSLContext.getInstance("SSL");
// 单向认证
String serverPublicCertPath = "serverPublicCert.cer";
String serverPublicCertAlias = null;
HttpUtil.trustManager(sc,serverPublicCertPath,serverPublicCertAlias);
CloseableHttpClient httpclient = HttpClients.custom()
.setSSLContext(sc)
.build();
HttpGet request = new HttpGet(url);
CloseableHttpResponse execute = httpclient.execute(request);
HttpEntity entity = execute.getEntity();
String s = EntityUtils.toString(entity);
System.out.println("===s" + s);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 请求https时
* 单向认证时候使用
*
* @param sslContext
* @param serverPublicCertPath 公钥证书的classPath
* @param serverPublicCertAlias 公钥证书的别名 可以不传
* @throws CertificateException
* @throws KeyStoreException
* @throws IOException
* @throws NoSuchAlgorithmException
* @throws KeyManagementException
*/
public static void trustManager(SSLContext sslContext, String serverPublicCertPath, String serverPublicCertAlias) throws CertificateException, KeyStoreException, IOException, NoSuchAlgorithmException, KeyManagementException {
InputStream caInputStream = HttpUtil.class.getClassLoader().getResourceAsStream(serverPublicCertPath);
// 证书的别名,即:key。 注:cAalias只需要保证唯一即可,不过推荐使用生成keystore时使用的别名。
if (StringUtils.isBlank(serverPublicCertAlias)) {
serverPublicCertAlias = System.currentTimeMillis() + "" + new SecureRandom().nextInt(1000);
}
// 证书工厂
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
// 秘钥仓库
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(null);
keyStore.setCertificateEntry(serverPublicCertAlias, certificateFactory.generateCertificate(caInputStream));
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(keyStore);
TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
throw new IllegalStateException("Unexpected default trust managers:" + Arrays.toString(trustManagers));
}
X509TrustManager trustManager = (X509TrustManager) trustManagers[0];
sslContext.init(null, new TrustManager[]{trustManager}, new SecureRandom());
}
public static void main(String[] args) {
try {
String url = "https://127.0.0.1:8080/trapi/index";
SSLContext sc = SSLContext.getInstance("SSL");
// 单向认证
String serverPublicCertPath = "serverPublicCert.cer";
String serverPublicCertAlias = null;
String clientPrivateCertPath = "client.p12";
String clientPrivateCertPassword = "123456";
HttpUtil.trustManager(sc,serverPublicCertPath,serverPublicCertAlias,clientPrivateCertPath,clientPrivateCertPassword);
CloseableHttpClient httpclient = HttpClients.custom()
.setSSLContext(sc)
.build();
HttpGet request = new HttpGet(url);
CloseableHttpResponse execute = httpclient.execute(request);
HttpEntity entity = execute.getEntity();
String s = EntityUtils.toString(entity);
System.out.println("===s" + s);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* https 双向认证
* @param sslContext
* @param serverPublicCertPath
* @param serverPublicCertAlias
* @param clientPrivateCertPath
* @param clientPrivateCertPassword
* @throws CertificateException
* @throws KeyStoreException
* @throws IOException
* @throws NoSuchAlgorithmException
* @throws KeyManagementException
* @throws UnrecoverableKeyException
*/
public static void trustManager(SSLContext sslContext, String serverPublicCertPath, String serverPublicCertAlias,
String clientPrivateCertPath,String clientPrivateCertPassword) throws CertificateException, KeyStoreException, IOException, NoSuchAlgorithmException, KeyManagementException, UnrecoverableKeyException {
InputStream caInputStream = HttpUtil.class.getClassLoader().getResourceAsStream(serverPublicCertPath);
InputStream caInputStream2 = HttpUtil.class.getClassLoader().getResourceAsStream(clientPrivateCertPath);
// 证书的别名,即:key。 注:cAalias只需要保证唯一即可,不过推荐使用生成keystore时使用的别名。
if (StringUtils.isBlank(serverPublicCertAlias)) {
serverPublicCertAlias = System.currentTimeMillis() + "" + new SecureRandom().nextInt(1000);
}
// 证书工厂
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
// 秘钥仓库
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(null);
keyStore.setCertificateEntry(serverPublicCertAlias, certificateFactory.generateCertificate(caInputStream));
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(keyStore);
TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
throw new IllegalStateException("Unexpected default trust managers:" + Arrays.toString(trustManagers));
}
X509TrustManager trustManager = (X509TrustManager) trustManagers[0];
KeyManagerFactory keyFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
KeyStore keyStore2 = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore2.load(caInputStream2, clientPrivateCertPassword.toCharArray());
keyFactory.init(keyStore2, clientPrivateCertPassword.toCharArray());
KeyManager[] keyManagers = keyFactory.getKeyManagers();
sslContext.init(keyManagers, new TrustManager[]{trustManager}, new SecureRandom());
}
至此,springboot2项目使用内嵌tomcat支持单向或双向ssl认证流程讲述完结。
参考文献:
干货 | 图解 https 单向认证和双向认证! https://blog.csdn.net/superviser3000/article/details/80812263
spring boot,https,双向ssl认证 https://www.cnblogs.com/htuao/p/10091458.html