gRPC入门指南 — 自定义认证(六)
Go语言精选
共 6065字,需浏览 13分钟
·
2021-08-29 07:28
前言
在前面的章节(文末推荐阅读)中,我们通过 TLS 证书的方式对通信数据进行了加密。另外,我们还可以给 RPC 方法添加自定义的验证方法,使得数据更加安全。这篇文章我们就以 Token 认证为例,介绍 gRPC 如何添加自定义验证方法。
自定义认证
gRPC 官方默认提供了用于自定义认证的接口,作用是将所需的安全认证信息添加到 RPC 方法的上下中。
type PerRPCCredentials interface {
GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error)
RequireTransportSecurity() bool
}
GetRequestMetadata() 获取当前请求认证所需的元数据; RequireTransportSecurity() 是否需要基于 TLS 认证进行安全传输;
接下来我们自定义 token,实现这两个方法:
type Token struct {
AppId string
AppSecret string
}
// GetRequestMetadata 获取当前请求认证所需的元数据
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 false
}
Server 端
完整的代码如下:
package main
import (
"context"
pb "go-grpc-example/6-rpc_auth/proto"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
"log"
"net"
)
const (
Address string = ":8000"
Network string = "tcp"
)
type SimpleService struct{}
func (s *SimpleService) GetSimpleInfo(ctx context.Context, req *pb.SimpleRequest) (*pb.SimpleResponse, error) {
// 检测Token是否有效
if err := check(ctx); err != nil {
return nil, err
}
data := req.Data
log.Printf("get from client: %v", data)
resp := pb.SimpleResponse{
Code: 1,
Value: "gRPC",
}
return &resp, nil
}
func main() {
listener, err := net.Listen(Network, Address)
if err != nil {
log.Fatalf("net.listen err: %v", err)
}
log.Println(Address, " net listening...")
grpcServer := grpc.NewServer()
pb.RegisterSimpleServer(grpcServer, &SimpleService{})
err = grpcServer.Serve(listener)
if err != nil {
log.Fatalf("grpc server err: %v", err)
}
}
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_auth" || appSecret != "123456" {
return status.Errorf(codes.Unauthenticated, "Token无效,appId:%v,appSecret:%v", appId, appSecret)
}
return nil
}
上面的代码,在 GetSimpleInfo() 方法中,在处理实际的业务逻辑之前,我们调用了 check() 函数验证 token 信息是否正确。
Client 端
完整的代码如下:
package main
import (
"context"
pb "go-grpc-example/5-security/proto"
"google.golang.org/grpc"
"log"
)
const Address = ":8000"
type Token struct {
AppId string
AppSecret string
}
// GetRequestMetadata 获取当前请求认证所需的元数据
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 false
}
func main() {
// Token token认证
token := Token{
AppId: "grpc_auth",
AppSecret: "123456",
}
opts := []grpc.DialOption{
grpc.WithInsecure(),
grpc.WithPerRPCCredentials(&token),
}
conn, err := grpc.Dial(Address, opts...)
if err != nil {
log.Fatalf("dial conn err: %v", err)
}
defer conn.Close()
grpcClient := pb.NewSimpleClient(conn)
req := pb.SimpleRequest{
Data: "seekload",
}
resp, err := grpcClient.GetSimpleInfo(context.Background(), &req)
if err != nil {
log.Fatalf("resp err: %v", err)
}
log.Printf("get from client,code: %v,value: %v", resp.Code, resp.Value)
}
上面的代码,自定义 token,实现了 gRPC 提供的两个方法。在客户端中调用 Dial() 时添加自定义验证方法。
grpc.WithPerRPCCredentials(&token)
验证
分别运行服务端和客户端,输出:
go run server.go
:8000 net listening...
get from client: seekload
go run client.go
get from client,code: 1,value: gRPC
可以制造 token 信息不正确,客户端返回错误:
resp err: rpc error: code = Unauthenticated desc = Token无效,appId:grpc_auth,appSecret:123457
思考
大家试想下,实际业务中肯定不止一个 RPC 方法,每个方法中都需要手动加一段验证 token 信息的代码,这样岂不是很繁琐。那有没有“一处验证,处处验证”的方法?答案肯定是有的,对!拦截器,这就是我们下一篇文章给大家介绍的。
推荐阅读
评论