[转载]Go gRPC进阶-TLS认证+自定义方法认证(七)

前言

前面篇章的gRPC都是明文传输的,容易被篡改数据。本章将介绍如何为gRPC添加安全机制,包括TLS证书认证和Token认证。

TLS证书认证

什么是TLS

TLS(Transport Layer Security,安全传输层),TLS是建立在传输层TCP协议之上的协议,服务于应用层,它的前身是SSL(Secure Socket Layer,安全套接字层),它实现了将应用层的报文进行加密后再交由TCP进行传输的功能。

TLS的作用

TLS协议主要解决如下三个网络安全问题。

  • 保密(message privacy),保密通过加密encryption实现,所有信息都加密传输,第三方无法嗅探;
  • 完整性(message integrity),通过MAC校验机制,一旦被篡改,通信双方会立刻发现;
  • 认证(mutual authentication),双方认证,双方都可以配备证书,防止身份被冒充;

生成私钥

生成RSA私钥:openssl genrsa -out server.key 2048

生成RSA私钥,命令的最后一个参数,将指定生成密钥的位数,如果没有指定,默认512

生成ECC私钥:openssl ecparam -genkey -name secp384r1 -out server.key

生成ECC私钥,命令为椭圆曲线密钥参数生成及操作,本文中ECC曲线选择的是secp384r1

生成公钥

openssl req -new -x509 -sha256 -key server.key -out server.pem -days 3650

openssl req:生成自签名证书,-new指生成证书请求、-sha256指使用sha256加密、-key指定私钥文件、-x509指输出证书、-days 3650为有效期

此后则输入证书拥有者信息


Country Name (2 letter code) [AU]:CN
State or Province Name (full name) [Some-State]:XxXx
Locality Name (eg, city) []:XxXx
Organization Name (eg, company) [Internet Widgits Pty Ltd]:XX Co. Ltd
Organizational Unit Name (eg, section) []:Dev
Common Name (e.g. server FQDN or YOUR name) []:go-grpc-example
Email Address []:xxx@xxx.com

服务端构建TLS证书并认证


func main() {
    // 监听本地端口
    listener, err := net.Listen(Network, Address)
    if err != nil {
        log.Fatalf("net.Listen err: %v", err)
    }
    // 从输入证书文件和密钥文件为服务端构造TLS凭证
    creds, err := credentials.NewServerTLSFromFile("../pkg/tls/server.pem", "../pkg/tls/server.key")
    if err != nil {
        log.Fatalf("Failed to generate credentials %v", err)
    }
    // 新建gRPC服务器实例,并开启TLS认证
    grpcServer := grpc.NewServer(grpc.Creds(creds))
    // 在gRPC服务器注册我们的服务
    pb.RegisterSimpleServer(grpcServer, &SimpleService{})
    log.Println(Address + " net.Listing whth TLS and token...")
    //用服务器 Serve() 方法以及我们的端口信息区实现阻塞等待,直到进程被杀死或者 Stop() 被调用
    err = grpcServer.Serve(listener)
    if err != nil {
        log.Fatalf("grpcServer.Serve err: %v", err)
    }
}
  • credentials.NewServerTLSFromFile:从输入证书文件和密钥文件为服务端构造TLS凭证
  • grpc.Creds:返回一个ServerOption,用于设置服务器连接的凭证。

完整server.go代码

客户端配置TLS连接

var grpcClient pb.SimpleClient

func main() {
    //从输入的证书文件中为客户端构造TLS凭证
    creds, err := credentials.NewClientTLSFromFile("../pkg/tls/server.pem", "go-grpc-example")
    if err != nil {
        log.Fatalf("Failed to create TLS credentials %v", err)
    }
    // 连接服务器
    conn, err := grpc.Dial(Address, grpc.WithTransportCredentials(creds))
    if err != nil {
        log.Fatalf("net.Connect err: %v", err)
    }
    defer conn.Close()

    // 建立gRPC连接
    grpcClient = pb.NewSimpleClient(conn)
}
  • credentials.NewClientTLSFromFile:从输入的证书文件中为客户端构造TLS凭证。
  • grpc.WithTransportCredentials:配置连接级别的安全凭证(例如,TLS/SSL),返回一个DialOption,用于连接服务器。

完整client.go代码

到这里,已经完成TLS证书认证了,gRPC传输不再是明文传输。此外,添加自定义的验证方法能使gRPC相对更安全。下面以Token认证为例,介绍gRPC如何添加自定义验证方法。

Token认证

客户端发请求时,添加Token到上下文context.Context中,服务器接收到请求,先从上下文中获取Token验证,验证通过才进行下一步处理。

客户端请求添加Token到上下文中

type PerRPCCredentials interface {
    GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error)
    RequireTransportSecurity() bool
}

gRPC 中默认定义了 PerRPCCredentials,是提供用于自定义认证的接口,它的作用是将所需的安全认证信息添加到每个RPC方法的上下文中。其包含 2 个方法:

  • GetRequestMetadata:获取当前请求认证所需的元数据
  • RequireTransportSecurity:是否需要基于 TLS 认证进行安全传输

接下来我们实现这两个方法

// Token token认证
type Token struct {
    AppID     string
    AppSecret string
}


// GetRequestMetadata 获取当前请求认证所需的元数据(metadata)
func (t *Token) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) {
    return map[string]string{"app_id": t.AppID, "app_secret": t.AppSecret}, nil
}

// RequireTransportSecurity 是否需要基于 TLS 认证进行安全传输
func (t *Token) RequireTransportSecurity() bool {
    return true
}

然后再客户端中调用Dial时添加自定义验证方法进去

//构建Token
    token := auth.Token{
        AppID:     "grpc_token",
        AppSecret: "123456",
    }
    // 连接服务器
    conn, err := grpc.Dial(Address, grpc.WithTransportCredentials(creds), grpc.WithPerRPCCredentials(&token))

完整client.go代码

服务端验证Token

首先需要从上下文中获取元数据,然后从元数据中解析Token进行验证

// Check 验证token
func Check(ctx context.Context) error {
    //从上下文中获取元数据
    md, ok := metadata.FromIncomingContext(ctx)
    if !ok {
        return status.Errorf(codes.Unauthenticated, "获取Token失败")
    }
    var (
        appID     string
        appSecret string
    )
    if value, ok := md["app_id"]; ok {
        appID = value[0]
    }
    if value, ok := md["app_secret"]; ok {
        appSecret = value[0]
    }
    if appID != "grpc_token" || appSecret != "123456" {
        return status.Errorf(codes.Unauthenticated, "Token无效: app_id=%s, app_secret=%s", appID, appSecret)
    }
    return nil
}

// Route 实现Route方法
func (s *SimpleService) Route(ctx context.Context, req *pb.SimpleRequest) (*pb.SimpleResponse, error) {
    //检测Token是否有效
    if err := Check(ctx); err != nil {
        return nil, err
    }
    res := pb.SimpleResponse{
        Code:  200,
        Value: "hello " + req.Data,
    }
    return &res, nil
}
  • metadata.FromIncomingContext:从上下文中获取元数据

完整server.go代码

服务端代码中,每个服务的方法都需要添加Check(ctx)来验证Token,这样十分麻烦。gRPC拦截器,能很好地解决这个问题。gRPC拦截器功能类似中间件,拦截器收到请求后,先进行一些操作,然后才进入服务的代码处理。

服务端添加拦截器

func main() {
    // 监听本地端口
    listener, err := net.Listen(Network, Address)
    if err != nil {
        log.Fatalf("net.Listen err: %v", err)
    }
    // 从输入证书文件和密钥文件为服务端构造TLS凭证
    creds, err := credentials.NewServerTLSFromFile("../pkg/tls/server.pem", "../pkg/tls/server.key")
    if err != nil {
        log.Fatalf("Failed to generate credentials %v", err)
    }
    //普通方法:一元拦截器(grpc.UnaryInterceptor)
    var interceptor grpc.UnaryServerInterceptor
    interceptor = func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
        //拦截普通方法请求,验证Token
        err = Check(ctx)
        if err != nil {
            return
        }
        // 继续处理请求
        return handler(ctx, req)
    }
    // 新建gRPC服务器实例,并开启TLS认证和Token认证
    grpcServer := grpc.NewServer(grpc.Creds(creds), grpc.UnaryInterceptor(interceptor))
    // 在gRPC服务器注册我们的服务
    pb.RegisterSimpleServer(grpcServer, &SimpleService{})
    log.Println(Address + " net.Listing whth TLS and token...")
    //用服务器 Serve() 方法以及我们的端口信息区实现阻塞等待,直到进程被杀死或者 Stop() 被调用
    err = grpcServer.Serve(listener)
    if err != nil {
        log.Fatalf("grpcServer.Serve err: %v", err)
    }
}
  • grpc.UnaryServerInterceptor:为一元拦截器,只会拦截简单RPC方法。流式RPC方法需要使用流式拦截器grpc.StreamInterceptor进行拦截。

客户端发起请求,当Token不正确时候,会返回

Call Route err: rpc error: code = Unauthenticated desc = Token无效: app_id=grpc_token, app_secret=12345

总结

本篇介绍如何为gRPC添加TLS证书认证和自定义认证,从而让gRPC更安全。添加gRPC拦截器,从而省略在每个方法前添加Token检测代码,使代码更简洁。

教程源码地址:https://github.com/Bingjian-Zhu/go-grpc-example

参考:

作者: 烟花易冷人憔悴

出处:https://www.cnblogs.com/FireworksEasyCool/p/12710325.html

版权:本文采用「署名-非商业性使用-相同方式共享 4.0 国际」知识共享许可协议进行许可。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350

推荐阅读更多精彩内容