go学习笔记 Windows Go 1.15 以上版本的 GRPC 通信【自签CA和双向认证】
简单说一下我的环境 win7+go1.15.6,GO1.15 X509 不能用了 ,
证书
需要用到SAN证书,下面就介绍一下SAN证书生成。首先需要下载 OpenSSL http://slproweb.com/products/Win32OpenSSL.html
第1步:生成 CA 根证书
openssl genrsa -out ca.key 2048 openssl req -new -x509 -days 3650 -key ca.key -out ca.pem
You are about to be asked to enter information that will be incorporated into your certificate request. What you are about to enter is what is called a Distinguished Name or a DN. There are quite a few fields but you can leave some blank For some fields there will be a default value, If you enter '.', the field will be left blank. ----- Country Name (2 letter code) [AU]:cn State or Province Name (full name) [Some-State]:shanghai Locality Name (eg, city) []:shanghai Organization Name (eg, company) [Internet Widgits Pty Ltd]:custer Organizational Unit Name (eg, section) []:custer Common Name (e.g. server FQDN or YOUR name) []:localhost Email Address []:
第2步:用 openssl 生成 ca 和双方 SAN 证书。
准备默认 OpenSSL 配置文件于当前目录
linux系统在 : /etc/pki/tls/openssl.cnf
Mac系统在: /System/Library/OpenSSL/openssl.cnf
Windows:安装目录下 openssl.cfg 比如 D:\Program Files\OpenSSL-Win64\bin\openssl.cfg
1:拷贝配置文件到项目 然后修改
2:找到 [ CA_default ],打开 copy_extensions = copy
3:找到[ req ],打开 req_extensions = v3_req # The extensions to add to a certificate request
4:找到[ v3_req ],添加 subjectAltName = @alt_names
5:添加新的标签 [ alt_names ] , 和标签字段
[ alt_names ] DNS.1 = localhost DNS.2 = *.custer.fun
这里填入需要加入到 Subject Alternative Names 段落中的域名名称,可以写入多个。
第3步:生成服务端证书
openssl genpkey -algorithm RSA -out server.key openssl req -new -nodes -key server.key -out server.csr -days 3650 -subj "/C=cn/OU=custer/O=custer/CN=localhost" -config ./openssl.cfg -extensions v3_req openssl x509 -req -days 3650 -in server.csr -out server.pem -CA ca.pem -CAkey ca.key -CAcreateserial -extfile ./openssl.cfg -extensions v3_req
server.csr是上面生成的证书请求文件。ca.pem/ca.key是CA证书文件和key,用来对server.csr进行签名认证。这两个文件在之前生成的。
第4步:生成客户端证书
openssl genpkey -algorithm RSA -out client.key openssl req -new -nodes -key client.key -out client.csr -days 3650 -subj "/C=cn/OU=custer/O=custer/CN=localhost" -config ./openssl.cfg -extensions v3_req openssl x509 -req -days 3650 -in client.csr -out client.pem -CA ca.pem -CAkey ca.key -CAcreateserial -extfile ./openssl.cfg -extensions v3_req
现在 Go 1.15 以上版本的 GRPC 通信,这样就完成了使用自签CA、Server、Client证书和双向认证
GPRC
1.安装protobuf , https://github.com/protocolbuffers/protobuf/releases可以下载【protoc-3.14.0-win64.zip】, 然后配置到环境变量Path中 ,protoc --version 查看版本如下:
2,。安装相应的包
go get -u github.com/golang/protobuf/proto //安装 golang 的proto工具包 go get -u github.com/golang/protobuf/protoc-gen-go //安装 goalng 的proto编译支持 go get -u google.golang.org/grpc //安装 GRPC 包
3.创建 proto 文件#
proto 文件是微服务交互的基本,proto的语法可见GoogleDocs,这里简单写一个示例(spider.proto)
syntax = "proto3"; // 协议为proto3 package spider; // 包名 // 发送请求 message SendAddress { // 发送的参数字段 // 参数类型 参数名 标识号(不可重复) string address = 1; // 要请求的地址 string method = 2; // 请求方式 } // 返回响应 message GetResponse { // 接收的参数字段 // 参数类型 参数名 标识号 int32 httpCode = 1; // http状态码 string response = 2; // 返回体 } // 定义服务,可定义多个服务,每个服务可多个接口 service GoSpider { // rpc请求 请求的函数 (发送请求参数) returns (返回响应的参数) rpc GetAddressResponse (SendAddress) returns (GetResponse); }
4.生成 .bp.go
文件# 使用刚才下载的 protoc 工具将 proto 文件编译成 golang 可识别的文件 【我是后来才创建spider 文件夹的,把spider.pb.go 和spider.proto 移到文件夹中】
输出的目录 proto所在目录
protoc --go_out=plugins=grpc:./ ./spider.proto
需要注意的是,在本个 demo 中,客户端与服务端都是
Golang
,所以在客户端与服务端都公用一个 pb.go
模板文件(如果是不同的语言生成的pb是对应语言),可以将 pb.go
文件放置在云上由双方引用,也可以生成两个副本放在两端项目中,本次就使用 COPY
两份的方式
由于 Golang
一个文件夹只有一个 package
,而生成的 pb.go
文件 package
为创建 proto
的名字(示例为 spider
), 所以我们在项目内单独建立文件夹 spider
将文件放入其中即可正常使用
5.整个GPRC的代码如下【我是服务端 和客户端在一起的】
package main import ( "context" "fmt" "io/ioutil" "main/spider" "net" "net/http" "time" "google.golang.org/grpc" "google.golang.org/grpc/reflection" ) type server struct{} // 接收client端的请求,函数名需保持一致 // ctx参数必传 // 参数二为自定义的参数,需从pb文件导入,因此pb文件必须可导入,文件放哪里随意 // 返回值同参数二,为pb文件的返回结构体指针 func (s *server) GetAddressResponse(ctx context.Context, a *spider.SendAddress) (*spider.GetResponse, error) { // 逻辑写在这里 switch a.Method { case "get", "Get", "GET": // 演示微服务用,故只写get示例 status, body, err := get(a.Address) if err != nil { return nil, err } res := spider.GetResponse{ HttpCode: int32(status), Response: body, } return &res, nil } return nil, nil } func get(address string) (s int, r string, err error) { // get请求 resp, err := http.Get(address) if err != nil { return } defer resp.Body.Close() s = resp.StatusCode body, err := ioutil.ReadAll(resp.Body) if err != nil { return } r = string(body) return } func GPRCServer() { // 监听本地端口 listener, err := net.Listen("tcp", "localhost:8080") if err != nil { return } s := grpc.NewServer() // 创建GRPC spider.RegisterGoSpiderServer(s, &server{}) // 在GRPC服务端注册服务 reflection.Register(s) // 在GRPC服务器注册服务器反射服务 // Serve方法接收监听的端口,每到一个连接创建一个ServerTransport和server的grroutine // 这个goroutine读取GRPC请求,调用已注册的处理程序进行响应 err = s.Serve(listener) if err != nil { return } } func GPRCClient() { // 连接服务器 conn, err := grpc.Dial("localhost:8080", grpc.WithInsecure()) if err != nil { fmt.Println(err) return } defer conn.Close() // 连接GRPC c := spider.NewGoSpiderClient(conn) // 创建要发送的结构体 req := spider.SendAddress{ Address: "http://www.baidu.com", Method: "get", } // 调用server的注册方法 r, err := c.GetAddressResponse(context.Background(), &req) if err != nil { fmt.Println(err) return } // 打印返回值 fmt.Println(r) } func main() { go GPRCServer() time.Sleep(1000) go GPRCClient() var s string fmt.Scan(&s) }
3.修改GPRC程序 使用证书
package main import ( "context" "crypto/tls" "crypto/x509" "fmt" "io/ioutil" "main/spider" "net" "net/http" "time" "google.golang.org/grpc" "google.golang.org/grpc/credentials" "google.golang.org/grpc/reflection" ) type server struct{} // 接收client端的请求,函数名需保持一致 // ctx参数必传 // 参数二为自定义的参数,需从pb文件导入,因此pb文件必须可导入,文件放哪里随意 // 返回值同参数二,为pb文件的返回结构体指针 func (s *server) GetAddressResponse(ctx context.Context, a *spider.SendAddress) (*spider.GetResponse, error) { // 逻辑写在这里 switch a.Method { case "get", "Get", "GET": // 演示微服务用,故只写get示例 status, body, err := get(a.Address) if err != nil { return nil, err } res := spider.GetResponse{ HttpCode: int32(status), Response: body, } return &res, nil } return nil, nil } func get(address string) (s int, r string, err error) { // get请求 resp, err := http.Get(address) if err != nil { return } defer resp.Body.Close() s = resp.StatusCode body, err := ioutil.ReadAll(resp.Body) if err != nil { return } r = string(body) return } func GPRCServer() { // 监听本地端口 listener, err := net.Listen("tcp", "localhost:8080") if err != nil { return } //证书 cert, _ := tls.LoadX509KeyPair("server.pem", "server.key") certPool := x509.NewCertPool() ca, _ := ioutil.ReadFile("ca.pem") certPool.AppendCertsFromPEM(ca) creds := credentials.NewTLS(&tls.Config{ Certificates: []tls.Certificate{cert}, ClientAuth: tls.RequireAndVerifyClientCert, ClientCAs: certPool, }) s := grpc.NewServer(grpc.Creds(creds)) // 创建GRPC spider.RegisterGoSpiderServer(s, &server{}) // 在GRPC服务端注册服务 reflection.Register(s) // 在GRPC服务器注册服务器反射服务 // Serve方法接收监听的端口,每到一个连接创建一个ServerTransport和server的grroutine // 这个goroutine读取GRPC请求,调用已注册的处理程序进行响应 err = s.Serve(listener) if err != nil { return } } func GPRCClient() { cert, _ := tls.LoadX509KeyPair("client.pem", "client.key") certPool := x509.NewCertPool() ca, _ := ioutil.ReadFile("ca.pem") certPool.AppendCertsFromPEM(ca) creds := credentials.NewTLS(&tls.Config{ Certificates: []tls.Certificate{cert}, ServerName: "localhost", RootCAs: certPool, }) // 连接服务器 conn, err := grpc.Dial("localhost:8080", grpc.WithTransportCredentials(creds)) if err != nil { fmt.Println(err) return } defer conn.Close() // 连接GRPC c := spider.NewGoSpiderClient(conn) // 创建要发送的结构体 req := spider.SendAddress{ Address: "http://www.baidu.com", Method: "get", } // 调用server的注册方法 r, err := c.GetAddressResponse(context.Background(), &req) if err != nil { fmt.Println(err) return } // 打印返回值 fmt.Println(r) } func main() { go GPRCServer() time.Sleep(1000) go GPRCClient() var s string fmt.Scan(&s) }
--------------------------------------------------------------------------------------------------------------------
开启grpc的双向认证
受到 asp.ner core 5.0 Grpc双向认证 和 restful api包装 外加swagger启用【VSCode创建】,决定多开一个 端口 用于grpc tsl的双向认证, 修改后的server/main.go代码如下:
package main import ( "crypto/tls" "crypto/x509" "fmt" "hello/gateway" pb "hello/protos" "hello/server/services" "io/ioutil" "log" "net" "google.golang.org/grpc" "google.golang.org/grpc/credentials" "google.golang.org/grpc/reflection" ) func main() { //grpc grpctslPort := ":9090" grpcPort := ":8081" httpPort := ":8080" ///grpc tsl 用于双向认证 go GrpcTslServer(grpctslPort) ///普通的主要是便于gateway使用 lis, err := net.Listen("tcp", grpcPort) if err != nil { log.Fatalf("failed to listen: %v", err) } //gateway go gateway.HttpRun(grpcPort, httpPort) s := grpc.NewServer() pb.RegisterGreeterServer(s, services.NewServer()) fmt.Println("rpc services started, listen on localhost" + grpcPort) s.Serve(lis) } func GrpcTslServer(grpctslPort string) error { //证书 cert, a := tls.LoadX509KeyPair("../certs/server.pem", "../certs/server.key") if a != nil { fmt.Println(a) } certPool := x509.NewCertPool() ca, _ := ioutil.ReadFile("../certs/ca.pem") certPool.AppendCertsFromPEM(ca) creds := credentials.NewTLS(&tls.Config{ Certificates: []tls.Certificate{cert}, ClientAuth: tls.RequireAndVerifyClientCert, ClientCAs: certPool, }) sl := grpc.NewServer(grpc.Creds(creds)) // 创建GRPC pb.RegisterGreeterServer(sl, services.NewServer()) reflection.Register(sl) // 在GRPC服务器注册服务器反射服务 listener, err := net.Listen("tcp", grpctslPort) if err != nil { fmt.Println(err) return err } fmt.Println("rpc tsl services started, listen on localhost" + grpctslPort) return sl.Serve(listener) }
client/main.go如下:
package main import ( "context" "crypto/tls" "crypto/x509" "encoding/json" "fmt" pb "hello/protos" "io/ioutil" "net/http" "strings" "time" "google.golang.org/grpc" "google.golang.org/grpc/credentials" ) func main() { ///grpc client req := pb.HelloRequest{Name: "gavin"} cert, err := tls.LoadX509KeyPair("../certs/client.pem", "../certs/client.key") certPool := x509.NewCertPool() ca, _ := ioutil.ReadFile("../certs/ca.pem") certPool.AppendCertsFromPEM(ca) creds := credentials.NewTLS(&tls.Config{ Certificates: []tls.Certificate{cert}, ServerName: "localhost", RootCAs: certPool, }) // GRPC conn, err := grpc.Dial("localhost:9090", grpc.WithTransportCredentials(creds)) if err != nil { fmt.Println(err) return } defer conn.Close() //c := sv.NewServer() c := pb.NewGreeterClient(conn) r, err := c.SayHello(context.Background(), &req) if err != nil { fmt.Println(err) } // 打印返回值 fmt.Println(r) fmt.Println("http Start......................") //http requestByte, _ := json.Marshal(req) client := http.Client{Timeout: 15 * time.Second} resp, err := client.Post("http://localhost:8080/hello_world", "application/json", strings.NewReader(string(requestByte))) bodyBytes, err := ioutil.ReadAll(resp.Body) defer resp.Body.Close() fmt.Println(string(bodyBytes)) }
运行效果:
D:\GoProject\src\gogrpccertAndgateway\server>go run main.go rpc tsl services started, listen on localhost:9090 rpc services started, listen on localhost:8081 grpc-gateway listen on localhost:8080 request: gavin
D:\GoProject\src\gogrpccertAndgateway\client>go run main.go request: gavin message:"hello, gavin" http Start...................... {"message":"hello, gavin"}
源码下载 https://download.csdn.net/download/dz45693/13985838 https://github.com/dz45693/gogrpccertAndgateway.git