在上文我们通过Java来实现了Grpc的使用,但是有一点我们注意到,虽然使用的是HTTP/2协议进行通信的,但是为什么我们使用的是PLAINTEXT,HTTP/2的实现中间不是有一层TLS/1.2+吗,没证书还能给我整上加密了???
在这里插入图片描述

其实在HTTP/2的实现当中有一种h2c的协议升级方式,这种方式就是不带有加密TLS的协议。

H2C明文HTTP/2

普通浏览器实现HTTP/2需要先使用HTTP/1.x来请求服务器,在获取到服务器Upgrade:h2c(明文)返回后来转换协议

客户端                                               服务端
│ │
├─────── 发起 HTTP/1.1 请求 ──────────────────────────>│
│ GET / HTTP/1.1 │
│ Host: server.example.com │
│ Connection: Upgrade, HTTP2-Settings │
│ Upgrade: h2c │
│ HTTP2-Settings: <base64> │
│ │
│ │
│<────── 响应 101 Switching Protocols ─────────────────┤
│ HTTP/1.1 101 Switching Protocols │
│ Connection: Upgrade │
│ Upgrade: h2c │
│ │
│ │
│<────── 发送 HTTP/2 Server Preface (SETTINGS) ────────┤
│ PRI * HTTP/2.0\r\n │
│ \r\n │
│ SM\r\n │
│ \r\n │
│ SETTINGS frame │
│ │
│ │
│────── 发送 HTTP/2 Client Preface (SETTINGS) ────────>│
│ PRI * HTTP/2.0\r\n │
│ \r\n │
│ SM\r\n │
│ \r\n │
│ SETTINGS frame │
│ │
│ │
│<────── 响应流 stream 1 (初始请求响应) ─────────────────┤
│ HEADERS + (可选 DATA) frames │
│ │
│ │
│<────── 后续 HTTP/2 请求/响应帧交换 ────────────────────│
│ HEADERS + DATA frames for new streams │
│ │

客户端的前言内容包含一个内容为 PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n 的序列加上一个可以为空的 SETTINGS 帧,在收到 101(Switching Protocols) 响应 (代表 upgrade 成功) 后发送,或者作为 TLS 连接的第一个传输的应用数据。

如果在预先知道服务端支持 HTTP/2 的情况下启用 HTTP/2 连接,客户端连接前言在连接建立时发送。

我们的GRPC就是已知直接使用HTTP/2的情形,所以在建立连接的时候不需要HTTP/1.X来升级协议,在TCP握手之后直接使用Magic帧来确定协议

image-20250623140808811

H2 TLS加密的HTTP/2

当然对于带有TLS的HTTP/2就不会通过HTTP/1.X来升级协议,他们会直接使用HTTPS来协商

HTTPS需要经过一个协议协商阶段来建立连接,在建立连接并交换HTTP消息之前,它们需要协商 SSL/TLS协议、加密的密码,以及其他的设置。这个过程比较灵活,可以引入新的HTTPS协议和密码,只要客户端和服务端都支持就行。在HTTPS握手的过程中,可以同时完成HTTP/2协商,这就不需要在建立连接时增加一次跳转。

上面提到的协商协议阶段就是使用ALPN来进行协商使用的协议

image-20250623163619503

  1. 客户端发起 TLS 握手:

    客户端在发起 TLS 握手请求时,会在 “ClientHello” 消息中包含一个 ALPN 扩展字段,列出它支持的协议列表,例如 http/1.1 和 h2(HTTP/2 的标识符)。

  2. 服务器响应:

    服务器收到 “ClientHello” 消息后,在 “ServerHello” 消息中包含一个 ALPN 扩展字段,选择并响应一个客户端支持的协议,例如 h2,如果服务器支持 HTTP/2 并且客户端也支持。

  3. 建立 TLS 连接:

    协商完成后,客户端和服务器按照协商的协议版本进行通信,并完成 TLS 握手过程。

这是正常HTTP/2的协议流程

使用TLS加密的Grpc

那我们该如何才能让我们的grpc使用TLS加密呢

前期准备

将配置的grpc.client.feignName.negotiationType=PLAINTEXT删掉,配置这种情况表示走明文

需要让我们的服务支持HTTPS

我这边使用的是通义零码直接帮我做的

  1. 首先创建一个生成证书的配置,csm-csm是你服务在注册中心的名字,当然还有dns2,直接指向注册中心后面的域名也可以
    [req]
    default_bits = 2048
    prompt = no
    default_md = sha256
    distinguished_name = dn
    req_extensions = v3_req

    [dn]
    C = CN
    ST = Beijing
    L = Beijing
    O = Baiwang
    OU = IT
    CN = csm-csm

    [v3_req]
    basicConstraints = CA:FALSE
    keyUsage = nonRepudiation, digitalSignature, keyEncipherment
    subjectAltName = @alt_names

    [alt_names]
    DNS.1 = csm-csm
    DNS.2 = localhost
    DNS.3 = 127.0.0.1
  2. 通过配置文件生成证书
    openssl req -new -x509 -newkey rsa:2048 -sha256 -keyout server.key -out server.crt -days 365 -nodes -config openssl.cnf -extensions v3_req
  3. 查看证书创建是否准确
    # 验证生成的证书是否包含正确的SAN
    openssl x509 -in server.crt -text -noout | grep -A5 "Subject Alternative Name"
    # 返回内容
    X509v3 Subject Alternative Name:
    DNS:csm-csm, DNS:localhost, DNS:127.0.0.1
    X509v3 Subject Key Identifier:
    CB:6D:C5:23:E1:99:CC:B3:74:DC:B3:8F:5C:1D:0A:05:2A:EE:18:CB
    Signature Algorithm: sha256WithRSAEncryption
    Signature Value:
    # 检查证书的完整信息
  4. 配置服务端的Grpc
    grpc:
    server:
    port: 9099
    security:
    enabled: true
    certificate-chain: classpath:certificates/server.crt
    private-key: classpath:certificates/server.key

配置客户端的Grpc

grpc:
client:
csm-csm:
address: 'discovery:///csm-csm'
negotiation-type: TLS
security:
trust-cert-collection: classpath:certificates/server.crt

这样我们在使用grpc调用的时候就是带证书的访问了

未使用TLS

image-20250623134632489

使用TLS

image-20250728161921558

image-20250728162150689

这两张图片完整的表示了HTTP/2的ALPN

因为被证书加密了,我们也无法看到传输的具体内容,甚至请求URL也看不到了(HTTPS的是可以看到的,我并不确定这两者的区别)

延展阅读

  1. HTTP2 详解

  2. Starting HTTP/2

  3. not an SSL/TLS record

  4. HTTP/2 的 ALPN(应用层协议协商)