[TOC]

gRPC认证

咱们再来回顾一下gRPC的根本构造

gRPC 是一个典型的C/S模型,须要开发客户端 和 服务端,客户端与服务端须要达成协议,应用某一个确认的传输协定来传输数据,gRPC通常默认是应用protobuf来作为传输协定,当然也是能够应用其余自定义的。

那么,客户端与服务端要通信之前,客户端如何晓得本人的数据是发给哪一个明确的服务端呢?反过来,服务端是不是也须要有一种形式来弄个分明本人的数据要返回给谁呢?

那么就不得不提gRPC的认证

认证形式

此处说到的认证,不是用户的身份认证而是指多个server 和 多个client之间,如何辨认对方是谁,并且能够平安的进行数据传输

  • SSL/TLS认证形式(采纳http2协定)
  • 基于Token的认证形式(基于平安连贯)
  • 不采纳任何措施的连贯,这是不平安的连贯(默认采纳http1)
  • 自定义的身份认证,gRPC提供了接口用于扩大自定义认证形式

明天就和大家分享一下 SSL/TLS认证形式基于Token的认证形式 ,这里再来回顾一下上一篇讲到的

gRPC音讯传输的四种类型

  • 申请-响应式
  • 服务端流式音讯,客户端申请一次,服务端会一一系列的数据,即数据流
  • 客户端流式音讯,客户端用数据流申请,服务端做响应
  • 双向流的形式,即单方都是流式数据

简略的例子:

service Example{    rpc ReqAndRsp(Req) returns (Response)    rpc ReqAndStream(Req) returns (Stream Response)    rpc StreamAndRsp(Stream Request) returns (Response)    rpc BidStream(Stream Request) returns (Stream response)}

SSL/TLS认证形式

那么什么是SSL/TLS?

TLS(Transport Layer Security) 是 SSL(Secure Socket Layer) 的后续版本,它们是用于在互联网两台计算机之间用于身份验证和加密的一种协定。

基于SSL/TLS的通道加密是gRPC罕用的伎俩,那么个别咱们都是如何使用他的,他的架构个别会是啥样的?

GRPC 默认是基于HTTP/2的TLS 对客户端和服务端替换的所有数据进行加密传输的

那么HTTP 2 默认就有加密吗?

HTTP 2 协定默认是没有加密的,它只是事后定义好了TLS的轮廓,是TLS保障安全性,TLS做的加密

HTTP 2 有啥个性?

这里简略说一下,HTTP 2 较之前的版本有如下4个重要的变动:

  • 二进制分帧

将所有传输的信息宰割为更小的音讯和帧,并对它们采纳二进制格局的编码

  • 多路io复用

在共享TCP链接的根底上同时发送申请和响应,http音讯被合成为独立的帧,乱序发送,服务端依据标识符和首部将音讯从新组装起来

  • 头部压缩
  • 服务器推送 server push

服务器能够额定的向客户端推送资源,而无需客户端明确的申请

SSL/TLS加密的根本做法是啥?

SSL/TLS 通过将称为X.509 证书的数字文档将网站和公司的实体信息绑定到加密密钥来进行工作。

每一个密钥对(key pairs)都有一个公有密钥(private key)私有密钥(public key),公有密钥是独有的,个别位于服务器上,用于解密由公共密钥加密过的信息;

私有密钥是私有的,与服务器进行交互的每个人都能够持有私有密钥,用公钥加密的信息只能由公有密钥来解密。

简略来说就是

SSL/TLS协定,客户端向服务器端索要公钥,而后用公钥加密信息,服务器收到密文后,用本人的私钥解密。

SSL/TLS协定提供啥服务呢?

  • 认证用户和服务器,确保数据发送到正确的客户端和服务器;
  • 加密数据以避免数据中途被窃取;
  • 保护数据的完整性,确保数据在传输过程中不被扭转;

SSL/TLS协定提供的平安通道有哪些个性呢?

  • 机密性:SSL协定应用密钥加密通信数据。
  • 可靠性:服务器和客户端都会被认证,客户端的认证是可选的。
  • 完整性:SSL协定会对传送的数据进行完整性检查。

说了这么多,咱们来演示一下gRPC的 SSL/TLS协定如何实际吧

必要环境搭建

OpenSSL装置

  • 官网下载地址: openssl-3.0.0-alpha17.tar.gz>https://www.openssl.org/sourc...

解压源代码

tar xzvf openssl-3.0.0-alpha17.tar.gz

进入源代码目录

cd openssl-3.0.0-alpha17

编译和装置

./Configuremakesudo make install

装置完结后,应用 openssl version 查看openssl 版本号

若报错如下信息:

openssl: error while loading shared libraries: libssl.so.3: cannot open shared object file: No such file or directory

通常状况下只须要建一个软链接,链接过来即可

sudo ln -s /usr/local/lib/libssl.so.3 /usr/lib/libssl.so.3sudo ln -s /usr/local/lib/libcrypto.so.3 /usr/lib/libcrypto.so.3

TLS证书制作

如下是一张生成key的简略形式,不实用go1.15之后的版本,go1.15曾经弃用了 x509

# 制作私钥openssl genrsa -out server.key 2048openssl ecparam -genkey -name secp384r1 -out server.key# 自签名公钥 ,设置无效工夫365 天openssl req -new -x509 -sha256 -key server.key -out server.pem -days 365

一个DEMO

开始应用上述生成的key

.
├── client
│ ├── keys
│ │ ├── server.key
│ │ └── server.pem
│ ├── main.go
│ ├── myclient
│ └── protoc
│ └── hi
│ ├── hi.pb.go
│ └── hi.proto
└── server

├── keys│   ├── server.key│   └── server.pem├── main.go├── myserver└── protoc    └── hi        ├── hi.pb.go        └── hi.proto

hi.proto

将proto编译成pb.go文件

protoc --go_out=plugins=grpc:. hi.proto

syntax = "proto3"; // 指定proto版本package hi;     // 指定默认包名// 指定golang包名option go_package = "hi";// 定义Hi服务service Hi {// 定义SayHi办法rpc SayHi(HiRequest) returns (HiResponse) {}}// HiRequest 申请构造message HiRequest {string name = 1;}// HiResponse 响应构造message HiResponse {string message = 1;}

server/main.go

package mainimport (   "fmt"   "log"   "net"   pb "myserver/protoc/hi"   "golang.org/x/net/context"   "google.golang.org/grpc"   "google.golang.org/grpc/credentials" // 引入grpc认证包)const (   // Address gRPC服务地址   Address = "127.0.0.1:9999")// 定义HiService并实现约定的接口type HiService struct{}// HiService Hello服务var HiSer = HiService{}// SayHi 实现Hi服务接口func (h HiService) SayHi(ctx context.Context, in *pb.HiRequest) (*pb.HiResponse, error) {   resp := new(pb.HiResponse)   resp.Message = fmt.Sprintf("Hi %s.", in.Name)   return resp, nil}func main() {   log.SetFlags(log.Ltime | log.Llongfile)   listen, err := net.Listen("tcp", Address)   if err != nil {      log.Panicf("Failed to listen: %v", err)   }   // TLS认证   creds, err := credentials.NewServerTLSFromFile("./keys/server.pem", "./keys/server.key")   if err != nil {      log.Panicf("Failed to generate credentials %v", err)   }   // 实例化grpc Server, 并开启TLS认证   s := grpc.NewServer(grpc.Creds(creds))   // 注册HelloService   pb.RegisterHiServer(s, HiSer)   log.Println("Listen on " + Address + " with TLS")   s.Serve(listen)}

client/main.go

package mainimport (   "log"   pb "myclient/protoc/hi" // 引入proto包   "golang.org/x/net/context"   "google.golang.org/grpc"   "google.golang.org/grpc/credentials" // 引入grpc认证包   "google.golang.org/grpc/grpclog")const (   // Address gRPC服务地址   Address = "127.0.0.1:9999")func main() {   log.SetFlags(log.Ltime | log.Llongfile)   // TLS连贯  记得把xxx改成你写的服务器地址   creds, err := credentials.NewClientTLSFromFile("./keys/server.pem", "xiaomotong")   if err != nil {      log.Panicf("Failed to create TLS credentials %v", err)   }   conn, err := grpc.Dial(Address, grpc.WithTransportCredentials(creds))   if err != nil {      grpclog.Fatalln(err)   }   defer conn.Close()   // 初始化客户端   c := pb.NewHiClient(conn)   // 调用办法   req := &pb.HiRequest{Name: "gRPC"}   res, err := c.SayHi(context.Background(), req)   if err != nil {      log.Panicln(err)   }   log.Println(res.Message)}

如果你的go是在1.15版本以上的,请从新生成key,参考文档 openssl证书生成 记录(GO1.15版本以上)

生成后,放到我的项目响应的地位,编译运行即可成果如下:

服务端:

客户端:

基于Token的认证形式

将上述TLS实际DEMO进行优化,加上Token机制,须要做如下2点改变

  • 客户端,实现credentials包的接口,GetRequestMetadataRequireTransportSecurity
  • 服务端在 metadata 验证客户端的信息

代码构造与上一个DEMO统一,别离在客户端和服务端的代码中退出相应的逻辑即可。

如下是credentials包中待实现接口:

又一个DEMO

client/main.go

增加如下逻辑即可

package mainimport (    "log"    pb "myclient/protoc/hi" // 引入proto包    "golang.org/x/net/context"    "google.golang.org/grpc"    "google.golang.org/grpc/credentials" // 引入grpc认证包    "google.golang.org/grpc/grpclog")const (    // Address gRPC服务地址    Address = "127.0.0.1:9999")// ====== 增加的逻辑  START ==============var IsTls = true// myCredential 自定义认证type myCredential struct{}// GetRequestMetadata 实现自定义认证接口func (c myCredential) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) {    return map[string]string{        "appid":  "myappid",        "appkey": "mykey",    }, nil}// RequireTransportSecurity 自定义认证是否开启TLSfunc (c myCredential) RequireTransportSecurity() bool {    return IsTls}// ====== 增加的逻辑  END  ==============func main() {    log.SetFlags(log.Ltime | log.Llongfile)        // ====== 增加的逻辑  START ==============    var err error    var opts []grpc.DialOption    if IsTls {        //关上tls 走tls认证        creds, err := credentials.NewClientTLSFromFile("./keys/server.pem", "小魔童")        if err != nil {            log.Panicf("Failed to create TLS mycredentials %v", err)        }        opts = append(opts, grpc.WithTransportCredentials(creds))    }else{        opts = append(opts, grpc.WithInsecure())    }    opts = append(opts, grpc.WithPerRPCCredentials(new(myCredential)))        // TLS连贯  记得把xxx改成你写的服务器地址,能够默认写127.0.0.1    conn, err := grpc.Dial(Address, opts...)    if err != nil {        grpclog.Fatalln(err)    }// ====== 增加的逻辑  END  ==============        defer conn.Close()    // 初始化客户端    c := pb.NewHiClient(conn)    // 调用办法    req := &pb.HiRequest{Name: "gRPC"}    res, err := c.SayHi(context.Background(), req)    if err != nil {        log.Panicln(err)    }    log.Println(res.Message)}

server/main.go

增加如下逻辑即可

package mainimport (   "fmt"   "google.golang.org/grpc/codes"   "google.golang.org/grpc/metadata"   "log"   "net"   pb "myserver/protoc/hi"   "golang.org/x/net/context"   "google.golang.org/grpc"   "google.golang.org/grpc/credentials" // 引入grpc认证包)const (   // Address gRPC服务地址   Address = "127.0.0.1:9999")// 定义helloService并实现约定的接口type HiService struct{}// HiService Hello服务var HiSer = HiService{}// SayHello 实现Hello服务接口func (h HiService) SayHi(ctx context.Context, in *pb.HiRequest) (*pb.HiResponse, error) {// ====== 增加的逻辑  START ==============       // 解析metada中的信息并验证   md, ok := metadata.FromIncomingContext(ctx)   if !ok {      return nil, grpc.Errorf(codes.Unauthenticated, "no token ")   }   var (      appId  string      appKey string   )   // md 是一个 map[string][]string 类型的   if val, ok := md["appid"]; ok {      appId = val[0]   }   if val, ok := md["appkey"]; ok {      appKey = val[0]   }   if appId != "myappid" || appKey != "mykey" {      return nil, grpc.Errorf(codes.Unauthenticated, "token invalide: appid=%s, appkey=%s", appId, appKey)   }// ====== 增加的逻辑  END ==============   resp := new(pb.HiResponse)   resp.Message = fmt.Sprintf("Hi %s.", in.Name)   return resp, nil}func main() {   log.SetFlags(log.Ltime | log.Llongfile)   listen, err := net.Listen("tcp", Address)   if err != nil {      log.Panicf("Failed to listen: %v", err)   }   // TLS认证   creds, err := credentials.NewServerTLSFromFile("./keys/server.pem", "./keys/server.key")   if err != nil {      log.Panicf("Failed to generate credentials %v", err)   }   // 实例化grpc Server, 并开启TLS认证   s := grpc.NewServer(grpc.Creds(creds))   // 注册HelloService   pb.RegisterHiServer(s, HiSer)   log.Println("Listen on " + Address + " with TLS")   s.Serve(listen)}

好了,本次就到这里,下一次分享 gRPC的interceptor

技术是凋谢的,咱们的心态,更应是凋谢的。拥抱变动,背阴而生,致力向前行。

我是小魔童哪吒,欢送点赞关注珍藏,下次见~