• Java SSL/TLS证书认证逻辑


    前言

    最近做项目使用httpclient转发https请求,但是遇到一些坑,尤其是证书的认证,证书认证一般都是单向的,除非相互访问,证书一般基于host,但是如果访问需要ip,那么JDK默认的认证就会不通过,但是SSL的握手只要jdk信任证书就可以通过认证。

    demo

    还是上2章的zuul 的demo,Apache httpclient 4.5.13

    1. ## 配置zuul的路由规则
    2. zuul:
    3. routes:
    4. rule1:
    5. path: /demo/**
    6. url: https://202.89.233.101:443/

    url配置IP,实际上应该配置host,但是host需要DNS的解析,并不是所有情况DNS都能解析的。 

    IP证书认证不过 

    这里使用cn.bing.com的IP来实验,通过ping获取ip,SSL默认的端口为443,访问http://localhost:8080/demo/ 

    1. Caused by: javax.net.ssl.SSLPeerUnverifiedException: Certificate for <202.89.233.101> doesn't match any of the subject alternative names: [*.platform.bing.com, *.bing.com, bing.com, ieonline.microsoft.com, *.windowssearch.com, cn.ieonline.microsoft.com, *.origin.bing.com, *.mm.bing.net, *.api.bing.com, *.cn.bing.net, *.cn.bing.com, ssl-api.bing.com, ssl-api.bing.net, *.api.bing.net, *.bingapis.com, bingsandbox.com, feedback.microsoft.com, insertmedia.bing.office.net, r.bat.bing.com, *.r.bat.bing.com, *.dict.bing.com, *.ssl.bing.com, *.appex.bing.com, *.platform.cn.bing.com, wp.m.bing.com, *.m.bing.com, global.bing.com, windowssearch.com, search.msn.com, *.bingsandbox.com, *.api.tiles.ditu.live.com, *.ditu.live.com, *.t0.tiles.ditu.live.com, *.t1.tiles.ditu.live.com, *.t2.tiles.ditu.live.com, *.t3.tiles.ditu.live.com, *.tiles.ditu.live.com, 3d.live.com, api.search.live.com, beta.search.live.com, cnweb.search.live.com, dev.live.com, ditu.live.com, farecast.live.com, image.live.com, images.live.com, local.live.com.au, localsearch.live.com, ls4d.search.live.com, mail.live.com, mapindia.live.com, local.live.com, maps.live.com, maps.live.com.au, mindia.live.com, news.live.com, origin.cnweb.search.live.com, preview.local.live.com, search.live.com, test.maps.live.com, video.live.com, videos.live.com, virtualearth.live.com, wap.live.com, webmaster.live.com, www.local.live.com.au, www.maps.live.com.au, webmasters.live.com, ecn.dev.virtualearth.net, www.bing.com]
    2. at org.apache.http.conn.ssl.SSLConnectionSocketFactory.verifyHostname(SSLConnectionSocketFactory.java:507)
    3. at org.apache.http.conn.ssl.SSLConnectionSocketFactory.createLayeredSocket(SSLConnectionSocketFactory.java:437)
    4. at org.apache.http.conn.ssl.SSLConnectionSocketFactory.connectSocket(SSLConnectionSocketFactory.java:384)
    5. at org.apache.http.impl.conn.DefaultHttpClientConnectionOperator.connect(DefaultHttpClientConnectionOperator.java:142)
    6. at org.apache.http.impl.conn.PoolingHttpClientConnectionManager.connect(PoolingHttpClientConnectionManager.java:376)
    7. at org.apache.http.impl.execchain.MainClientExec.establishRoute(MainClientExec.java:393)
    8. at org.apache.http.impl.execchain.MainClientExec.execute(MainClientExec.java:236)
    9. at org.apache.http.impl.execchain.ProtocolExec.execute(ProtocolExec.java:186)
    10. at org.apache.http.impl.execchain.RetryExec.execute(RetryExec.java:89)
    11. at org.apache.http.impl.client.InternalHttpClient.doExecute(InternalHttpClient.java:185)
    12. at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:118)
    13. at org.springframework.cloud.netflix.zuul.filters.route.SimpleHostRoutingFilter.forwardRequest(SimpleHostRoutingFilter.java:392)
    14. at org.springframework.cloud.netflix.zuul.filters.route.SimpleHostRoutingFilter.forward(SimpleHostRoutingFilter.java:311)
    15. at org.springframework.cloud.netflix.zuul.filters.route.SimpleHostRoutingFilter.run(SimpleHostRoutingFilter.java:227)

    报错了,因为证书是host,使用IP访问的时候host是认证不了的

    那么关键是org.apache.http.conn.ssl.SSLConnectionSocketFactory.verifyHostname(SSLConnectionSocketFactory.java:507)

    看这个类的定义,详细的说明了,证书认证不过怎么处理,导入自签证书或者jdk没收录的证书即可实现认证

     看定义SSLv2,要制定协议版本TLS1.2 TLS1.3(extend),支持的加密套件,比如国密就不支持,需要额外的jar。

     那么zuul的证书验证报错原理是默认情况下:org.apache.http.conn.ssl.DefaultHostnameVerifier

    1. public boolean verify(final String host, final SSLSession session) {
    2. try {
    3. final Certificate[] certs = session.getPeerCertificates();
    4. final X509Certificate x509 = (X509Certificate) certs[0];
    5. verify(host, x509);
    6. return true;
    7. } catch (final SSLException ex) {
    8. if (log.isDebugEnabled()) {
    9. log.debug(ex.getMessage(), ex);
    10. }
    11. return false;
    12. }
    13. }

    读取host(zuul的配置转发的url读取,这里配置了IP),获取证书链,获取链尾部的证书(尾部即第一个证书,根证书是CA的认证逻辑) ,判断证书的host是否相同,实际上也可以用IP作为证书的host

    1. public void verify(
    2. final String host, final X509Certificate cert) throws SSLException {
    3. final HostNameType hostType = determineHostFormat(host);
    4. final List subjectAlts = getSubjectAltNames(cert);
    5. if (subjectAlts != null && !subjectAlts.isEmpty()) {
    6. switch (hostType) {
    7. case IPv4:
    8. matchIPAddress(host, subjectAlts);
    9. break;
    10. case IPv6:
    11. matchIPv6Address(host, subjectAlts);
    12. break;
    13. default:
    14. matchDNSName(host, subjectAlts, this.publicSuffixMatcher);
    15. }
    16. } else {
    17. // CN matching has been deprecated by rfc2818 and can be used
    18. // as fallback only when no subjectAlts are available
    19. final X500Principal subjectPrincipal = cert.getSubjectX500Principal();
    20. final String cn = extractCN(subjectPrincipal.getName(X500Principal.RFC2253));
    21. if (cn == null) {
    22. throw new SSLException("Certificate subject for <" + host + "> doesn't contain " +
    23. "a common name and does not have alternative names");
    24. }
    25. matchCN(host, cn, this.publicSuffixMatcher);
    26. }
    27. }

    获取host的过程,以上面的bing证书为例,JDK读取后会区分是DNS(type=2)还是IP(type=7)

    1. static List getSubjectAltNames(final X509Certificate cert) {
    2. try {
    3. final Collection> entries = cert.getSubjectAlternativeNames();
    4. if (entries == null) {
    5. return Collections.emptyList();
    6. }
    7. final List result = new ArrayList();
    8. for (final List entry : entries) {
    9. final Integer type = entry.size() >= 2 ? (Integer) entry.get(0) : null;
    10. if (type != null) {
    11. if (type == SubjectName.DNS || type == SubjectName.IP) {
    12. final Object o = entry.get(1);
    13. if (o instanceof String) {
    14. result.add(new SubjectName((String) o, type));
    15. } else if (o instanceof byte[]) {
    16. // TODO ASN.1 DER encoded form
    17. }
    18. }
    19. }
    20. }
    21. return result;
    22. } catch (final CertificateParsingException ignore) {
    23. return Collections.emptyList();
    24. }
    25. }

    笔者怀疑是jdk的问题,毕竟jdk的证书与操作系统不通用,但是go语言也是同样的逻辑,也是解析为IP和host,然后验证。

    可以看到先把host判断类型,IPv4、IPv6、DNS(默认) ,比如bing的证书,这定义了很多DNS

    导出证书方式,以Chrome 浏览器访问bing为例

    先点击锁🔐图标,然后点击连接是安全的,也可能不安全,也是点击这一行

    导出,选择Base64格式,其他的格式也可以,实际上JDK也可以支持,选择每一层证书可以看证书的信息

    证书链,是从根证书开始的过程,如果是自签证书一般自己就是根证书,根证书一般是CA颁发用来签名的。

    比如bing的根证书,操作系统就有认证

     

    比如bing就是www.digicert.com 颁发的根证书,认证的,根证书有公钥和hash签名,根证书的私钥只有证书机构自己有,根证书是操作系统或浏览器信任的,所以根证书颁发的签名证书是信任的。证书认证的过程就是把自己的证书加入根证书的链中。

    证书签名是使用私钥加密hash签名,使用SSL加密传输,对方使用公钥解密,然后使用自己的hash签名和解密的hash比对,相同表示没有篡改,否则都是已经篡改。

    解决IP认证证书是host的情况

    那么怎么解决呢,

    1. 使用host转发,host转发不涉及证书验证host的问题
    2. 使用IP的证书,不推荐,IP很可能经常变化,域名是相对长期固定的,证书也是长期的
    3. 使用证书host配置验证,zuul以这个为例

    以zuul为例,实际上就是host的传递问题,验证证书时,zuul通过url解析,配置host域名转发即可,如果一定要配置IP,那么在org.springframework.cloud.netflix.zuul.filters.ZuulProperties

    ZuulRoute

    中增加dns的字段,配置各个url正确的host后,然后通过threadlocal传递到转发逻辑,这样写的比较死,实际上可以在创建连接的工厂里,通过开关忽略host校验

    1. public class ZuulApacheHttpClientConnectionManagerFactory
    2. implements ApacheHttpClientConnectionManagerFactory {
    3. private static final Log LOG = LogFactory
    4. .getLog(ZuulApacheHttpClientConnectionManagerFactory.class);
    5. private boolean ignoreHostVerify;
    6. public boolean isIgnoreHostVerify() {
    7. return ignoreHostVerify;
    8. }
    9. public void setIgnoreHostVerify(boolean ignoreHostVerify) {
    10. this.ignoreHostVerify = ignoreHostVerify;
    11. }
    12. public HttpClientConnectionManager newConnectionManager(boolean disableSslValidation,
    13. int maxTotalConnections, int maxConnectionsPerRoute) {
    14. return newConnectionManager(disableSslValidation, maxTotalConnections,
    15. maxConnectionsPerRoute, -1, TimeUnit.MILLISECONDS, null);
    16. }
    17. @Override
    18. public HttpClientConnectionManager newConnectionManager(boolean disableSslValidation,
    19. int maxTotalConnections, int maxConnectionsPerRoute, long timeToLive,
    20. TimeUnit timeUnit, RegistryBuilder registryBuilder) {
    21. if (registryBuilder == null) {
    22. registryBuilder = RegistryBuilder.create()
    23. .register(HTTP_SCHEME, PlainConnectionSocketFactory.INSTANCE);
    24. }
    25. if (disableSslValidation) {
    26. try {
    27. final SSLContext sslContext = SSLContext.getInstance("SSL");
    28. sslContext.init(null,
    29. new TrustManager[] { new DisabledValidationTrustManager() },
    30. new SecureRandom());
    31. registryBuilder.register(HTTPS_SCHEME, new SSLConnectionSocketFactory(
    32. sslContext, NoopHostnameVerifier.INSTANCE));
    33. }
    34. catch (NoSuchAlgorithmException e) {
    35. LOG.warn("Error creating SSLContext", e);
    36. }
    37. catch (KeyManagementException e) {
    38. LOG.warn("Error creating SSLContext", e);
    39. }
    40. }
    41. else {
    42. if (ignoreHostVerify) {
    43. try {
    44. SSLContext sslContext = SSLContext.getInstance("TLS");
    45. sslContext.init(null, null, new SecureRandom());
    46. registryBuilder.register("https", new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE));
    47. } catch (KeyManagementException | NoSuchAlgorithmException e) {
    48. throw new RuntimeException(e);
    49. }
    50. } else {
    51. registryBuilder.register("https",
    52. SSLConnectionSocketFactory.getSocketFactory());
    53. }
    54. }
    55. final Registry registry = registryBuilder.build();
    56. PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(
    57. registry, null, null, null, timeToLive, timeUnit);
    58. connectionManager.setMaxTotal(maxTotalConnections);
    59. connectionManager.setDefaultMaxPerRoute(maxConnectionsPerRoute);
    60. return connectionManager;
    61. }
    62. class DisabledValidationTrustManager implements X509TrustManager {
    63. @Override
    64. public void checkClientTrusted(X509Certificate[] x509Certificates, String s)
    65. throws CertificateException {
    66. }
    67. @Override
    68. public void checkServerTrusted(X509Certificate[] x509Certificates, String s)
    69. throws CertificateException {
    70. }
    71. @Override
    72. public X509Certificate[] getAcceptedIssuers() {
    73. return null;
    74. }
    75. }
    76. }

    加上配置

    1. @Configuration
    2. public class DemoConfiguration {
    3. @Bean
    4. @ConditionalOnProperty(name = "zuul.ssl.ignore.host", havingValue = "true", matchIfMissing = false)
    5. public ApacheHttpClientConnectionManagerFactory initApacheHttpClientConnectionManagerFactory(){
    6. ZuulApacheHttpClientConnectionManagerFactory clientConnectionManagerFactory = new ZuulApacheHttpClientConnectionManagerFactory();
    7. clientConnectionManagerFactory.setIgnoreHostVerify(true);
    8. return clientConnectionManagerFactory;
    9. }
    10. }

     加上开关后

    笔者尝试后结果如下(修改了端口,笔者开了几个demo):

    说明访问成功,只是浏览器限制了我们访问 

    自建证书试验

    那么根证书是没有认证的呢,或者没有根证书,就是我们自己通过openssl或者jdk的工具做的呢,先做一个证书试试,macOS内置了openssl,实际上很多Linux也内置了,不然需要安装

    而且支持国密算法,JDK需要额外的套件jar支持,笔者就用国际算法吧,通用性强,先使用rsa创建私钥,这里不考虑安全性,就用默认算法长度

    openssl genpkey -algorithm RSA -out private.key

    然后创建签名请求

    openssl req -new -key private.key -out demo.csr

    然后生成公钥证书(公钥就是要给出去的)

    openssl x509 -req -in demo.csr -signkey private.key -out server.crt 

     也可以查看证书信息

    openssl x509 -in server.crt -text -noout

    证书配置tomcat(包括嵌入式),nginx,或者ssl服务器即可,demo tomcat、nginx官网很详细

    直接报错

    javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
    

    解决办法

    目前通用的办法有2种,让JDK识别信任根证书;忽略认证证书的过程

    证书认证过程分析

    证书认证在ssl握手的时候,即socket(Linux sock)连接上后就会client hello和server hello,这个时候会传递证书,当然TLS1.2和TLS1.3会有些不同,但是证书的传递是必须的,即服务器公钥传递

    SpringCloud使用httpclient的时候,在org.apache.http.conn.ssl.SSLConnectionSocketFactory发起socket的握手

    sslsock.startHandshake();

    而JDK使用JCE和JSSE的jar来完成SSL的通信,认证过程在JSSE的jar,里面有判断是否TLS1.3,然后使用sun.security.ssl.SSLHandshake,来发送client hello

    发送时协商tls版本和加密套件,会发送随机数,用于预主密钥的生成

     发送client hello的信息

    然后server发送serverhello回执,协商加密套件,服务端随机数,证书等

    serverhello 

    keyexchange

    进行证书认证

    比如微软的证书,证书链和密钥交换算法

    调用sun.security.validator.PKIXValidator,对证书链进行校验,和JDK自己内置的证书逐一对比,读取根证书

    比如demo这里就从jdk读取到了根证书

    然后sun.security.validator.Validator,检查host证书

    加入JDK信任

    实际上根据JDK的证书认证原理,只需要把JDK没有内置的根证书加入信任,或者把那个证书加入信任即可,建议信任那个证书,而非根证书

    可以使用keytool工具导入,这位博主写的这个很好,直接用openssl工具导出证书,很多教程都是通过浏览器导出证书,这个在很多生产环境是不现实的。

    将所访问的SSL站点证书添加至JVM。
    echo -n |openssl s_client -connect 182.242.198.40:5000|sed -ne'/BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' > xxxxx.cert
    此命令获取服务端证书链。
    keytool -importcert -alias 182.242.198.40-1 -keystore $JAVA_HOME/jre/lib/security/cacerts -storepass changeit -file xxxxx.cert
    ————————————————
    版权声明:本文为CSDN博主「呆呆鸟哈密瓜」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/qq_37084673/article/details/108597947

    笔者使用这个实际上也跳进坑里了,并不是命令的坑,而是JAVA_HOME,命令执行的JAVA_HOME和实际程序运行的JAVA_HOME未必是同一个,这个一定要在ps aux|grep java后根据实际执行的进程的JAVA_HOME执行命令

    忽略认证

    忽略信任这个实际上SpringCloud官方就已经支持,就是自己写一个信任manager类,然后里面什么都不做

    然后载入sslcontext,这样每次请求就不去信任证书了,安全等级会降低,可以安装zuul的请求放开某一个url的信任,这样可以精确限制,对于SpringCloud只需要配置disableSslValiadation即可

    JAVA程序自动加入信任

    使用代码加载,最开始源代码来源于InstallCert,不过我改了一下

    1. package com.feng.zuul.demo.connection;
    2. /*
    3. * Copyright 2006 Sun Microsystems, Inc. All Rights Reserved.
    4. *
    5. * Redistribution and use in source and binary forms, with or without
    6. * modification, are permitted provided that the following conditions
    7. * are met:
    8. *
    9. * - Redistributions of source code must retain the above copyright
    10. * notice, this list of conditions and the following disclaimer.
    11. *
    12. * - Redistributions in binary form must reproduce the above copyright
    13. * notice, this list of conditions and the following disclaimer in the
    14. * documentation and/or other materials provided with the distribution.
    15. *
    16. * - Neither the name of Sun Microsystems nor the names of its
    17. * contributors may be used to endorse or promote products derived
    18. * from this software without specific prior written permission.
    19. *
    20. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
    21. * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
    22. * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
    23. * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
    24. * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
    25. * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
    26. * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
    27. * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
    28. * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
    29. * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
    30. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    31. */
    32. import java.io.BufferedReader;
    33. import java.io.File;
    34. import java.io.FileInputStream;
    35. import java.io.FileOutputStream;
    36. import java.io.InputStream;
    37. import java.io.InputStreamReader;
    38. import java.io.OutputStream;
    39. import java.nio.file.Files;
    40. import java.nio.file.Paths;
    41. import java.security.KeyStore;
    42. import java.security.MessageDigest;
    43. import java.security.cert.CertificateException;
    44. import java.security.cert.X509Certificate;
    45. import java.util.Collection;
    46. import java.util.List;
    47. import javax.net.ssl.SSLContext;
    48. import javax.net.ssl.SSLException;
    49. import javax.net.ssl.SSLSocket;
    50. import javax.net.ssl.SSLSocketFactory;
    51. import javax.net.ssl.TrustManager;
    52. import javax.net.ssl.TrustManagerFactory;
    53. import javax.net.ssl.X509TrustManager;
    54. public class InstallCert {
    55. // 我们要访问的HTTPS服务,如访问 https://www.bing.com
    56. public static final String hostName = "cn.bing.com";
    57. private static final char SEP = File.separatorChar;
    58. public static void main(String[] args) throws Exception {
    59. String host = hostName;
    60. int port = 443;
    61. char[] passphrase = "changeit".toCharArray();
    62. File file = new File(System.getProperty("java.home") + SEP + "lib" + SEP + "security" + SEP + "cacerts");
    63. if (!file.exists() || !file.isFile()) {
    64. System.out.println("jre lib security cacerts is not existed or not file in jdk");
    65. return;
    66. }
    67. System.out.println("Loading KeyStore " + file + "...");
    68. KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
    69. try (InputStream in = Files.newInputStream(file.toPath())) {
    70. ks.load(in, passphrase);
    71. }
    72. SSLContext context = SSLContext.getInstance("TLS");
    73. TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
    74. tmf.init(ks);
    75. X509TrustManager defaultTrustManager = (X509TrustManager) tmf.getTrustManagers()[0];
    76. SavingTrustManager tm = new SavingTrustManager(defaultTrustManager);
    77. context.init(null, new TrustManager[]{tm}, null);
    78. SSLSocketFactory factory = context.getSocketFactory();
    79. System.out.println("Opening connection to " + host + ":" + port + "...");
    80. SSLSocket socket = (SSLSocket) factory.createSocket(host, port);
    81. socket.setSoTimeout(10000);
    82. try {
    83. System.out.println("Starting SSL handshake...");
    84. socket.startHandshake();
    85. System.out.println();
    86. System.out.println("No errors, certificate is already trusted");
    87. //当然不可能执行这个,因为我们的证书校验getAcceptedIssuers定义直接抛异常了
    88. return;
    89. } catch (SSLException e) {
    90. System.out.println();
    91. e.printStackTrace(System.out);
    92. } finally {
    93. socket.close();
    94. }
    95. X509Certificate[] chain = tm.chain;
    96. if (chain == null) {
    97. System.out.println("Could not obtain server certificate chain");
    98. return;
    99. }
    100. System.out.println("Server sent " + chain.length + " certificate(s):");
    101. System.out.println();
    102. for (X509Certificate cert : chain) {
    103. Collection> nativeNames = cert.getSubjectAlternativeNames();
    104. if (nativeNames == null || nativeNames.isEmpty()) continue;
    105. String alias = host + "-" + cert.getSerialNumber();
    106. ks.setCertificateEntry(alias, cert);
    107. System.out.println();
    108. System.out.println(cert);
    109. System.out.println();
    110. System.out.println("Added certificate to keystore 'jssecacerts' using alias '" + alias + "'");
    111. }
    112. try (OutputStream out = Files.newOutputStream(file.toPath());) {
    113. ks.store(out, passphrase);
    114. System.out.println("Added all certificate to keystore");
    115. }
    116. }
    117. private static class SavingTrustManager implements X509TrustManager {
    118. private final X509TrustManager tm;
    119. private X509Certificate[] chain;
    120. SavingTrustManager(X509TrustManager tm) {
    121. this.tm = tm;
    122. }
    123. public X509Certificate[] getAcceptedIssuers() {
    124. throw new UnsupportedOperationException();
    125. }
    126. public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
    127. throw new UnsupportedOperationException();
    128. }
    129. public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
    130. this.chain = chain;
    131. tm.checkServerTrusted(chain, authType);
    132. }
    133. }
    134. }

    这个加载的证书,可以被JDK初始化载入,那么信任的证书必须重启应用才能生效,那么用什么办法不重启呢。方法来源于org.springframework.cloud.configuration.SSLContextFactory,还是Spring实现的,照搬过来即可,不过Spring是静态文件加载,我们可以动态代码加载

    HTTPS是sslcontext去发起的,那么把证书动态给创建这个上下文即可

    1. public SSLContext createSSLContext() throws GeneralSecurityException, IOException {
    2. SSLContextBuilder builder = new SSLContextBuilder();
    3. char[] keyPassword = properties.keyPassword();
    4. KeyStore keyStore = createKeyStore();
    5. try {
    6. // 载入文件
    7. builder.loadKeyMaterial(keyStore, keyPassword);
    8. }
    9. catch (UnrecoverableKeyException e) {
    10. if (keyPassword.length == 0) {
    11. // Retry if empty password, see
    12. // https://rt.openssl.org/Ticket/Display.html?id=1497&user=guest&pass=guest
    13. builder.loadKeyMaterial(keyStore, new char[] { '\0' });
    14. }
    15. else {
    16. throw e;
    17. }
    18. }
    19. KeyStore trust = createTrustStore();
    20. if (trust != null) {
    21. // 加入信任
    22. builder.loadTrustMaterial(trust, null);
    23. }
    24. return builder.build();
    25. }

    改造一下:在自定义connectionmanager的bean增加信任证书加载的逻辑,把InstallCert的证书传过来,当然可以在初始化或者动态实时处理

    比如应用启动初始化

    1. @Configuration
    2. public class DemoConfiguration {
    3. @Bean
    4. @ConditionalOnProperty(name = "zuul.ssl.ignore.host", havingValue = "true", matchIfMissing = false)
    5. public ApacheHttpClientConnectionManagerFactory initApacheHttpClientConnectionManagerFactory(){
    6. ZuulApacheHttpClientConnectionManagerFactory clientConnectionManagerFactory = new ZuulApacheHttpClientConnectionManagerFactory();
    7. clientConnectionManagerFactory.setIgnoreHostVerify(true);
    8. return clientConnectionManagerFactory;
    9. }
    10. @Bean
    11. @ConditionalOnBean(InstallCert.class)
    12. @ConditionalOnProperty(name = "zuul.ssl.ignore.host", havingValue = "true", matchIfMissing = false)
    13. public ApacheHttpClientConnectionManagerFactory initApacheHttpClientConnectionManagerFactory(InstallCert installCert){
    14. ZuulApacheHttpClientConnectionManagerFactory clientConnectionManagerFactory = new ZuulApacheHttpClientConnectionManagerFactory();
    15. clientConnectionManagerFactory.setIgnoreHostVerify(true);
    16. clientConnectionManagerFactory.setInstallCert(installCert);
    17. return clientConnectionManagerFactory;
    18. }
    19. @Bean
    20. public InstallCert initCert(){
    21. return new InstallCert();
    22. }

    可以通过各种方式控制,来达到自动JDK不识别证书的信任,还可以根据sslcontext的创建逻辑,实现租户隔离,达到我们按想法定制的目的。 

    总结

    实际上证书的认证就是链式认证,加入根证书链,因为根证书是信任的,CA机构是认可的,那么CA颁发的根证书是信任的,经常报道的Chrome移除xxx机构颁发的根证书,表示这些证书链下的证书不信任了,毕竟公钥和私钥任何证书都能生成,证书链也可以仿造。

    在服务器的应用中,如果证书验证不通过可以加入证书认证的过程,如果是IP访问,那么host认证失败就让IP的host来验证,如果国密算法不支持就加入国密支持包,实在不想认证也可以跳过认证,浏览器一般是飘红,程序无感知,前提是需要知道安全性,实际上绝大多数内网络证书都是不被认证的,除非定制操作系统配置或者JDK等中间件配置,可根据实际情况来决定解决。

  • 相关阅读:
    【刷爆LeetCode】七月算法集训(30)拓扑排序
    同态加密原理解析
    【Python】split()方法简介
    【STM32训练—WiFi模块】第一篇、STM32驱动ESP8266WiFi模块获取网络时间
    「Python条件结构」嵌套if:判断三角形及三角形的类型
    C语言 const详解
    代码随想录算法训练营第60天|● 84.柱状图中最大的矩形
    8 种实现垂直和水平居中元素的方法汇总
    ICPC+CCPC写题日记~
    软件推荐:PowerToys
  • 原文地址:https://blog.csdn.net/fenglllle/article/details/133844068