2023-12-06
grpc
00
请注意,本文编写于 351 天前,最后修改于 350 天前,其中某些信息可能已经过时。

目录

1. Protocol Buffer 编译器和相关依赖安装(mac版)
2. grpc 项目开发的基本步骤
2.1 protos目录结构
2.1.1 最初的项目目录树(当前目录包含user一个子目录 和 group 子目录)
2.1.2 user.proto 内容如下
2.1.3 group.proto 内容如下:
2.2 生成客户端存根或服务端骨架
2.3 现在的protos目录树, 将该目录拷贝到grpc C/S 端备用
2.4 一个简单的server端
2.5 对应的client端
2.6 client发送grpc请求到server端,server端返回响应
2.7 使用mTLS grpc C/S端通信信道加密
2.7.1 生成ca证书和C/S端证书
2.7.1.1 生成ca根证书
2.7.1.2 生成服务端证书
2.7.1.3 生成客户端证书
2.7.1.4 此时所有的证书及文件如下
2.7.1.5 这是一个生成上面所有证书的脚本文件,请按需修改
2.7.2 引入证书创建C/S端加密通信
2.7.2.1 服务端配置
2.7.2.2 client端配置
2.8 项目所有源代码:
2.9 todo: 使用JWT认证grpc请求

本文档以一个实例,演示了grpc从0到0.5的过程. 使用go代码实现。全部源代码在文尾。

grpc官方文档

1. Protocol Buffer 编译器和相关依赖安装(mac版)

shell
# mac os brew install protobuf protoc --version # Ensure compiler version is 3+ libprotoc 25.1 go install google.golang.org/protobuf/cmd/protoc-gen-go@latest go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest

其他版本os的安装请参考文档: https://grpc.io/docs/protoc-installation/

2. grpc 项目开发的基本步骤

2.1 protos目录结构

2.1.1 最初的项目目录树(当前目录包含user一个子目录 和 group 子目录)

shell
├── proto │   ├── group │   │   └── group.proto │   └── user │   └── user.proto

2.1.2 user.proto 内容如下

go
syntax = "proto3"; package user; option go_package = "./user"; service UserService{ rpc CreateUser (User) returns (User) {} } message UserID{ int32 value = 1; } message UserName{ string value = 1; } message User { int32 id = 1; string name = 2; string password = 3; string email = 4; string phone = 5; int32 status = 7; int32 role = 8; int64 createTime = 9; int32 theme = 10; int32 language = 11; } message Empty { }

2.1.3 group.proto 内容如下:

go
syntax = "proto3"; package group; option go_package = "./group"; service GroupService{ rpc CreateGroup (Group) returns (Group) {} } message GroupID{ int32 value = 1; } message GroupName{ string value = 1; } message Group { int32 id = 1; string name = 2; } message Empty { }

2.2 生成客户端存根或服务端骨架

shell
# 在当前根目录下执行命令 # 会生成 'user_grpc.pb.go ''user.pb.go'文件 # 以及 'group_grpc.pb.go ''group.pb.go'文件 # 每次对 proto文件的修改,都要重新执行该命令 # user protoc --go_out=. --go_opt=paths=source_relative \ --go-grpc_out=. --go-grpc_opt=paths=source_relative \ user/user.proto # group protoc --go_out=. --go_opt=paths=source_relative \ --go-grpc_out=. --go-grpc_opt=paths=source_relative \ group/group.proto # 将整个 proto 目录库拷贝到 客户端备用

2.3 现在的protos目录树, 将该目录拷贝到grpc C/S 端备用

shell
├── proto │   ├── group │   │   ├── group.pb.go │   │   ├── group.proto │   │   └── group_grpc.pb.go │   └── user │   ├── user.pb.go │   ├── user.proto │   └── user_grpc.pb.go

2.4 一个简单的server端

shell
. ├── aipity-grpc.log ├── aipity.go ├── certs │   ├── ca.crt │   ├── server.crt │   └── server.key ├── cmd │   └── aipity │   └── main.go ├── generate-all-certs.sh ├── go.mod ├── go.sum ├── proto │   ├── group │   │   ├── group.pb.go │   │   ├── group.proto │   │   └── group_grpc.pb.go │   └── user │   ├── user.pb.go │   ├── user.proto │   └── user_grpc.pb.go └── service ├── group │   └── group.go └── user └── user.go

2.5 对应的client端

shell
. ├── aipity-grpc.log ├── aipity.go ├── certs │   ├── ca.crt │   ├── client.crt │   └── client.key ├── cmd │   └── aipity │   └── main.go ├── go.mod ├── go.sum ├── proto │   ├── group │   │   ├── group.pb.go │   │   ├── group.proto │   │   └── group_grpc.pb.go │   └── user │   ├── user.pb.go │   ├── user.proto │   └── user_grpc.pb.go └── service ├── group │   └── group.go └── user └── user.go

2.6 client发送grpc请求到server端,server端返回响应

shell
# 后端收到的前端请求 2023/12/05 22:53:03 grpc server with mTLS enabled listening at [::]:50051 2023/12/05 22:53:07 user create in=name:"aipity" password:"123456" email:"whsasf@aipity.com" phone:"111111111" status:1 role:1 createTime:1701787987 theme:1 language:1 2023/12/05 22:53:07 group create in=id:1 name:"aipity" # 客户端收到的后端返回 2023/12/05 22:53:07 creatd user: name:"aipity" password:"123456" email:"whsasf@aipity.com" phone:"111111111" status:1 role:1 createTime:1701787987 theme:1 language:1 added successfully 2023/12/05 22:53:07 created group: id:1 name:"aipity" added successfully

2.7 使用mTLS grpc C/S端通信信道加密

在项目根目录下创建一个certs目录,所有下面的命令都在该目录下执行。建议使用后面的shell脚本一次性生成所有证书。

2.7.1 生成ca证书和C/S端证书

2.7.1.1 生成ca根证书

shell
# 1. 生成 ca.key openssl genrsa -aes256 -out ca.key 4096 # PEM pass: aipity # 2. 成成 ca.crt, 10年有效期 openssl req -new -x509 -sha256 -days 3650 -key ca.key -out ca.crt # Common Name (e.g. server FQDN or YOUR name) []:aipity # 其余使用 . # 3. 输出证书内容() openssl x509 -noout -text -in ca.crt # 输出 Certificate: Data: Version: 3 (0x2) Serial Number: 7f:c0:65:c0:9f:68:41:d1:47:59:9c:bb:b9:5c:8c:1b:73:43:97:f0 Signature Algorithm: sha256WithRSAEncryption Issuer: CN=aipity Validity Not Before: Dec 4 07:33:03 2023 GMT Not After : Dec 1 07:33:03 2033 GMT Subject: CN=aipity Subject Public Key Info: Public Key Algorithm: rsaEncryption Public-Key: (4096 bit) Modulus: 00:aa:9d:cc:0a:cc:69:81:d6:8d:4f:07:2e:21:47: 02:f7:cb:0f:8b:31:45:13:3b:d5:1b:27:1f:42:76: 53:83:2e:34:c1:31:d6:02:a2:f1:c3:8e:4f:8d:d5: 21:a1:af:b0:3f:e1:4c:1f:44:58:70:a0:fc:22:bf: a1:ab:39:aa:66:7a:0e:71:d1:9e:8b:3c:7c:c1:71: 2c:2c:63:28:b3:f7:a0:7c:30:fd:2e:49:64:d0:1a: f3:44:b5:dc:db:f5:de:d6:ca:46:9c:92:d3:6a:d1: fb:20:45:c1:9c:7b:ab:b9:5f:dd:b6:b2:94:62:6f: b3:7d:41:e9:1e:f1:77:16:47:63:a4:73:6a:43:eb: 81:fc:ea:32:5e:e8:18:67:17:0f:14:f7:06:a8:42: 0f:ab:a2:fe:cc:f6:64:10:af:81:b6:0b:0d:d6:48: 06:5b:70:df:b8:ba:f2:40:d5:b1:60:6f:b5:37:50: 6b:ea:e4:6e:6e:26:9c:c8:fc:22:90:6d:c6:e3:c5: 6f:f5:f4:ee:e9:2d:19:40:b5:51:33:b7:1a:7d:f2: 1a:5c:08:e5:77:ae:47:f3:d0:02:05:06:f1:8a:fd: 95:88:22:60:39:e3:65:9d:66:01:df:8f:df:91:7d: 0a:bd:a2:9b:59:a2:db:8e:ef:24:38:13:b4:a0:5b: 8e:85:b1:3a:4f:c0:e5:18:ad:6f:1b:4b:0d:05:99: 2d:ad:05:a8:9d:92:f2:61:f3:40:08:df:da:cb:48: 6b:37:7b:37:c3:d1:8d:63:24:99:f1:14:69:87:fd: b2:ca:44:06:66:fb:e2:6c:5f:c1:1b:33:f5:12:d8: 64:48:05:6a:1c:d1:63:35:42:ed:50:af:03:f0:44: 59:7d:54:f9:b8:f2:e3:c1:f7:45:51:b2:84:48:45: d3:bd:a9:5e:2d:03:c5:a5:cf:f4:31:c1:ef:19:b1: da:df:ac:da:fa:d6:36:b5:7c:be:b3:76:db:0c:45: a4:a6:8e:cf:fc:a3:48:27:bc:ff:80:a7:2d:ad:5d: 41:47:37:64:b4:4c:bf:d2:6c:ef:15:8a:7e:ae:0b: 8f:13:7c:fa:16:ad:20:29:ef:41:79:b6:d0:0a:22: fd:56:1a:d6:2e:67:76:35:e8:6d:0f:10:48:0f:1d: 0e:6e:b0:2f:4f:6d:27:32:79:e5:85:50:13:04:41: 9f:19:50:9d:37:24:c6:64:d0:9b:e1:15:f9:f1:2f: fd:f5:ff:c9:51:1b:44:78:c7:01:5e:20:a7:6a:74: 0c:a8:82:44:dd:8e:3e:4b:7f:ae:e9:86:a9:0b:e8: de:5d:a6:f4:b1:cc:78:12:91:d3:63:c8:bf:8b:9b: 96:7a:c7 Exponent: 65537 (0x10001) X509v3 extensions: X509v3 Subject Key Identifier: 9E:4E:DC:F6:0F:32:6B:FA:EF:1D:66:58:60:CE:6C:91:29:10:A5:36 X509v3 Authority Key Identifier: 9E:4E:DC:F6:0F:32:6B:FA:EF:1D:66:58:60:CE:6C:91:29:10:A5:36 X509v3 Basic Constraints: critical CA:TRUE Signature Algorithm: sha256WithRSAEncryption Signature Value: 55:3f:d3:6e:fc:0d:2f:df:67:05:c0:94:06:26:77:38:0d:56: 11:fe:f5:6c:2f:4d:bc:29:5a:1a:ac:cd:7a:99:db:3c:24:01: 25:72:6b:4f:70:2d:78:f1:0c:34:a8:da:0b:b8:b9:3c:82:3f: 70:ea:64:ef:1a:05:71:aa:60:0c:6a:2a:27:08:9f:be:1b:54: e7:2c:14:da:d6:03:eb:91:a3:ac:15:f8:e2:bc:a2:0c:5f:41: f6:09:4c:ea:22:83:dc:46:20:de:04:a7:8a:56:02:cd:cf:ed: c0:a1:d4:fe:78:f7:0b:fa:8a:14:bf:89:08:b9:62:ab:06:cf: 4e:5b:44:72:4a:75:02:37:19:4f:f6:a4:e4:f1:90:5a:02:6c: 48:85:39:a8:95:35:18:d8:56:fa:6d:b4:8b:66:5f:c4:91:b0: 9e:9d:af:6a:f2:1e:df:ea:b2:0e:c4:54:e9:7c:50:d7:a9:11: 5a:85:dd:c2:fd:4d:d1:3a:ad:60:8d:58:50:dd:3b:0b:a2:00: c5:a1:c4:75:26:ef:a9:bc:97:af:15:2b:58:a8:8b:25:46:2d: 60:e3:4b:82:fc:14:51:85:56:c2:74:ac:9f:d4:aa:d8:9b:22: c0:1b:55:af:ea:03:a4:31:d3:a9:85:68:4f:ef:94:f9:7c:c0: f4:e1:09:6d:d5:a4:f9:4d:44:51:fd:c9:8c:14:77:43:5b:0a: 83:02:6d:54:2a:61:60:b1:e2:d0:5c:b2:38:24:4e:88:86:c1: e0:a1:80:b2:4c:30:a0:7d:da:bd:5d:f8:2c:9f:86:d8:9d:eb: eb:cd:de:3b:01:0b:fc:78:55:74:70:b5:0f:31:19:61:06:c7: 06:44:30:43:ec:43:d6:0f:43:3a:50:a9:7f:2f:72:a0:b3:be: 06:5e:db:37:d5:87:51:2f:ec:9d:45:ec:10:79:d6:57:ca:5f: 3a:90:32:ed:84:65:36:7d:fd:0f:25:86:8e:ea:a3:8f:fe:3c: aa:ed:11:d3:6b:6d:0a:ee:de:11:0f:35:92:60:57:f7:cd:ef: a8:07:49:42:42:ba:e1:5d:3d:53:bb:c2:cd:97:79:a6:d8:18: bb:15:ff:c4:26:bc:05:69:e1:ef:15:76:25:a0:07:29:6f:ae: 58:61:87:6a:34:f9:58:a8:89:d1:2b:41:7e:3d:6d:60:ad:1b: 9f:a7:a5:b0:23:76:b4:bc:0c:d3:92:1f:c4:65:60:62:e9:63: ce:cb:20:84:39:60:30:3c:4c:52:8a:79:f2:75:d8:0a:db:0c: 78:ee:6c:82:9c:99:17:b7:4d:7d:6c:98:97:cd:0e:9c:98:00: 73:9a:99:f0:f6:20:d8:2c

2.7.1.2 生成服务端证书

shell
# 1. 此命令会成成 server.key openssl genrsa -out server.key 2048 # 2. 此命令会生成 server.csr openssl req -new -sha256 -key server.key -out server.csr # Common Name (e.g. server FQDN or YOUR name) []:aipity # A challenge password []:localhost # 其余 . # 3. 此命令会生成 server.crt openssl x509 -req -days 3650 -sha256 -in server.csr -CA ca.crt -CAkey ca.key -set_serial 1 -out server.crt # 输入密码: aipity

2.7.1.3 生成客户端证书

shell
# 1. 此命令会生成 client.key openssl genrsa -out client.key 2048 # 2. 此命令会生成client.csr openssl req -new -key client.key -out client.csr # Common Name (e.g. server FQDN or YOUR name) []:localhost # A challenge password []:aipity # 其余 . # 3. 此命令会生成 client.crt openssl x509 -req -days 3650 -sha256 -in client.csr -CA ca.crt -CAkey ca.key -set_serial 2 -out client.crt # 输入密码: aipity

2.7.1.4 此时所有的证书及文件如下

shell
. ├── ca.crt # 拷贝到client/server 项目目录下使用 ├── ca.key # 该文件需要单独保密保存, 不要拷贝到任何项目目录 ├── client.crt # 拷贝到client 项目目录下使用 ├── client.csr ├── client.key # 拷贝到client 项目目录下使用 ├── server.crt # 拷贝到server 项目目录下使用 ├── server.csr └── server.key # 拷贝到server 项目目录下使用

2.7.1.5 这是一个生成上面所有证书的脚本文件,请按需修改

shell
#!/bin/bash # 2023.12.01 by whsasfdefy@gmail.com # 设置变量 DOMAIN="localhost" #域名 CERT_DIR="certs" #目录 COUNTRY="CN" #国家 STATE="SHANGHAI" #省/州 CITY="SHANGHAI" #城市 ORG_NAME="AIPITY" #组织 EMAIL="admin@aipity.com" #邮箱 KEY_SIZE="4096" DAYS="3650" #有效期10年 PASS="aipity" #PEM 密码 PASSPHRASEFILE="$CERT_DIR/passphrase.txt" # 如果用户指定了域名,则使用用户指定的域名 if [ -n "$1" ]; then DOMAIN="$1" fi mkdir $CERT_DIR # 创建certs 目录 echo "DOMAIN is: $DOMAIN" echo "################ 1. 创建创建PASSPHRASEFILE ###############" # 创建PASSPHRASEFILE touch $PASSPHRASEFILE echo $PASS > $PASSPHRASEFILE # 生成CA证书 echo "################ 2. 生成CA根证书 ########################" openssl genrsa -aes256 -out $CERT_DIR/ca.key --passout file:$CERT_DIR/passphrase.txt $KEY_SIZE openssl req -new -x509 -sha256 -days $DAYS -key $CERT_DIR/ca.key -out $CERT_DIR/ca.crt -subj "/C=$COUNTRY/ST=$STATE/L=$CITY/O=$ORG_NAME/CN=$DOMAIN/emailAddress=$EMAIL" --passin file:$CERT_DIR/passphrase.txt # 生成服务端证书 echo "################ 3. 生成服务端证书 #######################" openssl genrsa -out $CERT_DIR/server.key $KEY_SIZE openssl req -new -sha256 -key $CERT_DIR/server.key -out $CERT_DIR/server.csr -subj "/C=$COUNTRY/ST=$STATE/L=$CITY/O=$ORG_NAME/CN=$DOMAIN/emailAddress=$EMAIL" --passin file:$CERT_DIR/passphrase.txt openssl x509 -req -days $DAYS -sha256 -in $CERT_DIR/server.csr -CA $CERT_DIR/ca.crt -CAkey $CERT_DIR/ca.key -set_serial 1 -out $CERT_DIR/server.crt -extfile <(printf "subjectAltName=DNS:$DOMAIN") --passin file:$CERT_DIR/passphrase.txt # 输出证书内容 echo "生成的服务端证书为:" echo "" openssl x509 -noout -text -in $CERT_DIR/server.crt # # 生成客户端证书 echo "################ 4. 生成客户端证书 #######################" openssl genrsa -out $CERT_DIR/client.key $KEY_SIZE openssl req -new -key $CERT_DIR/client.key -out $CERT_DIR/client.csr -subj "/C=$COUNTRY/ST=$STATE/L=$CITY/O=$ORG_NAME/CN=$DOMAIN/emailAddress=$EMAIL" --passin file:$CERT_DIR/passphrase.txt openssl x509 -req -days $DAYS -sha256 -in $CERT_DIR/client.csr -CA $CERT_DIR/ca.crt -CAkey $CERT_DIR/ca.key -set_serial 2 -out $CERT_DIR/client.crt -extfile <(printf "subjectAltName=DNS:$DOMAIN") --passin file:$CERT_DIR/passphrase.txt # 输出证书内容 echo "生成的客户端证书为:" echo "" openssl x509 -noout -text -in $CERT_DIR/client.crt

2.7.2 引入证书创建C/S端加密通信

2.7.2.1 服务端配置

go
func main() { flag.Parse() // mTLS settings certificate, err := tls.LoadX509KeyPair(crtFile, keyFile) if err != nil { log.Fatalf("failed to load key pair: %s", err) } // Create a certificate pool from the certificate authority certPool := x509.NewCertPool() ca, err := os.ReadFile(caFile) if err != nil { log.Fatalf("could not read ca certificate: %s", err) } // Append the client certificates from the CA if ok := certPool.AppendCertsFromPEM(ca); !ok { log.Fatalf("failed to append client certs") } opts := []grpc.ServerOption{ // Enable TLS for all incoming connections. grpc.Creds( // Create the TLS credentials credentials.NewTLS(&tls.Config{ ClientAuth: tls.RequireAndVerifyClientCert, Certificates: []tls.Certificate{certificate}, ClientCAs: certPool, }, )), } s := grpc.NewServer(opts...) lis, err := net.Listen("tcp", fmt.Sprintf(":%d", *port)) if err != nil { log.Fatalf("failed to listen: %v", err) } pb_user.RegisterUserServiceServer(s, &user.Server{}) pb_group.RegisterGroupServiceServer(s, &group.Server{}) log.Printf("grpc server with mTLS enabled listening at %v", lis.Addr()) if err := s.Serve(lis); err != nil { log.Fatalf("failed to serve: %v", err) } }

2.7.2.2 client端配置

go
func main() { // Load the client certificates from disk certificate, err := tls.LoadX509KeyPair(crtFile, keyFile) if err != nil { log.Fatalf("could not load client key pair: %s", err) } // Create a certificate pool from the certificate authority certPool := x509.NewCertPool() ca, err := os.ReadFile(caFile) if err != nil { log.Fatalf("could not read ca certificate: %s", err) } // Append the certificates from the CA if ok := certPool.AppendCertsFromPEM(ca); !ok { log.Fatalf("failed to append ca certs") } // log.SetFlags file, _ := os.OpenFile("aipity-grpc.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) log.SetOutput(io.MultiWriter(file, os.Stdout)) opts := []grpc.DialOption{ // transport credentials. grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{ ServerName: hostname, // NOTE: this is required! Certificates: []tls.Certificate{certificate}, RootCAs: certPool, })), } conn, err := grpc.Dial(address, opts...) if err != nil { log.Fatalf("did not connect to grpc server: %v", err) } defer conn.Close() c := pb_user.NewUserServiceClient(conn) // Contact the server and print out its response. // 1. createuser name := "aipity" password := "123456" email := "whsasf@aipity.com" phone := "111111111" var status int32 = 1 var role int32 = 1 createTime := time.Now().Unix() var theme int32 = 1 var language int32 = 1 ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() r, err := c.CreateUser(ctx, &pb_user.User{ Name: name, Password: password, Email: email, Phone: phone, Status: status, CreateTime: createTime, Theme: theme, Role: role, Language: language, }) if err != nil { log.Fatalf("Could not create user: %v", err) } log.Printf("creatd user: %s added successfully", r) // 2. create group g := pb_group.NewGroupServiceClient(conn) gname := "aipity" gid := 1 t, err := g.CreateGroup(ctx, &pb_group.Group{ Name: gname, Id: int32(gid), }) if err != nil { log.Fatalf("Could not create group: %v", err) } log.Printf("created group: %s added successfully", t) }

2.8 项目所有源代码:

GitHub - ytipia/aipity-grpc-demo: A grpc practice example

2.9 todo: 使用JWT认证grpc请求

如果对你有用的话,可以打赏哦
打赏
ali pay
wechat pay

本文作者:王海生

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 MIT 许可协议。转载请注明出处!