本文档以一个实例,演示了grpc从0到0.5的过程. 使用go代码实现。全部源代码在文尾。
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/
shell├── proto │ ├── group │ │ └── group.proto │ └── user │ └── user.proto
gosyntax = "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 {
}
gosyntax = "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 {
}
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 目录库拷贝到 客户端备用
shell├── proto │ ├── group │ │ ├── group.pb.go │ │ ├── group.proto │ │ └── group_grpc.pb.go │ └── user │ ├── user.pb.go │ ├── user.proto │ └── user_grpc.pb.go
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
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
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
在项目根目录下创建一个certs目录,所有下面的命令都在该目录下执行。建议使用后面的shell脚本一次性生成所有证书。
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
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
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
shell. ├── ca.crt # 拷贝到client/server 项目目录下使用 ├── ca.key # 该文件需要单独保密保存, 不要拷贝到任何项目目录 ├── client.crt # 拷贝到client 项目目录下使用 ├── client.csr ├── client.key # 拷贝到client 项目目录下使用 ├── server.crt # 拷贝到server 项目目录下使用 ├── server.csr └── server.key # 拷贝到server 项目目录下使用
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
gofunc 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)
}
}
gofunc 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)
}
GitHub - ytipia/aipity-grpc-demo: A grpc practice example
本文作者:王海生
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 MIT 许可协议。转载请注明出处!