当前位置:   article > 正文

基于tomcat的https(ssl)双向认证_tomcat ssl

tomcat ssl

一、背景介绍

        某个供应商服务需要部署到海外,如果海外多个地区需要部署多个服务,最好能实现统一登录,这样可以减轻用户的使用负担(不用记录一堆密码)。由于安全问题(可能会泄露用户数据),海外服务不能直连公司sso服务端,因此需要其他的方案解决安全问题。最终的安全方案中需要用到SSL双向认证进行数据的传输和交互,并且只指定某些个别接口实现SSL双向认证。在此背景下,这篇文章介绍基于tomcat的SSL双向认证的简单实现。

二、SSL简单介绍

        SSL(Secure Sockets Layer 安全套接层)就是一种协议(规范),用于保障客户端和服务器端通信的安全,以免通信时传输的信息被窃取或者修改。

        1.怎样保障数据传输安全?

        客户端和服务器端在进行握手(客户端和服务器建立连接和交换参数的过程称之为握手)时会产生一个“对话密钥”(session key),用来加密接下来的数据传输,解密时也是用的这个“对话密钥”,而这个“对话密钥”只有客户端和服务器端知道。也就是说只要这个“对话密钥”不被破解,就能保证安全。

  2. 客户端证书和服务器端证书

        客户端证书和服务器端证书用于证明自己的身份,就好比每个人都有一张身份证,这种身份证是唯一的。一般来说,只要有服务器端的证书就可以了,但是有时需要客户端提供自己的证书,已证明其身份。

三、生成自签名的服务器端证书和导入服务器端信任证书库

        一般证书可以使用权威机构颁发的证书,如:veri sign,百度使用的就是veri sign颁发的证书,这样的权威证书机构是受信任的,但是这些机构颁发的证书往往是需要收费的,这样的证书也难得到。对于小型企业来说为了节约成本,常常使用自签名的证书。   

        接下来使用JDK keytool工具来签发证书,如果未安装JDK,请先安装JDK(本文使用的是JDK8)。本文所有的证书文件都放到/cert/test1(操作系统centos),您可以选择一个目录来存放。

        1.制作服务端密钥库

  1. keytool -genkey -v -alias server -keyalg RSA
  2. -keystore /cert/test1/server.keystore -validity 36500
  3. -ext SAN=dns:test-ssl,ip:10.1.x.x
  4. -dname "CN=test,OU=test,O=test,L=hz,ST=hz,C=cn"

 注意:SAN填写的是域名,IP填写是服务端IP。SAN和IP是解决谷歌浏览器证书无效的关键。

        2.制作客户端密钥库

  1. keytool -genkey -v -alias client -keyalg RSA -storetype PKCS12
  2. -keystore /cert/test1/client.p12 -dname "CN=test,OU=test,O=test,L=hz,ST=hz,C=cn"

        3.客户端证书导入服务端密钥库

        由于不能直接将p12导入,需要先从客户端密钥库导出证书,再将导出的证书导入服务端密钥库。   

  1. keytool -export -alias client -keystore /cert/test1/client.p12
  2. -storetype PKCS12 -storepass 123456 -rfc -file /cert/test1/client.cer

keytool -import -v -file /cert/test1/client.cer -keystore /cert/test1/server.keystore

        4.导出服务端密钥库证书 

keytool -keystore /cert/test1/server.keystore -export -alias server -file /cert/test1/server.cer

        5.配置tomcat 

        5.1配置server.xml

        找到conf目录下的server.xml文件,增加如下配置。

  1. <Connector port="8443" protocol="org.apache.coyote.http11.Http11NioProtocol"
  2. SSLEnabled="true"maxThreads="150" scheme="https" secure="true"
  3. clientAuth="true" sslProtocol="TLS"
  4. keystoreFile="/cert/test1/server.keystore" keystorePass="123456"
  5. truststoreFile ="/cert/test1/server.keystore" truststorePass="123456"
  6. />

        说明:
  • clientAuth为true表示开启SSL双向认证
  • keystoreFile指定服务器端的证书位置
  •  truststoreFile指定服务器端信任证书库
         5.2配置web.xml

        找到conf目录下的server.xml文件,增加如下配置。

  1. <security-constraint>
  2. <web-resource-collection>
  3. <web-resource-name>SSL</web-resource-name>
  4. <url-pattern>/ssl_test/*</url-pattern>
  5. </web-resource-collection>
  6. <user-data-constraint>
  7. <description>SSL required</description>
  8. <transport-guarantee>CONFIDENTIAL</transport-guarantee>
  9. </user-data-constraint>
  10. </security-constraint>

        说明:
  • 如果不加入这个配置,那么所有访问的地址都必须要使用SSL才能访问,有时我们可能只需要通过某个或者某些SSL地址获取客户端证书来认证用户身份,认证成功后不需要使用SSL来进行访问(可以配置多个security-constraint)
  • url-pattern:指定需要SSL才能进行访问的地址(/ssl_test/*)
  • transport-guarantee:合法值为NONE、 INTEGRAL或CONFIDENTIAL,transport-guarantee为NONE值将对所用的通讯协议不加限制。INTEGRAL值表示数据必须以一种防止截取它的人阅读它的方式传送。虽然原理上(并且在未来的HTTP版本中),在 INTEGRAL和CONFIDENTIAL之间可能会有差别,但在当前实践中,他们都只是简单地要求用SSL
  • 创建SSLServlet获取客户端证书

        6.编写用来获取客户端证书的filter及测试接口类

        客户端证书验证拦截器(拦截路径:/ssl_test/*)
  1. package com.example.demo;
  2. import java.io.IOException;
  3. import java.security.cert.X509Certificate;
  4. import javax.servlet.Filter;
  5. import javax.servlet.FilterChain;
  6. import javax.servlet.FilterConfig;
  7. import javax.servlet.ServletException;
  8. import javax.servlet.ServletRequest;
  9. import javax.servlet.ServletResponse;
  10. import javax.servlet.annotation.WebFilter;
  11. /**
  12. * description:MyFilter
  13. *
  14. * @author: lgq
  15. * @create: 2024-02-02 10:55
  16. */
  17. @WebFilter(urlPatterns = "/ssl_test/*")
  18. public class MyFilter implements Filter {
  19. private static final String REQUEST_ATTR_CERT = "javax.servlet.request.X509Certificate";
  20. private static final String SCHEME_HTTPS = "https";
  21. /**
  22. * web应用启动时,web服务器将创建Filter的实例对象,并调用init方法,读取web.xml的配置,完成对象的初始化功能,
  23. * 从而为后续的用户请求做好拦截的准备工作(filter对象只会创建一次,init方法也只会执行一次,开发人员通过init的参数,
  24. * 可或得代表当前filter配置信息的FilterConfig对象)
  25. * @param filterConfig
  26. * @throws ServletException
  27. */
  28. @Override
  29. public void init(FilterConfig filterConfig) throws ServletException {
  30. }
  31. /**
  32. * 这个方法完成实际的过滤操作,当客户请求访问与过滤器相关联的URL的时候,Servlet过滤器将先执行doFilter方法,FilterChain参数用于访问后续过滤器
  33. * @param request
  34. * @param response
  35. * @param filterChain
  36. * @throws IOException
  37. * @throws ServletException
  38. */
  39. @Override
  40. public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
  41. throws IOException, ServletException {
  42. X509Certificate[] certs = (X509Certificate[]) request.getAttribute(REQUEST_ATTR_CERT);
  43. if (certs != null) {
  44. int count = certs.length;
  45. System.out.println("共检测到[" + count + "]个客户端证书");
  46. for (int i = 0; i < count; i++) {
  47. X509Certificate cert = certs[i];
  48. System.out.println("客户端证书 [" + cert.getSubjectDN() + "]: ");
  49. System.out.println("证书是否有效:" + (verifyCertificate(cert) ? "是" : "否"));
  50. System.out.println("证书详细信息:\r" + cert.toString());
  51. }
  52. filterChain.doFilter(request, response);
  53. } else {
  54. if (SCHEME_HTTPS.equalsIgnoreCase(request.getScheme())) {
  55. System.out.println("这是一个HTTPS请求,但是没有可用的客户端证书");
  56. } else {
  57. System.out.println("这不是一个HTTPS请求,因此无法获得客户端证书列表 ");
  58. }
  59. }
  60. System.out.println("我是过滤器,我进来了");
  61. }
  62. /**
  63. * filter创建后会保存在内存中,当web应用移除或者服务器停止时才销毁,该方法在Filter的生命周期中仅执行一次,在这个方法中,可以释放过滤器使用的资源
  64. */
  65. @Override
  66. public void destroy() {
  67. }
  68. /**
  69. *
  70. * 校验证书是否过期
  71. *
  72. *
  73. * @param certificate
  74. * @return
  75. */
  76. private boolean verifyCertificate(X509Certificate certificate) {
  77. boolean valid = true;
  78. try {
  79. certificate.checkValidity();
  80. } catch (Exception e) {
  81. e.printStackTrace();
  82. valid = false;
  83. }
  84. return valid;
  85. }
  86. }
         启动类(服务部署到tomcat)
  1. package com.example.demo;
  2. import org.springframework.boot.SpringApplication;
  3. import org.springframework.boot.autoconfigure.SpringBootApplication;
  4. import org.springframework.boot.builder.SpringApplicationBuilder;
  5. import org.springframework.boot.web.servlet.ServletComponentScan;
  6. import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
  7. @SpringBootApplication
  8. @ServletComponentScan
  9. public class DemoApplication extends SpringBootServletInitializer {
  10. public static void main(String[] args) {
  11. SpringApplication.run(DemoApplication.class, args);
  12. }
  13. @Override
  14. protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
  15. return builder.sources(DemoApplication.class);
  16. }
  17. }
        pom依赖(打war包)
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  3. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  4. <modelVersion>4.0.0</modelVersion>
  5. <parent>
  6. <groupId>org.springframework.boot</groupId>
  7. <artifactId>spring-boot-starter-parent</artifactId>
  8. <version>2.4.3</version>
  9. <relativePath/> <!-- lookup parent from repository -->
  10. </parent>
  11. <groupId>com.example</groupId>
  12. <artifactId>demo</artifactId>
  13. <version>0.0.1-SNAPSHOT</version>
  14. <name>demo</name>
  15. <description>Demo project for Spring Boot</description>
  16. <packaging>war</packaging>
  17. <properties>
  18. <java.version>8</java.version>
  19. </properties>
  20. <dependencies>
  21. <dependency>
  22. <groupId>org.springframework.boot</groupId>
  23. <artifactId>spring-boot-starter-web</artifactId>
  24. <exclusions>
  25. <exclusion>
  26. <groupId>org.springframework.boot</groupId>
  27. <artifactId>spring-boot-starter-tomcat</artifactId>
  28. </exclusion>
  29. </exclusions>
  30. </dependency>
  31. <!--spring boot tomcat(默认可以不用配置,但当需要把当前web应用布置到外部servlet容器时就需要配置,并将scope配置为provided)-->
  32. <dependency>
  33. <groupId>org.springframework.boot</groupId>
  34. <artifactId>spring-boot-starter-tomcat</artifactId>
  35. <scope>provided</scope>
  36. </dependency>
  37. </dependencies>
  38. <build>
  39. <finalName>test</finalName>
  40. <plugins>
  41. <plugin>
  42. <groupId>org.apache.maven.plugins</groupId>
  43. <artifactId>maven-war-plugin</artifactId>
  44. <version>2.1.1</version>
  45. <configuration>
  46. <failOnMissingWebXml>false</failOnMissingWebXml>
  47. </configuration>
  48. </plugin>
  49. </plugins>
  50. </build>
  51. </project>
        tomcat下服务目录(工程路径/test)

        启动服务命令

        客户端ssl证书认证接口
  1. package com.example.demo;
  2. import org.springframework.web.bind.annotation.GetMapping;
  3. import org.springframework.web.bind.annotation.RequestMapping;
  4. import org.springframework.web.bind.annotation.RestController;
  5. /**
  6. * description:SSLTestController
  7. *
  8. * @author: lgq
  9. * @create: 2024-01-25 10:42
  10. */
  11. @RestController
  12. @RequestMapping("/ssl_test")
  13. public class SSLTestController {
  14. @GetMapping("/hello")
  15. public String auth() {
  16. return "Hello, I am the server! Your client's SSL certificate has been authenticated!";
  17. }
  18. }
        不需要认证客户端证书的接口
  1. package com.example.demo;
  2. import org.springframework.web.bind.annotation.GetMapping;
  3. import org.springframework.web.bind.annotation.RequestMapping;
  4. import org.springframework.web.bind.annotation.RestController;
  5. /**
  6. * description:NoSSLTestController
  7. *
  8. * @author: lgq
  9. * @create: 2024-01-25 10:42
  10. */
  11. @RestController
  12. @RequestMapping("/no_ssl_test")
  13. public class NoSSLTestController {
  14. @GetMapping("/hello")
  15. public String auth() {
  16. return "Hello, I am the server!";
  17. }
  18. }

        7.测试

        7.1 浏览器访问测试
        7.1.1ssl双向认证测试

        用浏览器访问http://10.1.x.x:8080/test/ssl_test/hello        

        细心的读者可能发现链接已经跳转到了  https://10.1.x.x:8443/test/ssl_test/hello,这是由于这个地址被设置为需要SSL才能访问,所以跳转到了这个地址。访问时页面提示如下:

        为了不出现这样的警告信息,我们可以导入服务器端证书到客户端,双击服务端证书

        选择当前用户 

        将证书放入可信任的根证书列表 ,随后安装成功

        再次访问: http://10.1.x.x:8080/test/ssl_test/hello,出现如下错误

        由于我们访问的接口是双向认证,所以也需要客户端的证书,我们接下来导入客户端证书

        自动选择证书存储 

        输入证书密钥,随即安装成功 

        第三次访问: http://10.1.x.x:8080/test/ssl_test/hello,结果如下所示

        需要选择客户端证书

        输出结果如下 

        tomcat 日志如下,(证书是否有效:是)表示客户端证书已经通过服务端验证

        7.1.2 不验证客户端证书

        访问地址http://10.1.x.x:8080/test/no_ssl_test/hello​​​​​​, 发现没有跳转到8443端口,正常返回内容如下

        7.2 测试java通过httpclient调用双向认证接口 
        1.增加apache httpclient依赖
  1. <!-- https://mvnrepository.com/artifact/org.apache.httpcomponents/httpcore -->
  2. <dependency>
  3. <groupId>org.apache.httpcomponents</groupId>
  4. <artifactId>httpcore</artifactId>
  5. <version>4.4.10</version>
  6. </dependency>
  7. <!-- https://mvnrepository.com/artifact/org.apache.httpcomponents/httpclient -->
  8. <dependency>
  9. <groupId>org.apache.httpcomponents</groupId>
  10. <artifactId>httpclient</artifactId>
  11. <version>4.5.6</version>
  12. </dependency>
        2.构建http请求类
  1. package com.example.demo;
  2. /**
  3. * description:HttpsRequest
  4. *
  5. * @author: lgq
  6. * @create: 2024-02-04 18:17
  7. */
  8. import java.io.File;
  9. import java.io.FileInputStream;
  10. import java.io.IOException;
  11. import java.security.KeyManagementException;
  12. import java.security.KeyStore;
  13. import java.security.KeyStoreException;
  14. import java.security.NoSuchAlgorithmException;
  15. import java.security.UnrecoverableKeyException;
  16. import java.security.cert.CertificateException;
  17. import java.util.HashMap;
  18. import java.util.Map;
  19. import javax.net.ssl.SSLContext;
  20. import org.apache.http.HttpEntity;
  21. import org.apache.http.HttpHost;
  22. import org.apache.http.client.config.RequestConfig;
  23. import org.apache.http.client.methods.CloseableHttpResponse;
  24. import org.apache.http.client.methods.HttpGet;
  25. import org.apache.http.client.methods.HttpPost;
  26. import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
  27. import org.apache.http.conn.ssl.TrustSelfSignedStrategy;
  28. import org.apache.http.entity.StringEntity;
  29. import org.apache.http.impl.client.CloseableHttpClient;
  30. import org.apache.http.impl.client.HttpClients;
  31. import org.apache.http.ssl.SSLContexts;
  32. import org.apache.http.util.EntityUtils;
  33. import org.springframework.util.ObjectUtils;
  34. import org.springframework.util.StringUtils;
  35. public class HttpsRequest {
  36. //.p12文件路径
  37. private String filePath;
  38. //密码
  39. private String passWord;
  40. //外呼url
  41. private String url;
  42. // 请求体
  43. private String body;
  44. //请求头
  45. private Map<String, String> header;
  46. //代理IP
  47. private String proxyIP;
  48. //代理端口
  49. private int proxyPort;
  50. private HttpsRequest(Builder builder) {
  51. this.filePath = builder.filePath;
  52. this.passWord = builder.passWord;
  53. this.url = builder.url;
  54. this.body = builder.body;
  55. this.header = builder.header;
  56. this.proxyIP = builder.proxyIP;
  57. this.proxyPort = builder.proxyPort;
  58. }
  59. public static class Builder {
  60. //.p12文件路径
  61. private String filePath;
  62. //密码
  63. private String passWord;
  64. //外呼url
  65. private String url;
  66. // 请求体
  67. private String body;
  68. //请求头
  69. private Map<String, String> header = new HashMap<>();
  70. //代理IP
  71. private String proxyIP;
  72. //代理端口
  73. private int proxyPort;
  74. public Builder filePath(String filePath) {
  75. this.filePath = filePath;
  76. return this;
  77. }
  78. public Builder passWord(String passWord) {
  79. this.passWord = passWord;
  80. return this;
  81. }
  82. public Builder url(String url) {
  83. this.url = url;
  84. return this;
  85. }
  86. public Builder body(String body) {
  87. this.body = body;
  88. return this;
  89. }
  90. public Builder header(String key, String value) {
  91. this.header.put(key, value);
  92. return this;
  93. }
  94. public Builder proxy(String ip, int port) {
  95. this.proxyPort = port;
  96. this.proxyIP = ip;
  97. return this;
  98. }
  99. public HttpsRequest build() {
  100. return new HttpsRequest(this);
  101. }
  102. }
  103. public String doPost() {
  104. String rep = "";
  105. SSLContext sslcontext;
  106. try {
  107. KeyStore keyStore = KeyStore.getInstance("PKCS12");
  108. try (FileInputStream fileInputStream = new FileInputStream(filePath)) {
  109. keyStore.load(fileInputStream, passWord.toCharArray());
  110. sslcontext = SSLContexts.custom()
  111. //忽略掉对服务器端证书的校验
  112. //.loadTrustMaterial((TrustStrategy) (chain, authType) -> true)
  113. //加载服务端提供的truststore(如果服务器提供truststore的话就不用忽略对服务器端证书的校验了)
  114. .loadTrustMaterial(new File("E:\\abc\\def\\server.jks"), "123456".toCharArray(),
  115. new TrustSelfSignedStrategy())
  116. .loadKeyMaterial(keyStore, passWord.toCharArray())
  117. .build();
  118. }
  119. SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(
  120. sslcontext,
  121. new String[]{"TLSv1"},
  122. null,
  123. SSLConnectionSocketFactory.getDefaultHostnameVerifier());
  124. try (CloseableHttpClient client = HttpClients.custom().setSSLSocketFactory(sslConnectionSocketFactory).build()) {
  125. HttpPost httpPost = new HttpPost(url);
  126. StringEntity req = new StringEntity(body, "UTF-8");
  127. httpPost.setEntity(req);
  128. if (header != null) {
  129. header.entrySet().stream().forEach((h) -> {
  130. httpPost.addHeader(h.getKey(), h.getValue());
  131. });
  132. }
  133. RequestConfig config;
  134. if (!StringUtils.isEmpty(proxyIP) && !StringUtils.isEmpty(proxyPort)) {
  135. HttpHost proxy = new HttpHost(proxyIP, proxyPort);
  136. config = RequestConfig.custom().setProxy(proxy).setConnectionRequestTimeout(5000)
  137. .setSocketTimeout(30000).setConnectTimeout(20000).build();
  138. } else {
  139. config = RequestConfig.custom().setConnectionRequestTimeout(5000)
  140. .setSocketTimeout(30000).setConnectTimeout(20000).build();
  141. }
  142. //连接超时时间, 单位毫秒
  143. //requestConfigBuilder.setConnectTimeout(2000);
  144. //从池中获取连接超时时间
  145. //requestConfigBuilder.setConnectionRequestTimeout(500);
  146. //读超时时间(等待数据超时时间)
  147. //requestConfigBuilder.setSocketTimeout(2000);
  148. httpPost.setConfig(config);
  149. try (CloseableHttpResponse httpResponse = client.execute(httpPost)) {
  150. HttpEntity entity = httpResponse.getEntity();
  151. rep = EntityUtils.toString(entity);
  152. }
  153. }
  154. } catch (KeyStoreException e) {
  155. e.printStackTrace();
  156. } catch (IOException e) {
  157. e.printStackTrace();
  158. } catch (NoSuchAlgorithmException e) {
  159. e.printStackTrace();
  160. } catch (CertificateException e) {
  161. e.printStackTrace();
  162. } catch (KeyManagementException e) {
  163. e.printStackTrace();
  164. } catch (UnrecoverableKeyException e) {
  165. e.printStackTrace();
  166. }
  167. return rep;
  168. }
  169. public String doGet() {
  170. String rep = "";
  171. SSLContext sslcontext;
  172. try {
  173. KeyStore keyStore = KeyStore.getInstance("PKCS12");
  174. try (FileInputStream fileInputStream = new FileInputStream(filePath)) {
  175. keyStore.load(fileInputStream, passWord.toCharArray());
  176. sslcontext = SSLContexts.custom()
  177. //忽略掉对服务器端证书的校验
  178. //.loadTrustMaterial((TrustStrategy) (chain, authType) -> true)
  179. //加载服务端提供的truststore(如果服务器提供truststore的话就不用忽略对服务器端证书的校验了)
  180. .loadTrustMaterial(new File("E:\\abc\\def\\server.jks"), "123456".toCharArray(),
  181. new TrustSelfSignedStrategy())
  182. .loadKeyMaterial(keyStore, passWord.toCharArray())
  183. .build();
  184. }
  185. SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(
  186. sslcontext,
  187. new String[]{"TLSv1.2"},
  188. null,
  189. SSLConnectionSocketFactory.getDefaultHostnameVerifier());
  190. try (CloseableHttpClient client = HttpClients.custom().setSSLSocketFactory(sslConnectionSocketFactory).build()) {
  191. HttpGet httpGet = new HttpGet(url);
  192. if (!ObjectUtils.isEmpty(header)) {
  193. header.entrySet().stream().forEach((h) -> {
  194. httpGet.addHeader(h.getKey(), h.getValue());
  195. });
  196. }
  197. RequestConfig config;
  198. if (!ObjectUtils.isEmpty(proxyIP) && !ObjectUtils.isEmpty(proxyPort)) {
  199. HttpHost proxy = new HttpHost(proxyIP, proxyPort);
  200. config = RequestConfig.custom().setProxy(proxy).setConnectionRequestTimeout(5000)
  201. .setSocketTimeout(30000).setConnectTimeout(20000).build();
  202. } else {
  203. config = RequestConfig.custom().setConnectionRequestTimeout(5000)
  204. .setSocketTimeout(30000).setConnectTimeout(20000).build();
  205. }
  206. //连接超时时间, 单位毫秒
  207. //requestConfigBuilder.setConnectTimeout(2000);
  208. //从池中获取连接超时时间
  209. //requestConfigBuilder.setConnectionRequestTimeout(500);
  210. //读超时时间(等待数据超时时间)
  211. //requestConfigBuilder.setSocketTimeout(2000);
  212. httpGet.setConfig(config);
  213. try (CloseableHttpResponse httpResponse = client.execute(httpGet)) {
  214. HttpEntity entity = httpResponse.getEntity();
  215. rep = EntityUtils.toString(entity);
  216. }
  217. }
  218. } catch (KeyStoreException e) {
  219. e.printStackTrace();
  220. } catch (IOException e) {
  221. e.printStackTrace();
  222. } catch (NoSuchAlgorithmException e) {
  223. e.printStackTrace();
  224. } catch (CertificateException e) {
  225. e.printStackTrace();
  226. } catch (KeyManagementException e) {
  227. e.printStackTrace();
  228. } catch (UnrecoverableKeyException e) {
  229. e.printStackTrace();
  230. }
  231. return rep;
  232. }
  233. }
3.将服务端证书由 cer格式转为jks
keytool -import -alias server -file /cert/test1/server.cer -keystore /cert/test1/server.jks
 4.测试双向认证请求
  1. package com.example.demo;
  2. /**
  3. * description:HttpsRequestTest
  4. *
  5. * @author: lgq
  6. * @create: 2024-02-04 18:24
  7. */
  8. public class HttpsRequestTest {
  9. public static void main(String[] args) {
  10. String result = new HttpsRequest.Builder()
  11. .filePath("E:\\abc\\def\\client.p12")
  12. .passWord("123456")
  13. .url("https://10.1.x.x:8443/test/ssl_test/hello")
  14. .header("charset", "UTF-8")//头信息,多个头信息多次调用此方法即可
  15. .build()
  16. .doGet();
  17. System.out.println(result);
  18. }
  19. }

        输出结果

  1. "C:\Program Files\Java\jdk1.8.0_101\bin\java.exe" -Dvisualvm.id=375771477320700 "-javaagent:E:\software-tools\JetBrains\IntelliJ IDEA 2020.3.3\lib\idea_rt.jar=55001:E:\software-tools\JetBrains\IntelliJ IDEA 2020.3.3\bin" -Dfile.encoding=UTF-8 -classpath "C:\Program Files\Java\jdk1.8.0_101\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_101\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_101\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_101\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_101\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_101\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_101\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_101\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_101\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_101\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_101\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_101\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_101\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_101\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_101\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_101\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_101\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_101\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_101\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_101\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_101\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_101\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_101\jre\lib\rt.jar;D:\project\demo1\target\classes;D:\maven\repository\org\springframework\boot\spring-boot-starter-web\2.4.3\spring-boot-starter-web-2.4.3.jar;D:\maven\repository\org\springframework\boot\spring-boot-starter\2.4.3\spring-boot-starter-2.4.3.jar;D:\maven\repository\org\springframework\boot\spring-boot\2.4.3\spring-boot-2.4.3.jar;D:\maven\repository\org\springframework\boot\spring-boot-autoconfigure\2.4.3\spring-boot-autoconfigure-2.4.3.jar;D:\maven\repository\org\springframework\boot\spring-boot-starter-logging\2.4.3\spring-boot-starter-logging-2.4.3.jar;D:\maven\repository\ch\qos\logback\logback-classic\1.2.3\logback-classic-1.2.3.jar;D:\maven\repository\ch\qos\logback\logback-core\1.2.3\logback-core-1.2.3.jar;D:\maven\repository\org\slf4j\slf4j-api\1.7.30\slf4j-api-1.7.30.jar;D:\maven\repository\org\apache\logging\log4j\log4j-to-slf4j\2.13.3\log4j-to-slf4j-2.13.3.jar;D:\maven\repository\org\apache\logging\log4j\log4j-api\2.13.3\log4j-api-2.13.3.jar;D:\maven\repository\org\slf4j\jul-to-slf4j\1.7.30\jul-to-slf4j-1.7.30.jar;D:\maven\repository\org\springframework\spring-core\5.3.4\spring-core-5.3.4.jar;D:\maven\repository\org\springframework\spring-jcl\5.3.4\spring-jcl-5.3.4.jar;D:\maven\repository\org\yaml\snakeyaml\1.27\snakeyaml-1.27.jar;D:\maven\repository\org\springframework\boot\spring-boot-starter-json\2.4.3\spring-boot-starter-json-2.4.3.jar;D:\maven\repository\com\fasterxml\jackson\core\jackson-databind\2.11.4\jackson-databind-2.11.4.jar;D:\maven\repository\com\fasterxml\jackson\core\jackson-annotations\2.11.4\jackson-annotations-2.11.4.jar;D:\maven\repository\com\fasterxml\jackson\core\jackson-core\2.11.4\jackson-core-2.11.4.jar;D:\maven\repository\com\fasterxml\jackson\datatype\jackson-datatype-jdk8\2.11.4\jackson-datatype-jdk8-2.11.4.jar;D:\maven\repository\com\fasterxml\jackson\datatype\jackson-datatype-jsr310\2.11.4\jackson-datatype-jsr310-2.11.4.jar;D:\maven\repository\com\fasterxml\jackson\module\jackson-module-parameter-names\2.11.4\jackson-module-parameter-names-2.11.4.jar;D:\maven\repository\org\springframework\spring-web\5.3.4\spring-web-5.3.4.jar;D:\maven\repository\org\springframework\spring-beans\5.3.4\spring-beans-5.3.4.jar;D:\maven\repository\org\springframework\spring-webmvc\5.3.4\spring-webmvc-5.3.4.jar;D:\maven\repository\org\springframework\spring-aop\5.3.4\spring-aop-5.3.4.jar;D:\maven\repository\org\springframework\spring-context\5.3.4\spring-context-5.3.4.jar;D:\maven\repository\org\springframework\spring-expression\5.3.4\spring-expression-5.3.4.jar;D:\maven\repository\jakarta\annotation\jakarta.annotation-api\1.3.5\jakarta.annotation-api-1.3.5.jar;D:\maven\repository\org\apache\httpcomponents\httpcore\4.4.10\httpcore-4.4.10.jar;D:\maven\repository\org\apache\httpcomponents\httpclient\4.5.6\httpclient-4.5.6.jar;D:\maven\repository\commons-codec\commons-codec\1.15\commons-codec-1.15.jar" com.example.demo.HttpsRequestTest
  2. 18:55:06.389 [main] DEBUG org.apache.http.client.protocol.RequestAddCookies - CookieSpec selected: default
  3. 18:55:06.411 [main] DEBUG org.apache.http.client.protocol.RequestAuthCache - Auth cache not set in the context
  4. 18:55:06.413 [main] DEBUG org.apache.http.impl.conn.PoolingHttpClientConnectionManager - Connection request: [route: {s}->https://10.1.x.x:8443][total kept alive: 0; route allocated: 0 of 2; total allocated: 0 of 20]
  5. 18:55:06.429 [main] DEBUG org.apache.http.impl.conn.PoolingHttpClientConnectionManager - Connection leased: [id: 0][route: {s}->https://10.1.x.x:8443][total kept alive: 0; route allocated: 1 of 2; total allocated: 1 of 20]
  6. 18:55:06.431 [main] DEBUG org.apache.http.impl.execchain.MainClientExec - Opening connection {s}->https://10.1.x.x:8443
  7. 18:55:06.434 [main] DEBUG org.apache.http.impl.conn.DefaultHttpClientConnectionOperator - Connecting to /10.1.x.x:8443
  8. 18:55:06.434 [main] DEBUG org.apache.http.conn.ssl.SSLConnectionSocketFactory - Connecting socket to /10.1.x.x:8443 with timeout 20000
  9. 18:55:06.491 [main] DEBUG org.apache.http.conn.ssl.SSLConnectionSocketFactory - Enabled protocols: [TLSv1.2]
  10. 18:55:06.491 [main] DEBUG org.apache.http.conn.ssl.SSLConnectionSocketFactory - Enabled cipher suites:[TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, TLS_RSA_WITH_AES_128_CBC_SHA256, TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256, TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256, TLS_DHE_RSA_WITH_AES_128_CBC_SHA256, TLS_DHE_DSS_WITH_AES_128_CBC_SHA256, TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, TLS_RSA_WITH_AES_128_CBC_SHA, TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA, TLS_ECDH_RSA_WITH_AES_128_CBC_SHA, TLS_DHE_RSA_WITH_AES_128_CBC_SHA, TLS_DHE_DSS_WITH_AES_128_CBC_SHA, TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, TLS_RSA_WITH_AES_128_GCM_SHA256, TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256, TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256, TLS_DHE_RSA_WITH_AES_128_GCM_SHA256, TLS_DHE_DSS_WITH_AES_128_GCM_SHA256, TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA, TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, SSL_RSA_WITH_3DES_EDE_CBC_SHA, TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA, TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA, SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA, SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA, TLS_EMPTY_RENEGOTIATION_INFO_SCSV]
  11. 18:55:06.491 [main] DEBUG org.apache.http.conn.ssl.SSLConnectionSocketFactory - Starting handshake
  12. 18:55:06.598 [main] DEBUG org.apache.http.conn.ssl.SSLConnectionSocketFactory - Secure session established
  13. 18:55:06.598 [main] DEBUG org.apache.http.conn.ssl.SSLConnectionSocketFactory - negotiated protocol: TLSv1.2
  14. 18:55:06.598 [main] DEBUG org.apache.http.conn.ssl.SSLConnectionSocketFactory - negotiated cipher suite: TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256
  15. 18:55:06.598 [main] DEBUG org.apache.http.conn.ssl.SSLConnectionSocketFactory - peer principal: CN=test, OU=test, O=test, L=hz, ST=hz, C=cn
  16. 18:55:06.599 [main] DEBUG org.apache.http.conn.ssl.SSLConnectionSocketFactory - peer alternative names: [test-ssl, 10.1.x.x]
  17. 18:55:06.599 [main] DEBUG org.apache.http.conn.ssl.SSLConnectionSocketFactory - issuer principal: CN=test, OU=test, O=test, L=hz, ST=hz, C=cn
  18. 18:55:06.605 [main] DEBUG org.apache.http.impl.conn.DefaultHttpClientConnectionOperator - Connection established 10.26.54.125:55008<->10.1.x.x:8443
  19. 18:55:06.605 [main] DEBUG org.apache.http.impl.conn.DefaultManagedHttpClientConnection - http-outgoing-0: set socket timeout to 30000
  20. 18:55:06.606 [main] DEBUG org.apache.http.impl.execchain.MainClientExec - Executing request GET /test/ssl_test/hello HTTP/1.1
  21. 18:55:06.606 [main] DEBUG org.apache.http.impl.execchain.MainClientExec - Target auth state: UNCHALLENGED
  22. 18:55:06.607 [main] DEBUG org.apache.http.impl.execchain.MainClientExec - Proxy auth state: UNCHALLENGED
  23. 18:55:06.609 [main] DEBUG org.apache.http.headers - http-outgoing-0 >> GET /test/ssl_test/hello HTTP/1.1
  24. 18:55:06.609 [main] DEBUG org.apache.http.headers - http-outgoing-0 >> charset: UTF-8
  25. 18:55:06.609 [main] DEBUG org.apache.http.headers - http-outgoing-0 >> Host: 10.1.x.x:8443
  26. 18:55:06.610 [main] DEBUG org.apache.http.headers - http-outgoing-0 >> Connection: Keep-Alive
  27. 18:55:06.610 [main] DEBUG org.apache.http.headers - http-outgoing-0 >> User-Agent: Apache-HttpClient/4.5.6 (Java/1.8.0_101)
  28. 18:55:06.610 [main] DEBUG org.apache.http.headers - http-outgoing-0 >> Accept-Encoding: gzip,deflate
  29. 18:55:06.610 [main] DEBUG org.apache.http.wire - http-outgoing-0 >> "GET /test/ssl_test/hello HTTP/1.1[\r][\n]"
  30. 18:55:06.610 [main] DEBUG org.apache.http.wire - http-outgoing-0 >> "charset: UTF-8[\r][\n]"
  31. 18:55:06.610 [main] DEBUG org.apache.http.wire - http-outgoing-0 >> "Host: 10.1.x.x:8443[\r][\n]"
  32. 18:55:06.610 [main] DEBUG org.apache.http.wire - http-outgoing-0 >> "Connection: Keep-Alive[\r][\n]"
  33. 18:55:06.610 [main] DEBUG org.apache.http.wire - http-outgoing-0 >> "User-Agent: Apache-HttpClient/4.5.6 (Java/1.8.0_101)[\r][\n]"
  34. 18:55:06.610 [main] DEBUG org.apache.http.wire - http-outgoing-0 >> "Accept-Encoding: gzip,deflate[\r][\n]"
  35. 18:55:06.610 [main] DEBUG org.apache.http.wire - http-outgoing-0 >> "[\r][\n]"
  36. 18:55:06.619 [main] DEBUG org.apache.http.wire - http-outgoing-0 << "HTTP/1.1 200 [\r][\n]"
  37. 18:55:06.619 [main] DEBUG org.apache.http.wire - http-outgoing-0 << "Cache-Control: private[\r][\n]"
  38. 18:55:06.619 [main] DEBUG org.apache.http.wire - http-outgoing-0 << "Content-Type: text/plain;charset=UTF-8[\r][\n]"
  39. 18:55:06.619 [main] DEBUG org.apache.http.wire - http-outgoing-0 << "Content-Length: 77[\r][\n]"
  40. 18:55:06.619 [main] DEBUG org.apache.http.wire - http-outgoing-0 << "Date: Sun, 04 Feb 2024 10:55:07 GMT[\r][\n]"
  41. 18:55:06.619 [main] DEBUG org.apache.http.wire - http-outgoing-0 << "Keep-Alive: timeout=60[\r][\n]"
  42. 18:55:06.619 [main] DEBUG org.apache.http.wire - http-outgoing-0 << "Connection: keep-alive[\r][\n]"
  43. 18:55:06.619 [main] DEBUG org.apache.http.wire - http-outgoing-0 << "[\r][\n]"
  44. 18:55:06.619 [main] DEBUG org.apache.http.wire - http-outgoing-0 << "Hello, I am the server! Your client's SSL certificate has been authenticated!"
  45. 18:55:06.624 [main] DEBUG org.apache.http.headers - http-outgoing-0 << HTTP/1.1 200
  46. 18:55:06.624 [main] DEBUG org.apache.http.headers - http-outgoing-0 << Cache-Control: private
  47. 18:55:06.624 [main] DEBUG org.apache.http.headers - http-outgoing-0 << Content-Type: text/plain;charset=UTF-8
  48. 18:55:06.624 [main] DEBUG org.apache.http.headers - http-outgoing-0 << Content-Length: 77
  49. 18:55:06.624 [main] DEBUG org.apache.http.headers - http-outgoing-0 << Date: Sun, 04 Feb 2024 10:55:07 GMT
  50. 18:55:06.624 [main] DEBUG org.apache.http.headers - http-outgoing-0 << Keep-Alive: timeout=60
  51. 18:55:06.624 [main] DEBUG org.apache.http.headers - http-outgoing-0 << Connection: keep-alive
  52. 18:55:06.632 [main] DEBUG org.apache.http.impl.execchain.MainClientExec - Connection can be kept alive for 60000 MILLISECONDS
  53. 18:55:06.640 [main] DEBUG org.apache.http.impl.conn.PoolingHttpClientConnectionManager - Connection [id: 0][route: {s}->https://10.1.x.x:8443][state: CN=test, OU=test, O=test, L=hz, ST=hz, C=cn] can be kept alive for 60.0 seconds
  54. 18:55:06.640 [main] DEBUG org.apache.http.impl.conn.DefaultManagedHttpClientConnection - http-outgoing-0: set socket timeout to 0
  55. 18:55:06.640 [main] DEBUG org.apache.http.impl.conn.PoolingHttpClientConnectionManager - Connection released: [id: 0][route: {s}->https://10.1.x.x:8443][state: CN=test, OU=test, O=test, L=hz, ST=hz, C=cn][total kept alive: 1; route allocated: 1 of 2; total allocated: 1 of 20]
  56. 18:55:06.641 [main] DEBUG org.apache.http.impl.conn.PoolingHttpClientConnectionManager - Connection manager is shutting down
  57. 18:55:06.641 [main] DEBUG org.apache.http.impl.conn.DefaultManagedHttpClientConnection - http-outgoing-0: Close connection
  58. 18:55:06.642 [main] DEBUG org.apache.http.impl.conn.PoolingHttpClientConnectionManager - Connection manager shut down
  59. Hello, I am the server! Your client's SSL certificate has been authenticated!
  60. Process finished with exit code 0

        其中,输出内容:“Hello, I am the server! Your client's SSL certificate has been authenticated!”,表示客户端证书认证已经通过。需要注意的是,上面方法中,客户端证书使用的是p12格式,服务端证书使用jks格式。

其他

        文中的例子只是demo,距离部署到生产还有距离,如果部署生产可以使用openssl生成证书,还需要考虑单点故障的问题,有以下两种方案:

1.需要实现客户端负载均衡,或者是服务注册和发现,最简单的方式就是发起https请求前轮询测试服务ip:port是否可用,第1不通就访问第2个;

2.基于nginx实现ssl双向认证,但是要通过其他方式比如网络限制等保证nginx到真实服务的访问是安全的。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/在线问答5/article/detail/1013942
推荐阅读
相关标签
  

闽ICP备14008679号