TLS+gRPC怎么玩?如何让自己的RPC通信更加安全?

开发 前端
确保 gRPC 的通信安全我们有很多种不同的方式,其中一种,就是对通信过程进行加密,使用上 TLS。对于 TLS 如何加密,如何协商密钥,这些我这里就不再啰嗦了,我在之前的文章中都已经介绍过了。咱们就直接来看具体的玩法。

今天我们要在前文的基础之上,来和小伙伴们聊一聊如何确保 gRPC 的通信安全。

确保 gRPC 的通信安全我们有很多种不同的方式,其中一种,就是对通信过程进行加密,使用上 TLS。对于 TLS 如何加密,如何协商密钥,这些我这里就不再啰嗦了,我在之前的文章中都已经介绍过了。咱们就直接来看具体的玩法。

这块整体上可以分为两大类:

  • 启用单向安全连接
  • 启用 mTLS 安全连接

我们分别来看。

1. 启用单向安全连接

单向安全连接其实就是说只需要客户端校验服务端,确保客户端收到的消息来自预期的服务端,整个的校验就涉及到我们前文所说的 TLS、CA 等内容了,具体流程是这样:

  1. 首先我们先在自己电脑本地生成一个自签名的 CA 证书。
  2. 利用这个 CA 证书,生成一个服务证书。

大致上就这两个步骤就行了,然后在客户端和服务端中分别加载相应的证书即可。

上面我们提到了需要先有一个自签名的 CA 证书,这一步其实也可以省略,省略之后就直接生成一个自签名的服务证书即可,然后在客户端和服务端都使用这个服务证书。

来实际操作一下。

先自己安装一下 openssl 工具,配置一下环境变量,软件安装比较简单,我这里就不啰嗦了。

1.1 生成 CA 证书

首先我们来看下如何生成 CA 证书。

一共是三个步骤:

  1. 生成 .key 私钥文件:
openssl genrsa -out ca.key 2048
  • out 表示输出的文件名。
  • 2048 表示私钥的位数。
  1. 生成 .csr 证书签名请求文件:

CSR 即证书签名申请(Certificate Signing Request),获取 SSL 证书,需要先生成 CSR 文件并提交给证书颁发机构(CA)。CSR 包含了用于签发证书的公钥、用于辨识的名称信息(Distinguished Name)(例如域名)、真实性和完整性保护(例如数字签名),通常从 Web 服务器生成 CSR,同时创建加解密的公钥私钥对。

openssl req -new -key ca.key -out ca.csr  -subj "/C=CN/L=GuangZhou/O=javaboy/CN=local.javaboy.org"
  • subj 中描述的是一些国家、城市、组织以及通用名称(域名)等信息。
  1. 自签名生成 .crt 证书文件
openssl req -new -x509 -days 3650 -key ca.key -out ca.crt  -subj "/C=CN/L=GuangZhou/O=javaboy/CN=local.javaboy.org"
  • -x509 表示是要生成自签名证书。
  • -days 3650 表示证书有效期是 3650 天。
  • -key 表示生成证书所需要的密钥。

有人说公钥呢?公钥其实就在 .crt 证书文件中。

1.2 生成服务证书

再来看生成服务证书,生成服务证书和生成 CA 证书其实整个过程差不多,唯一的区别在于,CA 证书是自签名的,而服务证书是 CA 的私钥给签名的,就这个差别。

  1. 生成 .key 私钥文件:
openssl genrsa -out server.key 2048
  1. 生成 .csr 证书签名请求文件:
openssl req -new -key server.key -out server.csr -subj "/C=CN/L=GuangZhou/O=javaboy/CN=local.javaboy.org"
  1. 签名生成 .crt 证书文件
openssl x509 -req -days 3650 -in server.csr -out server.crt -CA ca.crt -CAkey ca.key
  • -req 和 -in 指定了 server.csr,这个是证书请求文件,这里实际上是表示签署证书请求文件。

证书现在就生成完毕。

这里我们生成的私钥都是 .key​ 文件,这个用我们 Java 代码加载的时候会有问题,我们要将之转为 .pem 格式然后再用 Java 代码进行加载,转换的命令如下:

openssl pkcs8 -topk8 -inform pem -in server.key -outform pem -nocrypt -out server.pem

1.3 单向加密

现在证书都有了,在当前项目目录下新建一个文件夹,专门用来放证书,项目目录结构如下:

├── certs
│ ├── ca.crt
│ ├── ca.csr
│ ├── ca.key
│ ├── server.crt
│ ├── server.csr
│ ├── server.key
│ └── server.pem
├── grpc_api
│ ├── pom.xml
│ ├── src
│ └── target
├── grpc_client
│ ├── pom.xml
│ ├── src
│ └── target
├── grpc_server
│ ├── pom.xml
│ ├── src
│ └── target
└── pom.xml

我们看下代码该如何改造实现单向加密通信。

先来看服务端代码:

public void start() throws IOException {
int port = 50051;
File certFile = Paths.get( "certs", "server.crt").toFile();
File keyFile = Paths.get("certs", "server.pem").toFile();
server = ServerBuilder.forPort(port)
.addService(new LoginServiceImpl())
.addService(ServerInterceptors.intercept(new HelloServiceImpl(), new AuthInterceptor()))
.useTransportSecurity(certFile,keyFile)
.build()
.start();
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
LoginServer.this.stop();
}));
}

大家注意,由于我生成签名的时候,使用的域名是 local.javaboy.org​ 这是我在本地 hosts 文件中配置的,指向本地地址,所以在后续的通信中,我使用的域名都将是 local.javaboy.org。

  1. Paths.get 方法表示从项目的根目录下开始查找文件,参数是可变长度参数,参数共同组成文件完整路径。
  2. 服务端需要加载服务签名和服务私钥,签名证书是客户端验证服务端身份用的,私钥则是服务端解密客户端消息使用的。

服务端的改造就这些。

再来看客户端的改造:

File certFile = Paths.get( "certs", "ca.crt").toFile();
SslContext sslContext = GrpcSslContexts.forClient().trustManager(certFile).build();
ManagedChannel channel = NettyChannelBuilder.forAddress("local.javaboy.org", 50051)
.useTransportSecurity()
.sslContext(sslContext)
.build();

客户端主要是加载 CA 证书文件,服务端的证书就是 CA 私钥签发的,但是需要 CA 公钥也就是 ca.crt 进行验签,所以这里客户端加载了 ca.crt 即可。

好啦,整体上的流程差不多就是这个样子。

2. 启用 mTLS 安全连接

上面的例子只是客户端校验了服务端的身份,服务端并没有校验客户端的身份,如果想要双向校验,那么就把上面的流程对称操作一遍就可以了。

首先我们需要为客户端生成相应的证书,步骤跟前面也基本上一直,使用 CA 进行签名,如下:

  1. 生成 .key 私钥文件:
openssl genrsa -out client.key 2048
  1. 生成 .csr 证书签名请求文件:
openssl req -new -key client.key -out client.csr -subj "/C=CN/L=GuangZhou/O=javaboy/CN=local.javaboy.org"
  1. 签名生成 .crt 证书文件
openssl x509 -req -days 3650 -in client.csr -out client.crt -CA ca.crt -CAkey ca.key

然后来看看代码。

先来看服务端:

public void start() throws IOException {
int port = 50051;
File certFile = Paths.get( "certs", "server.crt").toFile();
File keyFile = Paths.get("certs", "server.pem").toFile();
File caFile = Paths.get("certs", "ca.crt").toFile();
server = NettyServerBuilder.forPort(port)
.addService(new LoginServiceImpl())
.addService(ServerInterceptors.intercept(new HelloServiceImpl(), new AuthInterceptor()))
.sslContext(GrpcSslContexts.forServer(certFile,keyFile).trustManager(caFile).clientAuth(ClientAuth.REQUIRE).build())
.build()
.start();
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
LoginServer3.this.stop();
}));
}

服务端要加载的文件多了 ca.crt,这是给客户端验签的时候需要用到。

再来看看客户端代码:

File caFile = Paths.get( "certs", "ca.crt").toFile();
File certFile = Paths.get( "certs", "client.crt").toFile();
File keyFile = Paths.get( "certs", "client.pem").toFile();
SslContext sslContext = GrpcSslContexts.forClient().trustManager(caFile)
.keyManager(certFile, keyFile).build();
ManagedChannel channel = NettyChannelBuilder.forAddress("local.javaboy.org", 50051)
.useTransportSecurity()
.sslContext(sslContext)
.build();

客户端多了 client.crt​ 和 client.pem,两者的作用根服务端中这两者的作用基本一致,前文已有说明,这里就不再赘述了。

好啦,如此之后,我们的 gRPC 通信就加上了 TLS 的外壳,更加安全了。

责任编辑:武晓燕 来源: 江南一点雨
相关推荐

2021-07-01 10:03:55

Distroless容器安全

2019-02-28 22:47:06

云计算数据安全企业

2016-11-24 13:54:56

2011-08-10 09:54:07

2017-07-07 14:52:43

2020-06-11 08:26:05

信息泄漏密码网络安全

2019-06-03 09:11:59

2019-08-28 09:28:07

SSHOpenSSH运维

2022-05-25 09:52:36

车联网通信安全SSL/TLS

2021-08-16 12:13:02

SwiftUIList ArticleList

2010-05-18 14:19:13

安全通信

2021-06-10 07:49:26

RPCgRPC模式

2010-06-25 18:00:02

APC InfraSt

2018-09-06 10:16:39

2017-09-26 11:40:38

Juniper安全Contrail Se

2016-10-10 23:00:18

2018-01-31 11:20:48

2010-09-09 16:39:24

2011-05-16 11:04:48

界面设计

2018-01-15 17:36:58

通信网
点赞
收藏

51CTO技术栈公众号