news 2026/9/15 13:47:12

grpc-java TLS 加密通信实战:基于 example-tls 实现单向 TLS 与双向 Mutual TLS

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
grpc-java TLS 加密通信实战:基于 example-tls 实现单向 TLS 与双向 Mutual TLS

grpc-java TLS 加密通信实战:基于 example-tls 实现单向 TLS 与双向 Mutual TLS

【免费下载链接】grpc-javaThe Java gRPC implementation. HTTP/2 based RPC项目地址: https://gitcode.com/GitHub_Trending/gr/grpc-java

gRPC 默认基于 HTTP/2 明文传输,生产环境中必须通过 TLS 保证通信的机密性与身份真实性。本指南以 grpc-java 仓库中的 examples/example-tls 示例为核心,完整讲解 Hello World 服务在 TLS 场景下的构建、证书准备、单向 TLS 与双向 mTLS 的配置方法,并深入剖析TlsServerCredentialsTlsChannelCredentialsoverrideAuthority的底层实现原理。读完本文,你将掌握 grpc-java 中基于文件证书的 TLS 服务端/客户端完整配置流程,并能用三种主流构建工具(Gradle、Maven、Bazel)运行 TLS 示例。

示例概览与目录结构

example-tls是 grpc-java 仓库中的官方 TLS 演示项目,与基础 Hello World 示例的唯一区别在于:服务端与客户端在启动时加载 PEM 格式证书,并通过Grpc.newServerBuilderForPort(...)/Grpc.newChannelBuilderForAddress(...)传入 TLS 凭据。其核心源码与配置如下:

  • HelloWorldServerTls.java:TLS 服务端入口,解析命令行参数并组装TlsServerCredentials
  • HelloWorldClientTls.java:TLS 客户端入口,解析命令行参数并组装TlsChannelCredentials
  • helloworld.proto:仅包含一个Greeter.SayHello一元 RPC,与普通 Hello World 完全相同;
  • pom.xml、BUILD.bazel、settings.gradle:三种构建系统的配置文件。

proto 定义中java_multiple_files = truejava_package = "io.grpc.examples.helloworld"决定了生成的桩代码(GreeterGrpcHelloRequestHelloReply)所在的包路径,服务端与客户端的 import 均依赖这些生成类。

前置条件:gRPC Java 库与代码生成插件的安装

示例的运行依赖 grpc-java 核心库与protoc-gen-grpc-java代码生成插件,README 强烈建议先检出 git release 标签,因为发布版本已包含可用的预构建产物:

git checkout v<major>.<minor>.<patch>

如果坚持使用未发布版本(例如 master HEAD),则必须先按仓库根目录下的 COMPILING.md 完整构建 gRPC Java 库,并将其连同代码生成插件安装到本地:

  • 通过 Gradle 安装本地 SNAPSHOT:在仓库根目录执行./gradlew publishToMavenLocal(对应 COMPILING.md);
  • 构建时如需跳过 C++ codegen 与 Android 模块,可在<project-root>/gradle.properties中分别添加skipCodegen=trueskipAndroid=true
  • 注意构建需要 JDK 8,因为测试用例依赖 TLS。

也就是说,非 release 版本下运行本文所有构建与运行命令前,必须先完成这一步。

使用 Gradle 构建并运行(推荐路径)

examples/example-tls目录内执行:

$ ../gradlew installDist

该命令会创建两个可直接执行的启动脚本,位于build/install/example-tls/bin/目录下:

  • hello-world-tls-server
  • hello-world-tls-client

与普通 Hello World 一样,示例要求先启动服务端,再启动客户端。TLS 版本只是额外增加了证书相关的命令行参数。

服务端参数说明

USAGE: HelloWorldServerTls port certChainFilePath privateKeyFilePath [trustCertCollectionFilePath] Note: You only need to supply trustCertCollectionFilePath if you want to enable Mutual TLS.

参数含义如下:

参数必选说明
port服务监听端口
certChainFilePathPEM 格式的证书链文件路径(服务器证书)
privateKeyFilePath与证书配对的私钥文件路径
trustCertCollectionFilePathCA 证书集合文件路径;仅在启用双向 mTLS 时需要

客户端参数说明

USAGE: HelloWorldClientTls host port [trustCertCollectionFilePath [clientCertChainFilePath clientPrivateKeyFilePath]] Note: clientCertChainFilePath and clientPrivateKeyFilePath are only needed if mutual auth is desired.
参数必选说明
host服务端主机名
port服务端端口
trustCertCollectionFilePathCA 证书集合文件路径;若不提供则使用系统默认证书颁发机构(system default CA)
clientCertChainFilePathclientPrivateKeyFilePath客户端证书链与私钥;仅在双向认证时需要

单向 TLS(无 mutual auth)

所谓单向 TLS,指仅服务端出示证书、客户端通过 CA 校验服务端身份:

# 运行服务端: ./build/install/example-tls/bin/hello-world-tls-server 50440 ../../testing/src/main/resources/certs/server1.pem ../../testing/src/main/resources/certs/server1.key # 在另一个终端运行客户端: ./build/install/example-tls/bin/hello-world-tls-client localhost 50440 ../../testing/src/main/resources/certs/ca.pem

双向 TLS(Mutual Auth)

双向 TLS 要求客户端也持有证书并在握手阶段出示,服务端通过 CA 反向校验客户端身份:

# 运行服务端(追加第四个参数 trustCertCollectionFilePath,启用 mTLS): ./build/install/example-tls/bin/hello-world-tls-server 50440 ../../testing/src/main/resources/certs/server1.pem ../../testing/src/main/resources/certs/server1.key ../../testing/src/main/resources/certs/ca.pem # 在另一个终端运行客户端(追加 clientCertChainFilePath 与 clientPrivateKeyFilePath): ./build/install/example-tls/bin/hello-world-tls-client localhost 50440 ../../testing/src/main/resources/certs/ca.pem ../../testing/src/main/resources/certs/client.pem ../../testing/src/main/resources/certs/client.key

测试证书与 overrideAuthority:为什么必须匹配 SAN

README 特别强调:使用仓库自带测试证书时,客户端必须通过.overrideAuthority("foo.test.google.fr")覆盖ManagedChannelBuilder(此处实际为Grpc.newChannelBuilderForAddress返回的 builder)的目标授权,以匹配测试证书 Subject Alternative Names 中的域名。

这是因为 TLS 握手时 gRPC 客户端会用authority(默认是host:port)去校验服务器证书的 SAN(Subject Alternative Name)。测试证书server1.pem的 SAN 配置在 server1-openssl.cnf 中,CN 为*.test.google.com,SAN 包含foo.test.google.fr,因此连接localhost时只有覆盖 authority 才能通过主机名校验。HelloWorldClientTls.java 中的实现如下:

ManagedChannel channel = Grpc.newChannelBuilderForAddress(host, port, tlsBuilder.build()) /* Only for using provided test certs. */ .overrideAuthority("foo.test.google.fr") .build();

源码注释明确说明overrideAuthority仅用于测试证书场景。如果你使用的是由真实 CA 签发的、SAN 与主机名匹配的正式服务器证书,则不需要trustCertCollectionFilePath(直接走系统默认 CA),也不需要 overrideAuthority:

NotetrustCertCollectionFilePathis not needed if you are using system default certificate authority.

Note you can use system default certificate authority if you are using a real server certificate.

仓库中的测试凭据位于 testing/src/main/resources/certs,包含ca.pem/ca.key(自签 CA)、server1.pem/server1.key(由 CA 签发、含 SAN)、client.pem/client.key(客户端证书)、以及badclient.*/badserver.*(自签错误凭据,用于负面测试)。

用 OpenSSL 生成自己的自签名证书

若不想使用仓库测试证书,可参照 testing/src/main/resources/certs/README 中记录的生成命令。核心三步流程如下:

  1. 生成自签 CA:
openssl req -x509 -new -newkey rsa:2048 -nodes -keyout ca.key -out ca.pem \ -config ca-openssl.cnf -days 3650 -extensions v3_req
  1. 生成服务器私钥(PKCS#8 无加密格式,便于 gRPC 直接读取)与证书签名请求:
openssl genrsa -out server.key.rsa 2048 openssl pkcs8 -topk8 -in server.key.rsa -out server.key -nocrypt openssl req -new -key server.key -out server.csr -config server1-openssl.cnf
  1. 用 CA 签发服务器证书(务必带上 SAN 扩展,否则客户端主机名校验会失败):
openssl x509 -req -CA ca.pem -CAkey ca.key -CAcreateserial -in server.csr \ -out server.pem -extensions req_ext -extfile server1-openssl.cnf -days 3650

客户端证书的生成方式相同,仅需将 CN 设为testclient等标识。注意 gRPC 要求私钥为 PKCS#8 格式(openssl pkcs8 -topk8 ... -nocrypt生成),这正是仓库中client.keyserver1.key的由来。

源码剖析:服务端如何组装 TlsServerCredentials

HelloWorldServerTls.java 的main方法按参数个数决定是否启用 mTLS:

if (args.length < 3 || args.length > 4) { System.out.println( "USAGE: HelloWorldServerTls port certChainFilePath privateKeyFilePath " + "[trustCertCollectionFilePath]\n Note: You only need to supply trustCertCollectionFilePath if you want " + "to enable Mutual TLS."); System.exit(0); } // If only providing a private key, you can use TlsServerCredentials.create() instead of // interacting with the Builder. TlsServerCredentials.Builder tlsBuilder = TlsServerCredentials.newBuilder() .keyManager(new File(args[1]), new File(args[2])); if (args.length == 4) { tlsBuilder.trustManager(new File(args[3])); tlsBuilder.clientAuth(TlsServerCredentials.ClientAuth.REQUIRE); } final HelloWorldServerTls server = new HelloWorldServerTls( Integer.parseInt(args[0]), tlsBuilder.build()); server.start(); server.blockUntilShutdown();

关键点:

  • TlsServerCredentials.newBuilder()是 api/src/main/java/io/grpc/TlsServerCredentials.java 提供的工厂方法;
  • keyManager(File certChain, File privateKey)(TlsServerCredentials.java)加载服务端证书链与私钥;
  • trustManager(File rootCerts)(TlsServerCredentials.java)加载用于校验客户端证书的 CA 集合;
  • clientAuth(TlsServerCredentials.ClientAuth.REQUIRE)(TlsServerCredentials.java)强制要求客户端出示证书——这是单向 TLS 与双向 mTLS 在服务端侧的本质区别;
  • 若只提供私钥而不需要额外配置,也可直接用TlsServerCredentials.create()便捷方法,这是源码注释中明确提到的简化路径。

服务端随后通过Grpc.newServerBuilderForPort(port, creds)创建带 TLS 凭据的 ServerBuilder(HelloWorldServerTls.java),这与普通明文服务的ServerBuilder.forPort(port)形成对比——TLS 凭据必须在构建 ServerBuilder 时传入,而不是运行时动态附加

源码剖析:客户端如何组装 TlsChannelCredentials

HelloWorldClientTls.java 的main方法通过参数个数区分三种场景(单向信任自定义 CA、双向认证、系统默认 CA):

if (args.length < 2 || args.length == 4 || args.length > 5) { System.out.println("USAGE: HelloWorldClientTls host port [trustCertCollectionFilePath " + "[clientCertChainFilePath clientPrivateKeyFilePath]]\n Note: clientCertChainFilePath and " + "clientPrivateKeyFilePath are only needed if mutual auth is desired."); System.exit(0); } // If only defaults are necessary, you can use TlsChannelCredentials.create() instead of // interacting with the Builder. TlsChannelCredentials.Builder tlsBuilder = TlsChannelCredentials.newBuilder(); switch (args.length) { case 5: tlsBuilder.keyManager(new File(args[3]), new File(args[4])); // fallthrough case 3: tlsBuilder.trustManager(new File(args[2])); // fallthrough default: } String host = args[0]; int port = Integer.parseInt(args[1]); ManagedChannel channel = Grpc.newChannelBuilderForAddress(host, port, tlsBuilder.build()) /* Only for using provided test certs. */ .overrideAuthority("foo.test.google.fr") .build();

要点解析:

  • 参数组合规则args.length == 2表示完全信任系统默认 CA(无需任何证书文件);args.length == 3表示自定义 trustManager(信任私有 CA);args.length == 5表示双向认证(追加客户端证书与私钥);args.length == 4属于非法组合,直接打印 USAGE 退出;
  • switch fallthrough 的巧妙复用case 5先加载客户端 keyManager,随后 fallthrough 到case 3加载 trustManager,一段代码覆盖两种场景;
  • 客户端最终通过Grpc.newChannelBuilderForAddress(host, port, creds)建立带 TLS 的 Channel,并调用blockingStub.sayHello(request)发起 RPC;捕获StatusRuntimeException记录失败状态(HelloWorldClientTls.java);
  • 类似地,若只需默认配置,可使用TlsChannelCredentials.create()便捷方法。

从源码结构看,TlsServerCredentialsTlsChannelCredentials是对称设计:服务端侧重keyManager+ 可选的trustManager+clientAuth策略,客户端侧重可选的trustManager+ 可选的keyManager。两者最终都转换为 Netty 底层的 SslContext 配置(示例的 Bazel 构建中显式依赖了io_netty_netty_tcnative_boringssl_staticio_netty_netty_tcnative_classes,见 BUILD.bazel,这是 Netty TLS 的 BoringSSL 原生实现)。

使用 Maven 构建与运行

如果偏好 Maven,example-tls同样提供了完整的 pom.xml。该 POM 通过protobuf-maven-plugin自动完成 protoc 与protoc-gen-grpc-java的代码生成(其中grpc.version1.85.0-SNAPSHOTprotoc.version3.25.8),并依赖grpc-protobufgrpc-stubgrpc-netty-shaded三个核心 artifact。

examples/example-tls目录下执行:

$ mvn verify $ # 运行服务端 $ mvn exec:java -Dexec.mainClass=io.grpc.examples.helloworldtls.HelloWorldServerTls -Dexec.args="50440 ../../testing/src/main/resources/certs/server1.pem ../../testing/src/main/resources/certs/server1.key" $ # 在另一个终端运行客户端 $ mvn exec:java -Dexec.mainClass=io.grpc.examples.helloworldtls.HelloWorldClientTls -Dexec.args="localhost 50440 ../../testing/src/main/resources/certs/ca.pem"

mvn verify会先编译并执行测试,确保生成的桩代码正确;之后两次mvn exec:java分别以HelloWorldServerTlsHelloWorldClientTls作为主类启动。注意 Maven 路径下证书的相对路径../../testing/...同样以examples/example-tls为当前工作目录计算。

使用 Bazel 构建与运行

仓库根目录基于 Bazel 构建,example-tls的 BUILD.bazel 定义了proto_libraryjava_proto_libraryjava_grpc_library的生成链,以及两个java_binary目标。运行方式如下:

$ bazel build :hello-world-tls-server :hello-world-tls-client $ # 运行服务端 $ ../bazel-bin/hello-world-tls-server 50440 ../../testing/src/main/resources/certs/server1.pem ../../testing/src/main/resources/certs/server1.key $ # 在另一个终端运行客户端 $ ../bazel-bin/hello-world-tls-client localhost 50440 ../../testing/src/main/resources/certs/ca.pem

Bazel 生成的二进制输出到仓库根目录的bazel-bin/,因此命令中需要../bazel-bin/...前缀(相对于examples/example-tls)。另外该 BUILD 文件中两个java_binary目标均标记为testonly = 1,说明官方将示例视为测试用途,生产代码不应直接引用。

常见问题与排查思路

  • 证书与主机名不匹配(UNAVAILABLE / SSL 握手失败):检查服务器证书 SAN 是否包含客户端连接的 host,或是否遗漏.overrideAuthority(...)
  • 私钥格式错误:gRPC 需要 PKCS#8 无加密私钥,用openssl pkcs8 -topk8 -in key.rsa -out key -nocrypt转换;
  • mTLS 客户端未提供证书:服务端clientAuth(REQUIRE)后,若客户端只传了trustCertCollectionFilePath(args.length == 3),握手会因缺少客户端证书而失败;
  • 使用真实证书时的简化:真实 CA 签发的证书可省略客户端trustCertCollectionFilePath,直接依赖系统默认 CA 存储;
  • 运行顺序:务必先启动服务端再启动客户端,这与普通 Hello World 示例的行为一致。

小结

本文完整还原了 examples/example-tls/README.md 的全部构建与运行流程,并深入到HelloWorldServerTlsHelloWorldClientTls的源码实现与TlsServerCredentials/TlsChannelCredentials的 Builder API。核心要点可归纳为三条:单向 TLS 只需服务端证书;双向 mTLS 需服务端clientAuth(REQUIRE)且客户端提供证书;测试证书必须配合overrideAuthority使用,而真实证书则可直接走系统默认 CA。无论使用 Gradle、Maven 还是 Bazel,example-tls都是一份可直接复制的 TLS 接入范本——只需将测试证书替换为正式签发的证书,即可无缝迁移到生产环境。

【免费下载链接】grpc-javaThe Java gRPC implementation. HTTP/2 based RPC项目地址: https://gitcode.com/GitHub_Trending/gr/grpc-java

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/15 13:46:49

纯前端图片格式转换:Canvas实现PNG/JPG/WebP互转与压缩实践

直接用 Canvas 做纯前端图片格式互转&#xff0c;这事儿听起来好像有点简单&#xff0c;但真正把它做成一个能用的工具&#xff0c;里面值得抠的细节其实不少。最近在项目里要处理用户上传图片的格式统一问题&#xff0c;我又把这套逻辑重新撸了一遍&#xff0c;顺手封装了一个…

作者头像 李华
网站建设 2026/9/15 13:46:32

(全新整理)地级市绿色数据中心DID2016-2025年

文章目录资料下载地址介绍01、数据介绍02、数据指标03、数据截图项目备注资料下载地址资料下载地址 点击这里下载资料 介绍 01、数据介绍 参考了Chao Li等&#xff08;2025&#xff09;等人文献&#xff0c;采用多期双重差分法来识别政策实施的净效应。构建核心交互项 DID&…

作者头像 李华
网站建设 2026/9/15 13:46:00

JavaScript与AI结合:2024年高效学习与开发指南

1. 为什么现在学JavaScript必须结合AI&#xff1f;十年前我刚入行时&#xff0c;JavaScript还只是用来做表单验证的小工具。如今这个语言已经渗透到从浏览器到服务器、从移动端到物联网的每个角落。更关键的是&#xff0c;AI技术正在彻底改变我们编写和理解代码的方式。上周我团…

作者头像 李华
网站建设 2026/9/15 13:45:11

UE4 VR蓝图交互开发实战:从设备映射到动态材质

在VR项目的实习里&#xff0c;我大部分时间都泡在UE4的蓝图里&#xff0c;从人物移动、物件抓取、到触发机关、动态换材质&#xff0c;几乎把交互相关的常用事件都摸了个遍。这篇总结就是把这些实战经验整理出来&#xff0c;适合刚接触虚拟现实开发、正准备做交互Demo、或者马上…

作者头像 李华
网站建设 2026/9/15 13:44:44

AI如何重塑销售行业:工具、能力与实战案例

1. AI时代下的销售两极分化现象最近和几位做销售的朋友聊天&#xff0c;发现一个很有意思的现象&#xff1a;同样使用AI工具&#xff0c;有人业绩翻了三倍&#xff0c;有人却面临被淘汰的风险。这让我想起一个业内流传的说法&#xff1a;"AI面前&#xff0c;销售只剩下两种…

作者头像 李华