grpc-go在使用中,可能会遇到一些缺少工具的问题,具体参看往期文章:https://www.madbull.site/?p=1453
官方示例
在grpc-go代码中,有一个示例,给出了服务端和客户端证书双向认证的实现。可以在这里 https://github.com/grpc/grpc-go 拉取代码,示例在 examples/features/encryption/mTLS 目录中。
本文基于官方给的 mTLS 示例,对 helloworld示例(也是官方的示例,具体参看官网文章:https://grpc.io/docs/languages/go/quickstart/)进行修改,实现客户端和服务端双向认证,并做了一些注释以作备忘。
第一步:准备证书
1.1、证书创建
双向认证需要4个证书:
- 服务端CA证书:用来给服务端颁发证书的证书,此证书客户端必须信任,因为客户端需要用此证书来验证服务端证书的合法性。
- 服务端证书:由服务端CA证书颁发,客户端连接时,服务端把此证书发送给客户端,由客户端来验证合法性。
- 客户端CA证书:用来给客户端颁发证书的证书,此证书服务端必须信任,因为服务端需要用此证书来验证客户端证书的合法性。
- 客户端证书:由客户端CA证书颁发,客户端连接时,客户端把此证书发送给服务端,由服务端来验证合法性。
以上4个证书,其中服务端CA证书和客户端CA证书可以用同一个,所以,至少需要3个证书也可以。本文章示例就是用的3个证书做的示范。
关于证书颁发,参看往期文章:https://www.madbull.site/?p=1111。颁发客户端证书没有特殊要求,颁发服务端证书对DNS配置和IP配置有一些要求。配置文件详细内容如下:
[req]
default_bits = 2048
prompt = no
default_md = sha256
distinguished_name = req_distinguished_name
req_extensions = v3_req
[req_distinguished_name]
countryName = CN
stateOrProvinceName = Shandong
localityName = Qingdao
organizationName = madbull
organizationalUnitName = IT Department
commonName = node9
emailAddress = xxx@xxx.com
[v3_req]
subjectAltName = @alt_names
[alt_names]
DNS.1 = node9
DNS.2 = localhost
IP.1 = 127.0.0.1
IP.2 = 192.168.1.57
1.2、证书存放位置:

由于本次示例在本地主机做的测试,所以客户端和服务端的密钥对放在一起来了,对于跨主机的操作,目录应该是下面这个样子的:
服务端:

客户端:

第二步:代码
2.1、目录结构

2.2、接口文件
helloworld/helloworld.proto文件:
syntax = "proto3";
option go_package = "google.golang.org/grpc/project/helloworld";
option java_multiple_files = true;
option java_package = "io.grpc.project";
option java_outer_classname = "HelloWorldProto";
package helloworld;
// The greeting service definition.
service Greeter {
// Sends a greeting
rpc SayHello (HelloRequest) returns (HelloReply) {}
rpc SayHelloAgain (HelloRequest) returns (HelloReply) {}
}
// The request message containing the user's name.
message HelloRequest {
string name = 1;
}
// The response message containing the greetings
message HelloReply {
string message = 1;
}
2.3、服务端代码
server/main.go文件:
// Package main implements a server for Greeter service.
package main
import (
"context"
"flag"
"fmt"
"log"
"net"
"crypto/tls"
"crypto/x509"
"os"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
pb "google.golang.org/grpc/project/helloworld"
)
var (
port = flag.Int("port", 50051, "The server port")
)
// server is used to implement helloworld.GreeterServer.
type server struct {
pb.UnimplementedGreeterServer
}
// SayHello implements helloworld.GreeterServer
func (s *server) SayHello(_ context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
log.Printf("Received: %v", in.GetName())
return &pb.HelloReply{Message: "Hello " + in.GetName()}, nil
}
func (s * server) SayHelloAgain( ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
return &pb.HelloReply{Message: "Hello again " + in.GetName() }, nil
}
// 生成服务端 tls 配置
func GetServTlsConfig() *tls.Config {
// 1、加载服务的证书和私钥对
certs, err := tls.LoadX509KeyPair("/opt/certs/public.crt", "/opt/certs/private.key")
if err != nil {
log.Fatalf("failed to load key pair: %q", err)
}
// 2、加载信任的根证书
// 2.1、定义一个证书池
ca := x509.NewCertPool()
caFilePath := "/opt/certs/CAs/myCA.crt"
// 2.2、读取证书内容
caBytes, err := os.ReadFile(caFilePath)
if err != nil {
log.Fatalf("failed to read ca cert %q, %v", caFilePath, err)
}
// 2.3、把证书添加到证书池
if ok := ca.AppendCertsFromPEM(caBytes); !ok {
log.Fatalf("failed to parse %q", caFilePath)
}
// 3、tls配置设置
tlsConfig := &tls.Config{
ClientAuth: tls.RequireAndVerifyClientCert, // 验证策略:要求验证客户端证书。
Certificates: []tls.Certificate {certs}, // 服务的证书和私钥对
ClientCAs: ca, // 用于验证客户端证书的信任的根证书
}
return tlsConfig
}
func main() {
flag.Parse()
tlsConfig := GetServTlsConfig()
// 4、生成grpc服务
// 4.1、根据配置生成证书认证模块
creds := credentials.NewTLS(tlsConfig) ;
serv := grpc.NewServer(grpc.Creds(creds))
// 4.2、用生成的 grpc 服务 serv 来 注册Greeter服务
pb.RegisterGreeterServer(serv, &server{})
// 5、设置tcp监听
lis, err := net.Listen("tcp", fmt.Sprintf(":%d", *port))
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
// 5.1、服务绑定监听
if err := serv.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
2.4、客户端代码
client/main.go文件:
// Package main implements a client for Greeter service.
package main
import (
"flag"
"log"
"os"
"time"
"context"
"crypto/tls"
"crypto/x509"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
pb "google.golang.org/grpc/project/helloworld"
)
// 全局变量,默认值
const (
defaultName = "world"
)
// 设置参数
var (
addr = flag.String("addr", "localhost:50051", "the address to connect to")
name = flag.String("name", defaultName, "Name to greet")
)
// 生成客户端 tls 配置
func GetCliTlsConfig() *tls.Config {
// 1、加载客户端证书和私钥对
certs, err := tls.LoadX509KeyPair("/opt/certs/clnt.crt", "/opt/certs/clnt.key")
if err != nil {
log.Fatalf("failed to load client cert: %v", err)
}
// 2、加载信任的根证书
// 2.1、定义一个证书池
ca := x509.NewCertPool()
caFilePath := "/opt/certs/CAs/myCA.crt"
// 2.2、读取证书内容
caBytes, err := os.ReadFile(caFilePath)
if err != nil {
log.Fatalf("failed to read ca cert %q, %v", caFilePath, err)
}
// 2.3、把证书添加到证书池
if ok := ca.AppendCertsFromPEM(caBytes); !ok {
log.Fatalf("failed to parse %q", caFilePath)
}
// 3、tls配置设置
tlsConfig := &tls.Config{
ServerName: "node9", // 服务端证书的DNS名称
Certificates: []tls.Certificate {certs}, // 服务的证书和私钥对
RootCAs: ca, // 用于验证服务端的信任的根证书
}
return tlsConfig ;
}
func main() {
flag.Parse()
tlsConfig := GetCliTlsConfig()
// 4、连接 rpc 远端服务
// 4、tcp连接 和 认证
creds := credentials.NewTLS(tlsConfig) ;
conn, err := grpc.NewClient(*addr, grpc.WithTransportCredentials(creds))
if err != nil {
log.Fatalf("did not connect: %v", err)
}
defer conn.Close()
// 4.2、创建 Greeter 服务的客户端
cli := pb.NewGreeterClient(conn)
// 5、远程调用
// 5.1、创建上下文,设置10秒超时
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
// 5.2、远程调用 Greeter 服务的函数: SayHello 和 SayHelloAgain
resp, err := cli.SayHello(ctx, &pb.HelloRequest{Name: *name})
if err != nil {
log.Fatalf("could not greet: %v", err)
}
log.Printf("Greeting: %s", resp.GetMessage())
resp, err = cli.SayHelloAgain(ctx, &pb.HelloRequest{Name: *name})
if err != nil {
log.Fatalf("could not greet: %v", err)
}
log.Printf("Greeting: %s", resp.GetMessage())
}
第三步:运行和测试
3.1、生成go接口文件
在 project 目录下,执行指令:protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative helloworld/helloworld.proto

生成 golang 的 接口文件,helloworld.pb.go 和 helloworld_grpc.pb.go
3.2、运行服务端

3.3、运行客户端

发表回复