gRPC入門指南 — 自定義認(rèn)證(六)
前言
在前面的章節(jié)(文末推薦閱讀)中,我們通過(guò) TLS 證書的方式對(duì)通信數(shù)據(jù)進(jìn)行了加密。另外,我們還可以給 RPC 方法添加自定義的驗(yàn)證方法,使得數(shù)據(jù)更加安全。這篇文章我們就以 Token 認(rèn)證為例,介紹 gRPC 如何添加自定義驗(yàn)證方法。
自定義認(rèn)證
gRPC 官方默認(rèn)提供了用于自定義認(rèn)證的接口,作用是將所需的安全認(rèn)證信息添加到 RPC 方法的上下中。
type PerRPCCredentials interface {
GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error)
RequireTransportSecurity() bool
}
GetRequestMetadata() 獲取當(dāng)前請(qǐng)求認(rèn)證所需的元數(shù)據(jù); RequireTransportSecurity() 是否需要基于 TLS 認(rèn)證進(jìn)行安全傳輸;
接下來(lái)我們自定義 token,實(shí)現(xiàn)這兩個(gè)方法:
type Token struct {
AppId string
AppSecret string
}
// GetRequestMetadata 獲取當(dāng)前請(qǐng)求認(rèn)證所需的元數(shù)據(jù)
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 認(rèn)證進(jìn)行安全傳輸
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) {
// 檢測(cè)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 {
// 從上下文章獲取元數(shù)據(jù)
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無(wú)效,appId:%v,appSecret:%v", appId, appSecret)
}
return nil
}
上面的代碼,在 GetSimpleInfo() 方法中,在處理實(shí)際的業(yè)務(wù)邏輯之前,我們調(diào)用了 check() 函數(shù)驗(yàn)證 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 獲取當(dāng)前請(qǐng)求認(rèn)證所需的元數(shù)據(jù)
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 認(rèn)證進(jìn)行安全傳輸
func (t *Token) RequireTransportSecurity() bool {
return false
}
func main() {
// Token token認(rèn)證
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,實(shí)現(xiàn)了 gRPC 提供的兩個(gè)方法。在客戶端中調(diào)用 Dial() 時(shí)添加自定義驗(yàn)證方法。
grpc.WithPerRPCCredentials(&token)
驗(yàn)證
分別運(yùn)行服務(wù)端和客戶端,輸出:
go run server.go
:8000 net listening...
get from client: seekload
go run client.go
get from client,code: 1,value: gRPC
可以制造 token 信息不正確,客戶端返回錯(cuò)誤:
resp err: rpc error: code = Unauthenticated desc = Token無(wú)效,appId:grpc_auth,appSecret:123457
思考
大家試想下,實(shí)際業(yè)務(wù)中肯定不止一個(gè) RPC 方法,每個(gè)方法中都需要手動(dòng)加一段驗(yàn)證 token 信息的代碼,這樣豈不是很繁瑣。那有沒(méi)有“一處驗(yàn)證,處處驗(yàn)證”的方法?答案肯定是有的,對(duì)!攔截器,這就是我們下一篇文章給大家介紹的。
推薦閱讀
評(píng)論
圖片
表情
