【备忘录系列】springboot2 https 单向和双向证书 配置到内嵌tomcat

一、环境

springboot :2.1.3.RELEASE

tomcat:springboot内嵌tomcat

二、基础概念:

1.证书类型介绍:

  • .DER .CER,文件是二进制格式,只保存证书,不保存私钥。
  • .PEM,一般是文本格式,可保存证书,可保存私钥。
  • .CRT,可以是二进制格式,可以是文本格式,与 .DER 格式相同,不保存私钥。
  • .PFX .P12,二进制格式,同时包含证书和私钥,一般有密码保护。
  • .JKS,二进制格式,同时包含证书和私钥,一般有密码保护。

2.单向认证流程

【备忘录系列】springboot2 https 单向和双向证书 配置到内嵌tomcat_第1张图片

1、客户端向服务端发送SSL协议版本号、加密算法种类、随机数等信息。

2、服务端给客户端返回SSL协议版本号、加密算法种类、随机数等信息,同时也返回服务器端的证书,即公钥证书

3、客户端使用服务端返回的信息验证服务器的合法性,包括:

  • 证书是否过期

  • 发型服务器证书的CA是否可靠

  • 返回的公钥是否能正确解开返回证书中的数字签名

  • 服务器证书上的域名是否和服务器的实际域名相匹配

验证通过后,将继续进行通信,否则,终止通信

4、客户端向服务端发送自己所能支持的对称加密方案,供服务器端进行选择

5、服务器端在客户端提供的加密方案中选择加密程度最高的加密方式。

6、服务器将选择好的加密方案通过明文方式返回给客户端

7、客户端接收到服务端返回的加密方式后,使用该加密方式生成产生随机码,用作通信过程中对称加密的密钥,使用服务端返回的公钥进行加密,将加密后的随机码发送至服务器

8、服务器收到客户端返回的加密信息后,使用自己的私钥进行解密,获取对称加密密钥。 在接下来的会话中,服务器和客户端将会使用该密码进行对称加密,保证通信过程中信息的安全。

3.双向认证流程

【备忘录系列】springboot2 https 单向和双向证书 配置到内嵌tomcat_第2张图片

1、客户端向服务端发送SSL协议版本号、加密算法种类、随机数等信息。

2、服务端给客户端返回SSL协议版本号、加密算法种类、随机数等信息,同时也返回服务器端的证书,即公钥证书

3、客户端使用服务端返回的信息验证服务器的合法性,包括:

  • 证书是否过期

  • 发型服务器证书的CA是否可靠

  • 返回的公钥是否能正确解开返回证书中的数字签名

  • 服务器证书上的域名是否和服务器的实际域名相匹配

验证通过后,将继续进行通信,否则,终止通信

4、服务端要求客户端发送客户端的证书,客户端会将自己的证书发送至服务端

5、验证客户端的证书,通过验证后,会获得客户端的公钥

6、客户端向服务端发送自己所能支持的对称加密方案,供服务器端进行选择

7、服务器端在客户端提供的加密方案中选择加密程度最高的加密方式

8、将加密方案通过使用之前获取到的公钥进行加密,返回给客户端

9、客户端收到服务端返回的加密方案密文后,使用自己的私钥进行解密,获取具体加密方式,而后,产生该加密方式的随机码,用作加密过程中的密钥,使用之前从服务端证书中获取到的公钥进行加密后,发送给服务端

10、服务端收到客户端发送的消息后,使用自己的私钥进行解密,获取对称加密的密钥,在接下来的会话中,服务器和客户端将会使用该密码进行对称加密,保证通信过程中信息的安全。

三、springboot配置

此处使用java自带的keytool工具生产自签的证书。

单向认证时:

1.生成服务器端证书:

cmd:keytool -genkey -alias tomcat  -storetype PKCS12 -keyalg RSA -keysize 2048  -keystore keystore127.p12 -validity 3650

具体参数含义自行百度。

ps:在输入您的名字与姓氏是什么?时,输入相关域名或ip。否则在使用httpclient访问https接口时 如果不绕过证书校验会报域名不一致错误。

【备忘录系列】springboot2 https 单向和双向证书 配置到内嵌tomcat_第3张图片

2.修改application.yml

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 也不会报错。暂时不知道什么原因 先记录下来。

3.通过访问接口在浏览器里下载public cert。命名为:serverPublicCert.cer

双向认证时:

1.生成服务器端证书:

cmd:keytool -genkey -alias tomcat  -storetype PKCS12 -keyalg RSA -keysize 2048  -keystore keystore127.p12 -validity 3650

具体参数含义自行百度。

ps:在输入您的名字与姓氏是什么?时,输入相关域名或ip。否则在使用httpclient访问https接口时 如果不绕过证书校验会报域名不一致错误。

【备忘录系列】springboot2 https 单向和双向证书 配置到内嵌tomcat_第4张图片

2.生成客户端证书:

cmd:keytool -genkey -v -alias test -keyalg RSA -storetype PKCS12 -keystore client.p12

3.需要服务端证书信赖客户端证书:

个人理解:此处应该是为了在上图双向认证流程中的第5步验证客户端证书

p12类型证书可以包含多个证书但不可以包含p12类型的证书。所以需要把客户端证书导出成cer,再把cer导入到服务端的p12.

客户端证书导出成cer

cmd:keytool -export -alias test -keystore client.p12 -storetype PKCS12 -rfc -file client.cer

cer导入到服务端证书中

cmd:keytool -import -v -file client.cer -keystore keystore127.p12

4.修改application.yml

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

你可能感兴趣的:(其他,备忘录)