在靠近用户的地方部署容器
本工程教育(EngEd)计划由科支持。
在全球范围内即时部署容器。Section是经济实惠、简单而强大的。
免费开始。
构建具有相互TLS认证的微配置文件Rest客户端
10月19日, 2021
- 主题。
- 语言
在网络中,层是用来理解网络的复杂性的。一些网络模型,如OSI和TCP/IP一直是现代网络的关键。考虑到安全不是一个系统的事后考虑,而是一个不可或缺的部分,系统中的安全是很重要的。
在Microprofile中,安全是关键。它是在系统开发过程中并列建立的,以防止不必要的集成错误。
提高安全性的方法之一是在应用程序中添加TLS认证。
在这篇文章中,你将学习如何建立一个带有TLS认证的Microprofile Rest Client应用程序。
目录
- 主要收获
- 先决条件
- 什么是TLS认证?
- 开始学习
- 修改客户端和服务器端点
- 添加服务器配置
- 添加客户端配置
- 添加一个客户端界面并导入配置属性
- 使用一个简单的客户端调用从客户端访问服务器
- 生成密钥库和信任库
- 运行应用程序
- 使用ClientBuilder进行服务器调用
- 总结
- 进一步阅读
- 参考资料
主要收获
在本文结束时,你将获得以下知识。
- 什么是TLS认证
- 如何以及在哪里可以实现它
- 在Microprofile应用程序中设置相互的TLS认证,如Quarkus
- 运行应用程序
前提条件
这篇文章的基础知识包括以下内容。
- Java语言知识和使用。对于中级的Java开发者来说,这将是很容易的。
- 在机器上安装和设置好Java。对于这篇文章,你需要版本
11+。 - 在你的机器上设置了一个Java IDE。我推荐使用最新的IntelliJ终极版。它的互动性很强,而且它有所有必要的工具来使用Java框架。
- 一个稳定的互联网连接。
注意:本文的截图来自IntelliJ终极版
2021.2.2。使用的Java版本是Java 17版。
什么是TLS认证?
TLS(传输层安全)是一种加密协议,利用对称和非对称加密技术来确保信息从一台机器传递到另一台机器。
当信息从服务器传递到客户端时,它可以防止有恶意的未经授权的人进行窃听。它只保护信息的传输,但不保护终端设备的安全。
这是因为它是在TCP/IP层的顶部实现的,用于加密应用层协议,例如HTTP、FTP、IMAP和SMTP,尽管它也可以在UDP、DCCP和SCTP上实现(例如用于VPN和基于SIP的应用)。
这种实现被称为DTLS(数据报传输层安全),并在RFCs 6347、5238和6083中规定。
TLS是一种进化的SSL(安全套接字层)。它允许服务器和客户端交换用于加密信息的密钥。
TLS可用于网站,以加强电子邮件服务、银行网站、电子商务网站以及其他许多网站的安全性。
一旦启用,人们会注意到浏览器中URL左侧的挂锁图标。这表明该网站是安全的。
它看起来如下图所示。

当点击挂锁图标时,会出现以下弹出窗口。

始终避免访问没有SSL挂锁图标的网站,这表明它们是不安全的。

开始使用
初始化一个新的maven项目。将名称和artifactId设为quarkus-restclient-mutual-tls 。
单击 "完成"。


在生成的项目中,删除src文件。
这样可以减少混乱,以便将注意力集中在将要创建的新模块上。
在其中,通过导航到File > New > Module 选项创建一个新模块,如下图所示。

在新打开的窗口中,选择项目类型为Quarkus。
以下将是它的配置。
Name: quarkus-serverArtifactId: quarkus-serverGroup: org.gs

至于依赖关系,选择RESTEasy JAX-RS 和RESTEasy Jackson ,如下所示。

创建第二个模块,这次设置如下。
Name: quarkus-clientArtifactId: quarkus-clientGroup: org.gs

选择RESTEasy JAX-RS,RESTClient, 和RESTClient Jackson 作为依赖项,如下图所示。

项目结构如下图所示。

删除测试文件夹中的内容,包括quarkus-client 和quarkus-server 模块中的内容。
删除后可以在没有额外单元测试的情况下快速运行。
在quarkus-client 模块上点击右键,选择Open in > Terminal 选项。这将在IntelliJ的集成终端中打开它。
对quarkus-server 模块做同样的操作,这样它们就可以在两个不同的终端中打开。
运行它们来检查所有的依赖关系是否安装好了,或者是否有任何错误,运行如下。
- 在
quarkus-client终端,运行./mvnw quarkus:dev。打开一个新的终端,通过运行curl http://localhost:8080/hello来测试客户端,从客户端得到一个响应。如果响应是 "Hello RESTEasy",说明工作正常。停止它,在另一个终端上运行服务器。 - 在
quarkus-server终端上,运行./mvnw quarkus:dev -Ddebug=5006。这将把调试端口从默认的5005改为5006,以避免端口冲突。运行curl http://localhost:8080/hello,以获得服务器响应。停止它。
修改客户端和服务器端点
前往quarkus-client 模块内的ExampleResource.java 文件。
- 将其中的类和文件名改为
ClientResource。 - 通过改变GET请求的返回,将输出改为 "Hello from Client":
Hello from Client\n。 - 将端点改为
/client。
在quarkus-server 模块内的ExampleResource.java 文件中,做以下工作。
- 将其中的类和文件名改为
ServerResource。 - 通过将GET请求的返回值改为
Hello from Server\n,将输出改为 "Hello from Client"。 - 将端点改为
/server。
像之前那样使用设置好的端点分别重新运行模块,以确保返回的结果符合要求。
添加服务器配置
在服务器中,打开application.properties 文件。这个文件位于quarkus-server/src/main/resources 路径下。
在该文件中,进行以下操作。
- 禁用http请求,启用使用https请求。Https允许应用程序使用安全的SSL。要禁用http,使用端口号0。
在端口号8443处使用SSL,如下面的代码。
# Enable ssl but disable http to enforce security
quarkus.http.ssl-port=8443
quarkus.http.port=0
- 将应用程序设置为需要SSL客户端认证。这将要求在连接过程中提供SSL密钥。请看下面。
# Set the server to require ssl authentication
quarkus.http.ssl.client-auth=required
将在服务器上生成一个密钥库,它将被用作客户端的TrustStore。它将在服务器验证期间使用。
客户端认证时也会这样做。客户端KeyStore将作为TrustStore储存在服务器中。
这在下图中显示。

TrustStore用于存储认证机构(CA)的证书,以验证服务器在SSL连接中提交的证书。
另一方面,钥匙库用于存储由程序提交给双方(服务器或客户端)进行验证的私钥和身份证书。在这里阅读更多信息。
产生的KeyStore和TrustStore都有密码。在这个过程中需要这些正确的密码。
- 将KeyStore和TrustStore的位置与它们的密码加在一起。代码如下所示。
# Set the location of the server keystore and it's password
quarkus.http.ssl.certificate.key-store-file=META-INF/resources/server.keystore
quarkus.http.ssl.certificate.key-store-password=server_password
# Set the location of the server truststore and it's password
quarkus.http.ssl.certificate.trust-store-file=META-INF/resources/client.truststore
quarkus.http.ssl.certificate.trust-store-password=client_password
- 在另一个窗口重新运行服务器,并尝试使用
curl https://localhost:8443/server。由于没有提供证书,这立即产生了一个错误。 - 尝试用
curl -k https://localhost:8443/server进一步访问它,它可以快速检查错误的配置或其他故障。这也会产生一个错误,如下图所示。

添加客户端配置
在客户端,打开application.properties 文件。这个文件位于quarkus-client/src/main/resources 路径下。
在该文件中,做以下工作。
- 使用
MicroProfile Rest Client (mp-rest),通过HTTP调用RESTful服务。好处是它是一种类型安全的方法。在其中,指出服务器的网址、trustStore和trustStorePassword,以及Keystore连同其密码作为Keystore的密码。
下面检查一下。
# Use the mp-rest to pass the keystore and truststore from client to server and back
# Set the url, TrustStore and TrustStore password, KeyStore and KeyStore password to be used during the server call process
org.gs.Client/mp-rest/url=https://localhost:8443
org.gs.Client/mp-rest/trustStore=classpath:/META-INF/resources/server.truststore
org.gs.Client/mp-rest/trustStorePassword=server_password
org.gs.Client/mp-rest/keyStore=classpath:/META-INF/resources/client.keystore
org.gs.Client/mp-rest/keyStorePassword=client_password
- 添加其他配置属性,可以很容易地注入到模块中,以便在应用程序中注入,也可以重复使用。
查看其代码如下。
# Add some config properties
url=https://localhost:8443
keyStore=META-INF/resources/client.keystore
keyStorePassword=client_password
trustStore=META-INF/resources/server.truststore
trustStorePassword=server_password
添加一个客户端接口并导入配置属性
在客户端内部,在ClientResource.java 文件的位置,添加一个名为'Client.java'的新文件。这将是一个用于访问服务器API的接口。当所有给定的条件得到满足时,服务器应被调用。
该接口的代码如下所示。
package org.gs;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
@RegisterRestClient
@Path("/")
public interface Client {
// Access the server when all conditions required are met
@GET
@Path("/server")
@Produces(MediaType.TEXT_PLAIN)
String call();
}
使用一个简单的客户端调用从客户端访问服务器
- 首先,在 "ClientResource "类中,就在 "hello() "字符串的上方,导入应用程序的配置。导入用于调用的服务器网址、KeyStore和它的密码、TrustStore和它的密码。
在下面的代码中看到。
/*
Inject configuration properties to the app; they are stored in the application.properties file. These shall be used during the connection between the server and the client
These include: - URL to be used to contact the server
- The KeyStore and Keystore password
- The truststore and truststore password
*/
@ConfigProperty(name = "url")
URL serverURL;
@ConfigProperty(name = "keyStore")
String keyStoreFile;
@ConfigProperty(name = "keyStorePassword")
String keyStoreFilePassword;
@ConfigProperty(name = "trustStore")
String trustStoreFile;
@ConfigProperty(name = "trustStorePassword")
String trustStoreFilePassword;
- 添加一个
RestClient注解,以表明它是一个rest客户端。注入最初生成的接口。
/*
* Annotate that the application is a REST_CLIENT
* Inject the client interface into the application. This will be used as an interface between the server and client
*/
@RestClient
@Inject
Client client;
- 添加一个用于访问服务器的端点。该端点将通过
http://localhost:8080/client/client。
/*
* Create a simple GET request that will use the configurations entered to contact the server
*/
@GET
@Path("client")
@Produces(MediaType.TEXT_PLAIN)
public String callWithClient() {
return client.call();
}
它使用最初在客户端的application.properties 文件中设置的mp-rest 配置。
生成KeyStores和TrustStores
在客户端内部,在 "resources/META-INF/resources "文件夹中创建一个新文件,名称为 "generate_client_keystore.sh"。正如所见,该文件包含可以运行的shell脚本。
打开该文件,将下面的代码复制并粘贴到其中。
keytool -genkeypair \
-storepass client_password \
-keyalg RSA \
-keysize 2048 \
-dname "CN=client" \
-alias client \
-ext "SAN:c=DNS:localhost,IP:127.0.0.1" \
-keystore client.keystore \
&&
cp client.keystore \
../../../../../../quarkus-server/src/main/resources/META-INF/resources/client.truststore
当运行时,该代码将做以下工作。
- 生成公钥和相关的私钥
- 密钥的密码是
client_password。记住,这个密码是在服务器的application.property文件中设置的,作为客户的TrustStore。 - 底层的密钥算法为
RSA。 - 密钥的大小为2 MB (
2048)。 - 一个区分的名称为
CN=client。 - 要处理的条目的别名为
client。 - 一个X.509扩展,如下所示。
SAN:c=DNS:localhost,IP:127.0.0.1.在这里或这里阅读更多关于X.509扩展的信息。 - Keystore文件的名称为
client.keystore - 一旦生成,应用程序将把它复制到服务器的资源文件中,作为
client.truststore
通过导航到它,并按下组合Ctrl + Shift + F10 ,或在IntelliJ中看到的按钮来运行它。 运行按钮来运行它,如在IntelliJ中看到的那样。如下图所示。

在服务器模块内创建一个新文件,在'resources/META-INF/resources'文件下,创建一个文件并命名为'generate_server_keystore.sh'。
复制下面的代码并粘贴到该文件中。
keytool -genkeypair \
-storepass server_password \
-keyalg RSA \
-keysize 2048 \
-dname "CN=server" \
-alias server \
-ext "SAN:c=DNS:localhost,IP:127.0.0.1" \
-keystore server.keystore \
&& \
cp server.keystore \
../../../../../../quarkus-client/src/main/resources/META-INF/resources/server.truststore
它和前面的文件一样,只是做了一些修改以支持服务器端。
创建的文件应该如下图所示。
.
├── quarkus-client
│ └── src
│ └── main
│ ├── docker
│ ├── java
│ └── resources
│ └── META-INF
│ ├── resources
│ ├── client.keystore
│ ├── generate_server_keystore.sh
│ └── server.truststore
│ └── application.properties
├── quarkus-server
│ └── src
│ └── main
│ ├── docker
│ ├── java
│ └── resources
│ └── META-INF
│ ├── resources
│ ├── client.truststore
│ ├── generate_server_keystore.sh
│ └── server.keystore
│ └── application.properties
当创建在线版本时,例如GitHub版本,不要在线存储KeyStore或TrustStore。当GitGuardian被激活时,它会立即显示这是一个危险的举动,可能会被利用。相反,存储可以用来安全地生成其他的生成器。
运行应用程序
要运行应用程序,以确保客户端和服务器已经交换了文件,并使用密码来验证它们。
- 在不同的内部终端中打开两个模块。
- 在客户端的终端上,运行
./mvnw quarkus:dev。 - 在服务器的终端上,运行
./mvnw quarkus:dev -Ddebug=5006。 - 通过客户端访问服务器,使用
http://localhost:8080/client/client
响应将是Hello from Server 。
使用ClientBuilder进行一个服务器调用
在'ClientResources.java'文件中,做以下工作。
- 添加一个新的端点,用来检查生成器是否得到与之前相同的结果。通过添加下面的代码块来做到这一点。
/*
* Create a GET request using a builder that will use the configurations entered to contact the server
*/
@GET
@Path("clientBuilder")
@Produces(MediaType.TEXT_PLAIN)
- 添加
callWithClientBuilder函数和它在任何情况下抛出的错误。如下图所示。
/*
* Create a method to call the server using a Client_Builder
* Some errors it may give include: - KeyStoreException error
- CertificateException error
- NoSuchAlgorithmException error
- IOException error
*/
public String callWithClientBuilder() throws KeyStoreException, CertificateException, NoSuchAlgorithmException, IOException {
}
- 在上面的函数中,获取之前作为配置属性导入的KeyStore和其密码。请看下面。
/*
* Get the KeyStore file as a stream and store it in a Keystore object for use during the server call
* On load, use the Keystore and Keystore file password
*/
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
InputStream inputStreamKeyStore = this.getClass()
.getClassLoader()
.getResourceAsStream(keyStoreFile);
keyStore.load(inputStreamKeyStore, keyStoreFilePassword.toCharArray());
- 获取trustStore和它的密码。请看下面。
/*
* Get the TrustStore file as a stream and store it in a Keystore object for use during the server call
* On load, use the TrustStore and TrustStore file password
* The TrustStore is of a KeyStore type
*/
KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
InputStream inputStreamTrustStore = this.getClass()
.getClassLoader()
.getResourceAsStream(trustStoreFile);
trustStore.load(inputStreamTrustStore, trustStoreFilePassword.toCharArray());
- 使用获取的KeyStore对象来建立url,这样就可以调用服务器了。
/*
* Set the url, keyStore, and trustStore during the build
* Make the server call at the end of the build function
*/
Client clientBuild = RestClientBuilder.newBuilder()
.baseUrl(serverURL)
.keyStore(keyStore, keyStoreFilePassword)
.trustStore(trustStore)
.build(Client.class);
return clientBuild.call();
保存应用程序,像以前一样重新运行,但现在使用http://localhost:8080/client/clientBuilder 。
它给出的结果与最初的一样,是一个Hello from Server 的回复。这表明客户端已经正确和安全地联系了服务器。

代码中有什么问题吗?请在这里找到包含代码的资源库。
结论
在这篇文章中,已经完成了以下内容。
- 什么是TLS认证。
- 在哪里以及如何实现TLS。
- 在MicroProfile应用程序中设置相互的TLS认证,如Quarkus。
- 运行应用程序。
进一步阅读
增加对TLS主题的理解,如:。TLS如何工作,证书颁发机构(CA),公钥基础设施(PKI)的X.509标准,等等。
参考资料
同行评议的贡献者。Daniel Katungi
类似文章
[

语言
如何创建一个可重复使用的React表单组件
阅读更多

语言, Node.js
用Next.js构建一个薪资系统
阅读更多

架构
在Django中创建和使用装饰器
阅读更多信息