Hakky54/mutual-tls-ssl
GitHub: Hakky54/mutual-tls-ssl
一份全面的 TLS/SSL 双向认证教程项目,提供从证书生成到 40 余种 JVM 语言 HTTP 客户端 SSL 配置的完整示例与自动化脚本。
Stars: 634 | Forks: 127
[](https://github.com/Hakky54/mutual-tls-ssl/actions)
[](https://sonarcloud.io/dashboard?id=nl.altindag%3Amutual-tls-ssl)
[](https://sonarcloud.io/dashboard?id=nl.altindag%3Amutual-tls-ssl)
[](https://sonarcloud.io/dashboard?id=nl.altindag%3Amutual-tls-ssl)
[](#)
[](https://github.com/Hakky54/mutual-tls-ssl/blob/master/LICENSE)
[](https://seladb.github.io/StarTrack-js/#/preload?r=hakky54,mutual-tls-ssl)
[](https://gitter.im/hakky54/mutual-tls-ssl?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[](https://sonarcloud.io/dashboard?id=nl.altindag%3Amutual-tls-ssl)
# 掌握双向 TLS 🔐 [](https://twitter.com/intent/tweet?text=If%20you%20are%20interested%20in%20securing%20your%20web%20application,%20you%20might%20want%20to%20read%20the%20tutorial%20Mastering%20two-way%20tls&url=https://github.com/Hakky54/mutual-tls&via=hakky541&hashtags=encryption,security,https,ssl,tls,certificate,developer,java,scala,kotlin,sslcontextkickstart)
嘿,你好呀 👋 欢迎来到本教程!希望你会喜欢。
这个教程的诞生最初是因为我想学习基于 TLS 的双向认证。所以它一开始根本算不上教程,只是一个练手项目。
我想了解如何在本地设置它以及需要使用哪些命令。当我成功运行之后,我认为它可能对其他人也有用,所以我进行了重构,使其易于复现,并将其作为教程放在这个页面上,以便其他人可以从中学习。
本教程将引导你完成使用 TLS 认证保护应用程序的过程,仅允许持有特定证书的用户进行访问。这意味着你可以选择允许哪些用户调用你的应用程序。
# 目录
1. [简介](#introduction)
2. [教程](#tutorial)
- [启动服务器](#starting-the-server)
- [向服务器发送问候(无加密)](#saying-hello-to-the-server-without-encryption)
- [在服务器上启用 HTTPS(单向 TLS)](#enabling-https-on-the-server-one-way-tls)
- [要求客户端表明身份(双向 TLS)](#require-the-client-to-identify-itself-two-way-tls)
- [基于信任证书颁发机构的双向 TLS](#two-way-tls-based-on-trusting-the-certificate-authority)
3. [自动化脚本](#automated-script-for-enabling-authentication-with-tls)
4. [已测试的 Http 客户端](#tested-clients)
5. [演示和演练视频](#demo-and-walk-through-video)
6. [贡献](#contributing)
# 简介
这个示例项目演示了服务器和客户端的基本设置。服务器和客户端之间的通信通过 HTTP 进行,因此目前完全没有加密。目标是确保所有通信都被加密。
**定义**
* Identity(身份):一个包含密钥对(也称为私钥和公钥)的 KeyStore
* TrustStore(信任库):一个包含一个或多个证书(也称为公钥)的 KeyStore。此 KeyStore 包含受信任证书的列表
* 单向认证(也称为单向 tls,单向 ssl):客户端验证对端证书的 Https 连接
* 双向认证(也称为双向 tls,双向 ssl,相互认证):客户端和对端互相验证证书的 Https 连接,也称为相互认证
**有用的链接**
* [Keytool 备忘录](https://gist.github.com/Hakky54/7a2f0fcbcf5fdf4674d48f1a0b31c862)
* [Openssl 备忘录](https://gist.github.com/Hakky54/b30418b25215ad7d18f978bc0b448d81)
* [Curl 配合 Java KeyStore 使用](https://gist.github.com/Hakky54/049299f0874fd4b870257c6458e0dcbd)
* [Http 客户端配置备忘录](#tested-clients)
**一些历史**
我大部分时间都在使用 Apache Http Client,因此我最初创建这个项目时只使用了 Apache 的 http 客户端。
一段时间后,我发现还有很多其他的 Java 客户端,并且也有一些基于 Kotlin 和 Scala 的客户端可用。
配置 ssl/tls 可能很困难,而且每个客户端都需要不同的设置。我想与其他开发人员分享这些知识,因为每个开发人员可能会使用不同的 http 客户端。
我还想为所有开发人员和社区提供一份**备忘录**,请参阅[此处](#tested-clients)获取包含示例客户端配置和示例 http 请求的 [40+ http 客户端](#tested-clients)) 列表。
客户端模块随着时间推移不断增长,并包含了大量用于测试所有这些适用于 Java、Scala 和 Kotlin 的 http 客户端的依赖项。因此,客户端模块可能看起来很复杂。请注意,出于这个特定原因,它会在初次构建期间下载大量依赖项。
此外,在这个项目的生命周期中,[GitHub - Ayza](https://github.com/Hakky54/ayza) 也应运而生,以轻松配置所有这些客户端。每个 http 客户端可能需要不同的 ssl 对象来启用 ssl,而该库确保在拥有基本 ssl 配置的同时,能够提供所有类型来配置这些客户端。
# 教程
## 启动服务器
**最低要求:**
1. Java 21
2. Maven 3.9.10
3. Eclipse、Intellij IDEA(或任何其他文本编辑器,如 VIM)
4. 一个终端
本项目包含一个 Maven 包装器,因此你可以在不安装 Maven 的情况下运行此项目。本教程的文档除了默认的 mvn 命令外,还包含了 Maven 包装器的命令。
如果你想使用 Java 8 或 Java 11 运行此项目,可以通过下面的 git 命令获取较旧的版本。
- `git checkout tags/java-8-compatible`
- `git checkout tags/java-11-compatible`
服务器依赖于项目的其他组件,因此首先需要在根目录中运行 `mvn install`。
```
mvn install
```
通过运行服务器项目中 [App 类](server/src/main/java/nl/altindag/server/App.java) 的 main 方法,或者在根目录的终端中运行以下命令来启动服务器:
```
cd server/ && mvn spring-boot:run
```
或者使用 Maven 包装器
```
cd server/ && ./../mvnw spring-boot:run
```
## 向服务器发送问候(无加密)
当前,服务器在默认端口 8080 上运行且未启用加密。你可以在终端中使用以下 curl 命令调用 hello 接口:
```
curl -i -XGET http://localhost:8080/api/hello
```
它应该会给出以下响应:
```
HTTP/1.1 200
Content-Type: text/plain;charset=UTF-8
Content-Length: 5
Date: Sun, 11 Nov 2018 14:21:50 GMT
Hello
```
你也可以使用 client 目录中提供的客户端来调用服务器。该客户端是一个基于 Cucumber 的集成测试,你可以通过在 IDE 中运行 [ClientRunnerIT](client/src/test/java/nl/altindag/client/ClientRunnerIT.java) 类,或者在终端的根目录中运行以下命令来启动它:`cd client/ && mvn exec:java`,或者使用 Maven 包装器 `cd client/ && ./../mvnw exec:java`。
有一个描述集成测试步骤的 [Hello.feature](client/src/test/resources/features/Hello.feature) 文件。你可以在客户端项目的测试资源中找到它。
还有另一种方法可以同时运行服务器和客户端,即在根目录中使用以下命令:`mvn clean verify`,或者使用 Maven 包装器 `./mvnw clean verify`。
客户端默认向 localhost 发送请求,因为它期望服务器在同一台机器上。如果服务器运行在不同的机器上,你仍然可以在运行客户端时通过以下 VM 参数提供自定义 url:`-Durl=http://[HOST]:[PORT]`
## 在服务器上启用 HTTPS(单向 TLS)
现在,你将学习如何通过启用 TLS 来保护你的服务器。你可以通过将所需属性添加到名为 `application.yml` 的应用程序属性文件中来实现此目的。
添加以下属性:
```
server:
port: 8443
ssl:
enabled: true
```
你可能会问自己为什么端口设置为 8443。使用 https 的 Tomcat 服务器的端口约定是 8443,而 http 的约定是 8080。因此,我们可以将端口 8080 用于 https 连接,但这是一种糟糕的做法。有关端口约定的更多信息,请参阅 [Wikipedia](https://en.wikipedia.org/wiki/List_of_TCP_and_UDP_port_numbers)。
重启服务器,以便它能够应用你所做的更改。你可能会遇到以下异常:`IllegalArgumentException: Resource location must not be null`。
你收到此消息是因为服务器需要一个包含服务器证书的 keystore,以确保与外部世界的连接是安全的。如果你提供以下 VM 参数,服务器可以为你提供更多信息:`-Djavax.net.debug=SSL,keymanager,trustmanager,ssl:handshake`
为了解决这个问题,你将为服务器创建一个包含公钥和私钥的 keystore。公钥将与用户共享,以便他们可以加密通信。用户和服务器之间的通信可以使用服务器的私钥进行解密。请永远不要分享服务器的私钥,因为其他人可能会拦截通信,并能够看到加密通信的内容。
要创建包含公钥和私钥的 keystore,请在终端中执行以下命令:
```
keytool -v -genkeypair -dname "CN=Hakan,OU=Amsterdam,O=Thunderberry,C=NL" -keystore server/src/main/resources/identity.jks -storepass secret -keypass secret -keyalg RSA -keysize 2048 -alias server -validity 3650 -deststoretype pkcs12 -ext KeyUsage=digitalSignature,dataEncipherment,keyEncipherment,keyAgreement -ext ExtendedKeyUsage=serverAuth,clientAuth -ext SubjectAlternativeName:c=DNS:localhost,DNS:raspberrypi,IP:127.0.0.1
```
现在,你需要告诉你的服务器 keystore 的位置并提供密码。将以下内容粘贴到你的 `application.yml` 文件中:
```
server:
port: 8443
ssl:
enabled: true
key-store: classpath:identity.jks
key-password: secret
key-store-password: secret
```
恭喜!你在服务器和客户端之间启用了 TLS 加密连接!现在,你可以尝试使用以下 curl 命令调用服务器:`curl -i --insecure -v -XGET https://localhost:8443/api/hello`
让我们同时运行 [ClientRunnerIT](client/src/test/java/nl/altindag/client/ClientRunnerIT.java) 类中的客户端。
你将看到以下错误消息:`java.net.ConnectException: Connection refused (Connection refused)`。看起来客户端正试图向服务器发送问候,但服务器似乎并不存在。问题在于客户端正试图在端口 8080 上向服务器发送问候,而服务器在端口 8443 上处于活动状态。对 Constants 类应用以下更改:
**从:**
```
private static final String DEFAULT_SERVER_URL = "http://localhost:8080";
```
**到:**
```
private static final String DEFAULT_SERVER_URL = "https://localhost:8443";
```
让我们再次尝试运行客户端,你会看到出现以下消息:`javax.net.ssl.SSLHandshakeException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target`。
这意味着客户端希望通过 HTTPS 进行通信,并且在握手过程中收到了它尚未识别的服务器证书。
因此,你还需要创建一个 truststore。truststore 就像一个包含受信任证书的手提箱。客户端会将其在 SSL 握手过程中收到的证书与其 truststore 中的内容进行比较。如果匹配,则 SSL 握手过程将继续。在创建 truststore 之前,你需要拥有服务器的证书。你可以使用以下命令获取它:
**导出服务器的证书**
```
keytool -v -exportcert -file server/src/main/resources/server.cer -alias server -keystore server/src/main/resources/identity.jks -storepass secret -rfc
```
现在,你可以为客户端创建 truststore,并使用以下命令导入服务器的证书:
```
keytool -v -importcert -file server/src/main/resources/server.cer -alias server -keystore client/src/test/resources/truststore.jks -storepass secret -noprompt
```
你已为客户端创建了 truststore。不幸的是,客户端对此并不知情。现在,你需要告诉它需要使用具有正确位置和密码的 truststore。你还需要告诉客户端已启用认证。在客户端的 `application.yml` 文件中提供以下属性:
```
client:
ssl:
one-way-authentication-enabled: true
two-way-authentication-enabled: false
trust-store: truststore.jks
trust-store-password: secret
```
## 要求客户端表明身份(双向 TLS)
下一步是要求客户端进行认证。这将强制客户端表明身份,通过这种方式,服务器还可以验证客户端的身份以及它是否是受信任的。你可以通过使用 client-auth 属性告诉服务器你也希望验证客户端来启用此功能。将以下属性放入服务器的 `application.yml` 中:
```
server:
port: 8443
ssl:
enabled: true
key-store: classpath:identity.jks
key-password: secret
key-store-password: secret
client-auth: need
```
如果你运行客户端,它将失败并显示以下错误消息:`javax.net.ssl.SSLHandshakeException: Received fatal alert: bad_certificate`。这表明客户端的证书无效,因为根本没有证书。那么,让我们使用以下命令创建一个:
```
keytool -v -genkeypair -dname "CN=Suleyman,OU=Altindag,O=Altindag,C=NL" -keystore client/src/test/resources/identity.jks -storepass secret -keypass secret -keyalg RSA -keysize 2048 -alias client -validity 3650 -deststoretype pkcs12 -ext KeyUsage=digitalSignature,dataEncipherment,keyEncipherment,keyAgreement -ext ExtendedKeyUsage=serverAuth,clientAuth
```
你还需要为服务器创建一个 truststore。在创建 truststore 之前,你需要拥有客户端的证书。你可以使用以下命令获取它:
**导出客户端的证书**
```
keytool -v -exportcert -file client/src/test/resources/client.cer -alias client -keystore client/src/test/resources/identity.jks -storepass secret -rfc
```
**使用客户端证书创建服务器的 truststore**
```
keytool -v -importcert -file client/src/test/resources/client.cer -alias client -keystore server/src/main/resources/truststore.jks -storepass secret -noprompt
```
你为客户端创建了额外的 keystore。不幸的是,客户端对此并不知情。现在,你需要告诉它也需要使用具有正确位置和密码的 keystore。你还需要告诉客户端已启用双向认证。在客户端的 `application.yml` 文件中提供以下属性:
```
client:
ssl:
one-way-authentication-enabled: false
two-way-authentication-enabled: true
key-store: identity.jks
key-password: secret
key-store-password: secret
trust-store: truststore.jks
trust-store-password: secret
```
服务器也不知道新创建的 truststore。因此,将当前属性替换为以下属性:
```
server:
port: 8443
ssl:
enabled: true
key-store: classpath:identity.jks
key-password: secret
key-store-password: secret
trust-store: classpath:truststore.jks
trust-store-password: secret
client-auth: need
```
如果你再次运行客户端,你将看到测试通过,并且客户端以安全的方式收到了来自服务器的 hello 消息。恭喜!你完成了双向 TLS 的安装!
## 基于信任证书颁发机构的双向 TLS
还有另一种实现双向认证的方法,那就是基于信任证书颁发机构。它有优点也有缺点。
**优点**
- 客户端无需添加服务器的证书
- 服务器无需添加所有客户端的证书
- 维护工作量会减少,因为只有证书颁发机构的证书有效期会过期
**缺点**
- 你无法再控制哪些应用程序被允许调用你的应用程序。你向任何拥有由证书颁发机构签名证书的应用程序授予权限。
以下是具体步骤:
1. [创建证书颁发机构](#creating-a-certificate-authority)
2. [创建证书签名请求](_URL_34/>)
3. [使用证书签名请求签署证书](#signing-the-certificate-with-the-certificate-signing-request)
4. [用已签名的证书替换未签名的证书](#replace-the-unsigned-certificate-with-a-signed-one)
5. [仅信任证书颁发机构](#trusting-the-certificate-authority-only)
#### 创建证书颁发机构
通常已经存在一个证书颁发机构,你需要提供你的证书以使其被签名。在这里你将创建自己的证书颁发机构,并用它来签名客户端和服务器证书。要创建一个证书颁发机构,你可以执行以下命令:
```
keytool -v -genkeypair -dname "CN=Root-CA,OU=Certificate Authority,O=Thunderberry,C=NL" -keystore root-ca/identity.jks -storepass secret -keypass secret -keyalg RSA -keysize 2048 -alias root-ca -validity 3650 -deststoretype pkcs12 -ext KeyUsage=digitalSignature,keyCertSign -ext BasicConstraints=ca:true,PathLen:3
```
或者你可以使用仓库中已提供的那个,请参阅 [identity.jks](root-ca/identity.jks)
#### 创建证书签名请求
要让你的证书被签名,你需要提供一个证书签名请求文件。可以使用以下命令创建它:
##### 服务器的证书签名请求
```
keytool -v -certreq -file server/src/main/resources/server.csr -keystore server/src/main/resources/identity.jks -alias server -keypass secret -storepass secret -keyalg rsa
```
##### 客户端的证书签名请求
```
keytool -v -certreq -file client/src/test/resources/client.csr -keystore client/src/test/resources/identity.jks -alias client -keypass secret -storepass secret -keyalg rsa
```
证书颁发机构需要这些 csr 文件才能对其进行签名。下一步将是对请求进行签名。
#### 使用证书签名请求签署证书
##### 签署客户端证书
```
keytool -v -gencert -infile client/src/test/resources/client.csr -outfile client/src/test/resources/client-signed.cer -keystore root-ca/identity.jks -storepass secret -alias root-ca -validity 3650 -ext KeyUsage=digitalSignature,dataEncipherment,keyEncipherment,keyAgreement -ext ExtendedKeyUsage=serverAuth,clientAuth -rfc
```
##### 签署服务器证书
```
keytool -v -gencert -infile server/src/main/resources/server.csr -outfile server/src/main/resources/server-signed.cer -keystore root-ca/identity.jks -storepass secret -alias root-ca -validity 3650 -ext KeyUsage=digitalSignature,dataEncipherment,keyEncipherment,keyAgreement -ext ExtendedKeyUsage=serverAuth,clientAuth -ext SubjectAlternativeName:c=DNS:localhost,DNS:raspberrypi,IP:127.0.0.1 -rfc
```
#### 用已签名的证书替换未签名的证书
服务器和客户端的 identity keystore 仍然保留着未签名的证书。现在你可以用已签名的证书替换它。keytool 有一个奇怪的限制/设计。它不允许你直接导入已签名的证书,如果你尝试这样做,它会报错。证书颁发机构的证书必须存在于 identity.jks 中。
**导出 CA 证书**
```
keytool -v -exportcert -file root-ca/root-ca.pem -alias root-ca -keystore root-ca/identity.jks -storepass secret -rfc
```
**客户端**
```
keytool -v -importcert -file root-ca/root-ca.pem -alias root-ca -keystore client/src/test/resources/identity.jks -storepass secret -noprompt
keytool -v -importcert -file client/src/test/resources/client-signed.cer -alias client -keystore client/src/test/resources/identity.jks -storepass secret
keytool -v -delete -alias root-ca -keystore client/src/test/resources/identity.jks -storepass secret
```
**服务器**
```
keytool -v -importcert -file root-ca/root-ca.pem -alias root-ca -keystore server/src/main/resources/identity.jks -storepass secret -noprompt
keytool -v -importcert -file server/src/main/resources/server-signed.cer -alias server -keystore server/src/main/resources/identity.jks -storepass secret
keytool -v -delete -alias root-ca -keystore server/src/main/resources/identity.jks -storepass secret
```
#### 仅信任证书颁发机构
现在你需要将客户端和服务器配置为仅信任证书颁发机构。你可以通过将证书颁发机构的证书导入客户端和服务器的 truststore 来实现。你可以使用以下两个命令来完成此操作:
**客户端**
```
keytool -v -importcert -file root-ca/root-ca.pem -alias root-ca -keystore client/src/test/resources/truststore.jks -storepass secret -noprompt
```
**服务器**
```
keytool -v -importcert -file root-ca/root-ca.pem -alias root-ca -keystore server/src/main/resources/truststore.jks -storepass secret -noprompt
```
truststore 中仍然包含客户端和服务器特定的证书,这些需要被移除。你可以使用以下命令来完成此操作:
**客户端**
```
keytool -v -delete -alias server -keystore client/src/test/resources/truststore.jks -storepass secret
```
**服务器**
```
keytool -v -delete -alias client -keystore server/src/main/resources/truststore.jks -storepass secret
```
如果你再次运行客户端,你将看到测试通过,并且客户端收到了来自服务器的 hello 消息,并且是基于由证书颁发机构签名的证书。
## 用于启用 TLS 认证的自动化脚本
你还可以使用此项目 [脚本目录](script) 中提供的脚本,将上述所有步骤自动化。运行以下命令之一来执行脚本:
* 单向认证:`./configure-one-way-authentication`
* 双向认证:`./configure-two-way-authentication-by-trusting-each-other my-company-name`
* 基于信任证书颁发机构的双向认证:`./configure-two-way-authentication-by-trusting-root-ca my-company-name`
## 已测试的客户端
以下是已测试客户端的列表,基于纯 Java 的 Http Client 配置可以在 [ClientConfig 类](client/src/main/java/nl/altindag/client/ClientConfig.java) 中找到。
基于 Kotlin 和 Scala 的 http 客户端配置作为嵌套类包含在内,请参阅此处获取完整列表:[服务目录](client/src/main/java/nl/altindag/client/service)。
[服务目录](client/src/main/java/nl/altindag/client/service) 包含各个 Http 客户端及其示例请求。
所有客户端示例都使用在 [SSLConfig 类](https://github.com/Hakky54/mutual-tls-ssl/blob/master/client/src/main/java/nl/altindag/client/SSLConfig.java) 中创建的相同基础 ssl 配置。
**Java**
* [Apache HttpClient](https://hc.apache.org/httpcomponents-client-4.5.x/index.html) -> [客户端配置](https://github.com/Hakky54/mutual-tls-ssl/blob/35cba2f3a2dcd73b01fa323b99eec7777f7429bb/client/src/main/java/nl/altindag/client/ClientConfig.java#L68) | [示例请求](https://github.com/Hakky54/mutual-tls-ssl/blob/master/client/src/main/java/nl/altindag/client/service/ApacheHttpClientService.java)
* [Apache HttpAsyncClient](https://hc.apache.org/httpcomponents-asyncclient-4.1.x/index.html) -> [客户端配置](https://github.com/Hakky54/mutual-tls-ssl/blob/35cba2f3a2dcd73b01fa323b99eec7777f7429bb/client/src/main/java/nl/altindag/client/ClientConfig.java#L76) | [示例请求](https://github.com/Hakky54/mutual-tls-ssl/blob/master/client/src/main/java/nl/altindag/client/service/ApacheHttpAsyncClientService.java)
* [Apache 5 HttpClient](https://hc.apache.org/) -> [客户端配置](https://github.com/Hakky54/mutual-tls-ssl/blob/35cba2f3a2dcd73b01fa323b99eec7777f7429bb/client/src/main/java/nl/altindag/client/ClientConfig.java#L86) | [示例请求](https://github.com/Hakky54/mutual-tls-ssl/blob/master/client/src/main/java/nl/altindag/client/service/Apache5HttpClientService.java)
* [Apache 5 HttpAsyncClient](https://hc.apache.org/) -> [客户端配置](https://github.com/Hakky54/mutual-tls-ssl/blob/35cba2f3a2dcd73b01fa323b99eec7777f7429bb/client/src/main/java/nl/altindag/client/ClientConfig.java#L97) | [示例请求](https://github.com/Hakky54/mutual-tls-ssl/blob/master/client/src/main/java/nl/altindag/client/service/Apache5HttpAsyncClientService.java)
* [JDK HttpClient](https://openjdk.java.net/groups/net/httpclient/intro.html) -> [客户端配置](https://github.com/Hakky54/mutual-tls-ssl/blob/35cba2f3a2dcd73b01fa323b99eec7777f7429bb/client/src/main/java/nl/altindag/client/ClientConfig.java#L111) | [示例请求](https://github.com/Hakky54/mutual-tls-ssl/blob/master/client/src/main/java/nl/altindag/client/service/JdkHttpClientService.java)
* [旧版 JDK HttpClient](https://docs.oracle.com/javase/tutorial/networking/urls/readingWriting.html) -> [客户端配置与示例请求](https://github.com/Hakky54/mutual-tls-ssl/blob/master/client/src/main/java/nl/altindag/client/service/OldJdkHttpClientService.java)
* [Netty Reactor](https://github.com/reactor/reactor-netty) -> [客户端配置](https://github.com/Hakky54/mutual-tls-ssl/blob/35cba2f3a2dcd73b01fa323b99eec7777f7429bb/client/src/main/java/nl/altindag/client/ClientConfig.java#L134) | [示例请求](https://github.com/Hakky54/mutual-tls-ssl/blob/master/client/src/main/java/nl/altindag/client/service/ReactorNettyService.java)
* [Jetty Reactive HttpClient](https://github.com/jetty-project/jetty-reactive-httpclient) -> [客户端配置](https://github.com/Hakky54/mutual-tls-ssl/blob/35cba2f3a2dcd73b01fa323b99eec7777f7429bb/client/src/main/java/nl/altindag/client/ClientConfig.java#L142) | [示例请求](https://github.com/Hakky54/mutual-tls-ssl/blob/master/client/src/main/java/nl/altindag/client/service/JettyReactiveHttpClientService.java)
* [Spring RestTemplate](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/client/RestTemplate.html) -> [客户端配置](https://github.com/Hakky54/mutual-tls-ssl/blob/35cba2f3a2dcd73b01fa323b99eec7777f7429bb/client/src/main/java/nl/altindag/client/ClientConfig.java#L119) | [示例请求](https://github.com/Hakky54/mutual-tls-ssl/blob/master/client/src/main/java/nl/altindag/client/service/SpringRestTemplateService.java)
* [Spring WebFlux WebClient Netty](https://docs.spring.io/spring/docs/current/spring-framework-reference/web-reactive.html) -> [客户端配置](https://github.com/Hakky54/mutual-tls-ssl/blob/35cba2f3a2dcd73b01fa323b99eec7777f7429bb/client/src/main/java/nl/altindag/client/ClientConfig.java#L148) | [示例请求](https://github.com/Hakky54/mutual-tls-ssl/blob/master/client/src/main/java/nl/altindag/client/service/SpringWebClientService.java)
* [Spring WebFlux WebClient Jetty](https://docs.spring.io/spring/docs/current/spring-framework-reference/web-reactive.html) -> [客户端配置](https://github.com/Hakky54/mutual-tls-ssl/blob/35cba2f3a2dcd73b01fa323b99eec7777f7429bb/client/src/main/java/nl/altindag/client/ClientConfig.java#L155) | [示例请求](https://github.com/Hakky54/mutual-tls-ssl/blob/master/client/src/main/java/nl/altindag/client/service/SpringWebClientService.java)
* [OkHttp](https://github.com/square/okhttp) -> [客户端配置](https://github.com/Hakky54/mutual-tls-ssl/blob/35cba2f3a2dcd73b01fa323b99eec7777f7429bb/client/src/main/java/nl/altindag/client/ClientConfig.java#L125) | [示例请求](https://github.com/Hakky54/mutual-tls-ssl/blob/master/client/src/main/java/nl/altindag/client/service/OkHttpClientService.java)
* [Jersey Client](https://eclipse-ee4j.github.io/jersey/) -> [客户端配置](https://github.com/Hakky54/mutual-tls-ssl/blob/35cba2f3a2dcd73b01fa323b99eec7777f7429bb/client/src/main/java/nl/altindag/client/ClientConfig.java#L162) | [示例请求](https://github.com/Hakky54/mutual-tls-ssl/blob/master/client/src/main/java/nl/altindag/client/service/JerseyClientService.java)
* 旧版 Jersey Client -> [客户端配置](https://github.com/Hakky54/mutual-tls-ssl/blob/35cba2f3a2dcd73b01fa323b99eec7777f7429bb/client/src/main/java/nl/altindag/client/ClientConfig.java#L170) | [示例请求](https://github.com/Hakky54/mutual-tls-ssl/blob/master/client/src/main/java/nl/altindag/client/service/OldJerseyClientService.java)
* [Apache CXF JAX-RS](https://cxf.apache.org/) -> [客户端配置](https://github.com/Hakky54/mutual-tls-ssl/blob/35cba2f3a2dcd73b01fa323b99eec7777f7429bb/client/src/main/java/nl/altindag/client/ClientConfig.java#L182) | [示例请求](https://github.com/Hakky54/mutual-tls-ssl/blob/master/client/src/main/java/nl/altindag/client/service/ApacheCXFJaxRsClientService.java)
* [使用 ConduitConfigurer 的 Apache CXF](https://cxf.apache.org/) -> [客户端配置](https://github.com/Hakky54/mutual-tls-ssl/blob/35cba2f3a2dcd73b01fa323b99eec7777f7429bb/client/src/main/java/nl/altindag/client/ClientConfig.java#L191) | [示例请求](https://github.com/Hakky54/mutual-tls-ssl/blob/master/client/src/main/java/nl/altindag/client/service/ApacheCXFWebClientService.java)
* [Google HttpClient](https://github.com/googleapis/google-http-java-client) -> [客户端配置](https://github.com/Hakky54/mutual-tls-ssl/blob/35cba2f3a2dcd73b01fa323b99eec7777f7429bb/client/src/main/java/nl/altindag/client/ClientConfig.java#L206) | [示例请求](https://github.com/Hakky54/mutual-tls-ssl/blob/master/client/src/main/java/nl/altindag/client/service/GoogleHttpClientService.java)
* [Unirest](https://github.com/Kong/unirest-java) -> [客户端配置](https://github.com/Hakky54/mutual-tls-ssl/blob/35cba2f3a2dcd73b01fa323b99eec7777f7429bb/client/src/main/java/nl/altindag/client/ClientConfig.java#L214) | [示例请求](https://github.com/Hakky54/mutual-tls-ssl/blob/master/client/src/main/java/nl/altindag/client/service/UnirestService.java)
* [Retrofit](https://github.com/square/retrofit) -> [客户端配置](https://github.com/Hakky54/mutual-tls-ssl/blob/35cba2f3a2dcd73b01fa323b99eec7777f7429bb/client/src/main/java/nl/altindag/client/ClientConfig.java#L224) | [示例请求](https://github.com/Hakky54/mutual-tls-ssl/blob/master/client/src/main/java/nl/altindag/client/service/RetrofitService.java)
* [Async Http Client](https://github.com/AsyncHttpClient/async-http-client) -> [客户端配置](https://github.com/Hakky54/mutual-tls-ssl/blob/35cba2f3a2dcd73b01fa323b99eec7777f7429bb/client/src/main/java/nl/altindag/client/ClientConfig.java#L262) | [示例请求](https://github.com/Hakky54/mutual-tls-ssl/blob/master/client/src/main/java/nl/altindag/client/service/AsyncHttpClientService.java)
* [Feign](https://github.com/OpenFeign/feign) -> [客户端配置](https://github.com/Hakky54/mutual-tls-ssl/blob/35cba2f3a2dcd73b01fa323b99eec7777f7429bb/client/src/main/java/nl/altindag/client/ClientConfig.java#L272) | [示例请求](https://github.com/Hakky54/mutual-tls-ssl/blob/master/client/src/main/java/nl/altindag/client/service/FeignService.java)
* [Methanol](https://github.com/mizosoft/methanol) -> [客户端配置](https://github.com/Hakky54/mutual-tls-ssl/blob/35cba2f3a2dcd73b01fa323b99eec7777f7429bb/client/src/main/java/nl/altindag/client/ClientConfig.java#L308) | [示例请求](https://github.com/Hakky54/mutual-tls-ssl/blob/master/client/src/main/java/nl/altindag/client/service/MethanolService.java)
* [Vertx Webclient](https://github.com/vert-x3/vertx-web) -> [客户端配置](https://github.com/Hakky54/mutual-tls-ssl/blob/35cba2f3a2dcd73b01fa323b99eec7777f7429bb/client/src/main/java/nl/altindag/client/ClientConfig.java#L316) | [示例请求](https://github.com/Hakky54/mutual-tls-ssl/blob/master/client/src/main/java/nl/altindag/client/service/VertxWebClientService.java)
* [gRPC](https://grpc.io/) -> [客户端/服务端配置与示例请求](https://github.com/Hakky54/java-tutorials)
* [ElasticSearch](https://www.elastic.co/) -> [RestHighLevelClient 配置与示例请求](https://github.com/Hakky54/java-tutorials/blob/main/elasticsearch-with-ssl/src/main/java/nl/altindag/ssl/es/App.java)
* [Jetty WebSocket](https://www.eclipse.org/jetty/) -> [客户端配置与示例请求](https://github.com/Hakky54/java-tutorials/blob/2bf5d975347d500bb9d0aa3b32cbf33b345425ee/websocket-client-with-ssl/src/main/java/nl/altindag/ssl/ws/App.java#L14)
**Kotlin**
* [Fuel](https://github.com/kittinunf/fuel) -> [客户端配置与示例请求](https://github.com/Hakky54/mutual-tls-ssl/blob/master/client/src/main/java/nl/altindag/client/service/FuelService.kt)
* [Http4k 配合 Apache 4](https://github.com/http4k/http4k) -> [客户端配置](https://github.com/Hakky54/mutual-tls-ssl/blob/master/client/src/main/java/nl/altindag/client/service/Http4kApache4HttpClientService.kt) | [示例请求](https://github.com/Hakky54/mutual-tls-ssl/blob/master/client/src/main/java/nl/altindag/client/service/Http4kClientService.kt)
* [Http4k 配合 Async Apache 4](https://github.com/http4k/http4k) -> [客户端配置](https://github.com/Hakky54/mutual-tls-ssl/blob/master/client/src/main/java/nl/altindag/client/service/Http4kApache4AsyncHttpClientService.kt) | [示例请求](https://github.com/Hakky54/mutual-tls-ssl/blob/master/client/src/main/java/nl/altindag/client/service/Http4kAsyncClientService.kt)
* [Http4k 配合 Apache 5](https://github.com/http4k/http4k) -> [客户端配置](https://github.com/Hakky54/mutual-tls-ssl/blob/master/client/src/main/java/nl/altindag/client/service/Http4kApache5HttpClientService.kt) | [示例请求](https://github.com/Hakky54/mutual-tls-ssl/blob/master/client/src/main/java/nl/altindag/client/service/Http4kClientService.kt)
* [Http4k 配合 Async Apache 5](https://github.com/http4k/http4k) -> [客户端配置](https://github.com/Hakky54/mutual-tls-ssl/blob/master/client/src/main/java/nl/altindag/client/service/Http4kApache5AsyncHttpClientService.kt) | [示例请求](https://github.com/Hakky54/mutual-tls-ssl/blob/master/client/src/main/java/nl/altindag/client/service/Http4kAsyncClientService.kt)
* [Http4k 配合 Java Net](https://github.com/http4k/http4k) -> [客户端配置](https://github.com/Hakky54/mutual-tls-ssl/blob/master/client/src/main/java/nl/altindag/client/service/Http4kJavaHttpClientService.kt) | [示例请求](https://github.com/Hakky54/mutual-tls-ssl/blob/master/client/src/main/java/nl/altindag/client/service/Http4kClientService.kt)
* [Http4k 配合 Jetty](https://github.com/http4k/http4k) -> [客户端配置](https://github.com/Hakky54/mutual-tls-ssl/blob/master/client/src/main/java/nl/altindag/client/service/Http4kJettyHttpClientService.kt) | [示例请求](https://github.com/Hakky54/mutual-tls-ssl/blob/master/client/src/main/java/nl/altindag/client/service/Http4kClientService.kt)
* [Http4k 配合 OkHttp](https://github.com/http4k/http4k) -> [客户端配置](https://github.com/Hakky54/mutual-tls-ssl/blob/master/client/src/main/java/nl/altindag/client/service/Http4kOkHttpClientService.kt) | [示例请求](https://github.com/Hakky54/mutual-tls-ssl/blob/master/client/src/main/java/nl/altindag/client/service/Http4kClientService.kt)
* [Kohttp](https://github.com/rybalkinsd/kohttp) -> [客户端配置与示例请求](https://github.com/Hakky54/mutual-tls-ssl/blob/master/client/src/main/java/nl/altindag/client/service/KohttpService.kt)
* [Ktor 配合 Android 引擎](https://github.com/ktorio/ktor) -> [客户端配置](https://github.com/Hakky54/mutual-tls-ssl/blob/master/client/src/main/java/nl/altindag/client/service/KtorAndroidHttpClientService.kt) | [示例请求](https://github.com/Hakky54/mutual-tls-ssl/blob/master/client/src/main/java/nl/altindag/client/service/KtorHttpClientService.kt)
* [Ktor 配合 Apache 引擎](https://github.com/ktorio/ktor) -> [客户端配置](https://github.com/Hakky54/mutual-tls-ssl/blob/master/client/src/main/java/nl/altindag/client/service/KtorApacheHttpClientService.kt) | [示例请求](https://github.com/Hakky54/mutual-tls-ssl/blob/master/client/src/main/java/nl/altindag/client/service/KtorHttpClientService.kt)
* [Ktor 配合 CIO (基于协程的 I/O) 引擎](https://github.com/ktorio/ktor) -> [客户端配置](https://github.com/Hakky54/mutual-tls-ssl/blob/master/client/src/main/java/nl/altindag/client/service/KtorCIOHttpClientService.kt) | [示例请求](https://github.com/Hakky54/mutual-tls-ssl/blob/master/client/src/main/java/nl/altindag/client/service/KtorHttpClientService.kt)
* [Ktor 配合 Java 引擎](https://github.com/ktorio/ktor) -> [客户端配置](https://github.com/Hakky54/mutual-tls-ssl/blob/master/client/src/main/java/nl/altindag/client/service/KtorJavaHttpClientService.kt) | [示例请求](https://github.com/Hakky54/mutual-tls-ssl/blob/master/client/src/main/java/nl/altindag/client/service/KtorHttpClientService.kt)
* [Ktor 配合 Okhttp 引擎](https://github.com/ktorio/ktor) -> [客户端配置](https://github.com/Hakky54/mutual-tls-ssl/blob/master/client/src/main/java/nl/altindag/client/service/KtorOkHttpClientService.kt) | [示例请求](https://github.com/Hakky54/mutual-tls-ssl/blob/master/client/src/main/java/nl/altindag/client/service/KtorHttpClientService.kt)
**Scala**
* [Twitter Finagle](https://github.com/twitter/finagle) -> [客户端配置与示例请求](https://github.com/Hakky54/mutual-tls-ssl/blob/master/client/src/main/java/nl/altindag/client/service/FinagleHttpClientService.scala)
* [Twitter Finagle Featherbed](https://github.com/finagle/featherbed) -> [客户端配置与示例请求](https://github.com/Hakky54/mutual-tls-ssl/blob/d78e4e81b8b775d3ff09c11b0a7c1532a741199e/client/src/main/java/nl/altindag/client/service/FeatherbedRequestService.scala#L19)
* [Akka Http Client](https://github.com/akka/akka-http) -> [客户端配置](https://github.com/Hakky54/mutual-tls-ssl/blob/35cba2f3a2dcd73b01fa323b99eec7777f7429bb/client/src/main/java/nl/altindag/client/ClientConfig.java#L253) | [示例请求](https://github.com/Hakky54/mutual-tls-ssl/blob/f32fc11ac1bf879fe5f9ba55a57a4e8188eb29e3/client/src/main/java/nl/altindag/client/service/AkkaHttpClientService.java#L34)
* [Dispatch Reboot](https://github.com/dispatch/reboot) -> [客户端配置与示例请求](https://github.com/Hakky54/mutual-tls-ssl/blob/master/client/src/main/java/nl/altindag/client/service/DispatchRebootService.scala)
* [ScalaJ / Simplified Http Client](https://github.com/scalaj/scalaj-http) -> [客户端配置与示例请求](https://github.com/Hakky54/mutual-tls-ssl/blob/master/client/src/main/java/nl/altindag/client/service/ScalaJHttpClientService.scala)
* [Sttp](https://github.com/softwaremill/sttp) -> [客户端配置与示例请求](https://github.com/Hakky54/mutual-tls-ssl/blob/master/client/src/main/java/nl/altindag/client/service/SttpHttpClientService.scala)
* [Requests-Scala](https://github.com/lihaoyi/requests-scala) -> [客户端配置与示例请求](https://github.com/Hakky54/mutual-tls-ssl/blob/master/client/src/main/java/nl/altindag/client/service/RequestsScalaService.scala)
* [Http4s Blaze Client](https://github.com/http4s/http4s) -> [客户端配置](https://github.com/Hakky54/mutual-tls-ssl/blob/master/client/src/main/java/nl/altindag/client/service/Http4sBlazeClientService.scala) | [示例请求](https://github.com/Hakky54/mutual-tls-ssl/blob/master/client/src/main/java/nl/altindag/client/service/Http4sService.scala)
* [Http4s Java Net Client](https://github.com/http4s/http4s) -> [客户端配置](https://github.com/Hakky54/mutual-tls-ssl/blob/master/client/src/main/java/nl/altindag/client/service/Http4sJavaNetClientService.scala) | [示例请求](https://github.com/Hakky54/mutual-tls-ssl/blob/master/client/src/main/java/nl/altindag/client/service/Http4sService.scala)
**Groovy**
* Groovy 配合 Java Net -> [客户端配置与示例请求](https://github.com/Hakky54/mutual-tls-ssl/blob/master/client/src/main/java/nl/altindag/client/service/GroovyJavaNetClientService.groovy)
* [Hyper Poet](https://github.com/tambapps/hyperpoet) -> [客户端配置与示例请求](https://github.com/Hakky54/mutual-tls-ssl/blob/master/client/src/main/java/nl/altindag/client/service/HyperPoetService.groovy)
**Clojure**
* [clj-http](https://github.com/dakrone/clj-http) -> [客户端配置与示例请求](https://github.com/Hakky54/mutual-tls-ssl/blob/master/client/src/main/java/nl/altindag/client/service/CljHttpClientService.clj)
* JDK Http Client -> [客户端配置与示例请求](https://github.com/Hakky54/mutual-tls-ssl/blob/master/client/src/main/java/nl/altindag/client/service/ClojureJdkHttpClientService.clj)
* 旧版 JDK Http Client -> [客户端配置与示例请求](https://github.com/Hakky54/mutual-tls-ssl/blob/master/client/src/main/java/nl/altindag/client/service/ClojureHttpClientService.clj)
* [Hato](https://github.com/gnarroway/hato) -> [客户端配置与示例请求](https://github.com/Hakky54/mutual-tls-ssl/blob/master/client/src/main/java/nl/altindag/client/service/HatoHttpClientService.clj)
* [Aleph](https://github.com/clj-commons/aleph) -> [客户端配置与示例请求](https://github.com/Hakky54/mutual-tls-ssl/blob/master/client/src/main/java/nl/altindag/client/service/AlephHttpClientService.clj)
* [Http-Kit](https://github.com/http-kit/http-kit) -> [客户端配置与示例请求](https://github.com/Hakky54/mutual-tls-ssl/blob/master/client/src/main/java/nl/altindag/client/service/HttpKitService.clj)
**其他**
本教程不提供下列客户端的示例,但已证明它们可以与本项目提供的服务器配合使用。为了指引你正确的方向,你可以将创建的 Java keystore 文件转换为 pem 文件,然后将其与下面列出的其中一个客户端一起使用。以下 Gist 页面包含了将 Java keystore 转换为 pem 文件的命令:[Gist - Curl 配合 Java KeyStore 使用](https://gist.github.com/Hakky54/049299f0874fd4b870257c6458e0dcbd)
* [curl](https://curl.se/) -> [客户端配置与示例请求](https://gist.github.com/Hakky54/049299f0874fd4b870257c6458e0dcbd)
* [Postman](https://www.postman.com/)
## 演示和演练视频
[](https://youtu.be/yfOknrCBNbQ)
## 贡献
有很多方法可以为本项目做出贡献:
* 给个星标
* 通过 [GitHub](https://github.com/sponsors/Hakky54)、[Ko-fi](https://ko-fi.com/hakky54) 或 [open collective](https://opencollective.com/hakky54) 进行捐赠
* 通过 [](https://twitter.com/intent/tweet?text=If%20you%20are%20interested%20in%20securing%20your%20web%20application,%20you%20might%20want%20to%20read%20the%20tutorial%20Mastering%20two-way%20tls&=https://github.com/Hakky54/mutual-tls&via=hakky541&hashtags=encryption,security,https,ssl,tls,certificate,developer,java,scala,kotlin,sslcontextkickstart) 分享
* 加入 [Gitter 聊天室](https://gitter.im/hakky54/mutual-tls-ssl) 并留下反馈或帮助解答用户的问题
* 提交 PR
标签:Apache HttpClient, API安全, ElasticSearch, Feign, Google HttpClient, Grizzly, gRPC, HTTPS, JDK HttpClient, Jersey, Jetty, JSON输出, JS文件枚举, Kotlin, mTLS, Netty, OkHttp, Python工具, RestTemplate, Retrofit, Scala, Spring Boot, Spring WebFlux, SSL/TLS, Unirest, WebClient, WebSocket, 依赖分析, 加密通信, 单向认证, 双向认证, 域名枚举, 客户端, 教程, 服务端, 案例, 网络安全, 证书配置, 隐私保护