gRPC 入門指南 — 自定義認證(六)
前言
在前面的章節 (文末推薦閱讀) 中,我們通過 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 信息的代碼,這樣豈不是很繁瑣。那有沒有 “一處驗證,處處驗證” 的方法?答案肯定是有的,對!攔截器,這就是我們下一篇文章給大家介紹的。
對了,看完文章,記得點擊下方的卡片。關注我哦~ 👇👇👇
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/ofQj9afcKnwAqfBT3N-d8g