這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
gRPC 預設提供了兩種認證方式:
兩種方式可以混合使用
TLS認證樣本
這裡直接擴充hello項目,實現TLS認證機制
首先需要準備認證,在hello目錄建立keys目錄用於存放認證檔案。
認證製作
製作私密金鑰 (.key)
# Key considerations for algorithm "RSA" ≥ 2048-bitopenssl genrsa -out server.key 2048 # Key considerations for algorithm "ECDSA" ≥ secp384r1# List ECDSA the supported curves (openssl ecparam -list_curves)openssl ecparam -genkey -name secp384r1 -out server.key
自簽名公開金鑰(x509) (PEM-encodings .pem
|.crt
)
openssl req -new -x509 -sha256 -key server.key -out server.pem -days 3650
自訂資訊
-----Country Name (2 letter code) [AU]:CNState or Province Name (full name) [Some-State]:XxXxLocality Name (eg, city) []:XxXxOrganization Name (eg, company) [Internet Widgits Pty Ltd]:XX Co. LtdOrganizational Unit Name (eg, section) []:DevCommon Name (e.g. server FQDN or YOUR name) []:server nameEmail Address []:xxx@xxx.com
目錄結構
$GOPATH/src/grpc-go-practice/example/|—— hello-tls/ |—— client/ |—— main.go // 用戶端 |—— server/ |—— main.go // 服務端|—— keys/ // 認證目錄 |—— server.key |—— server.pem|—— proto/ |—— hello.proto // proto描述檔案 |—— hello.pb.go // proto編譯後檔案
範例程式碼
proto/helloworld.proto
及proto/hello.pb.go
檔案不需要改動
修改服務端代碼:server/main.go
package mainimport ( "net" pb "go-grpc-practice/example/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:50052")// 定義helloService並實現約定的介面type helloService struct{}// HelloService ...var HelloService = helloService{}func (h helloService) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) { resp := new(pb.HelloReply) resp.Message = "Hello " + in.Name + "." return resp, nil}func main() { listen, err := net.Listen("tcp", Address) if err != nil { grpclog.Fatalf("failed to listen: %v", err) } // TLS認證 creds, err := credentials.NewServerTLSFromFile("../../keys/server.pem", "../../keys/server.key") if err != nil { grpclog.Fatalf("Failed to generate credentials %v", err) } // 執行個體化grpc Server, 並開啟TLS認證 s := grpc.NewServer(grpc.Creds(creds)) // 註冊HelloService pb.RegisterHelloServer(s, HelloService) grpclog.Println("Listen on " + Address + " with TLS") s.Serve(listen)}
運行:
go run main.goListen on 127.0.0.1:50052 with TLS
服務端在執行個體化grpc Server時,可配置多種選項,TLS認證是其中之一
用戶端添加TLS認證:client/main.go
package mainimport ( pb "go-grpc-practice/example/proto" // 引入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:50052")func main() { // TLS串連 creds, err := credentials.NewClientTLSFromFile("../../keys/server.pem", "server name") if err != nil { grpclog.Fatalf("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.NewHelloClient(conn) // 調用方法 reqBody := new(pb.HelloRequest) reqBody.Name = "gRPC" r, err := c.SayHello(context.Background(), reqBody) if err != nil { grpclog.Fatalln(err) } grpclog.Println(r.Message)}
運行:
go run main.goHello gRPC
用戶端添加TLS認證的方式和服務端類似,在建立串連Dial
時,同樣可以配置多種選項,後面的樣本中會看到更多的選項。
Token認證樣本
再進一步,繼續擴充hello-tls項目,實現TLS + Token認證機制
目錄結構
$GOPATH/src/grpc-go-practice/example/|—— hello-token/ |—— client/ |—— main.go // 用戶端 |—— server/ |—— main.go // 服務端|—— keys/ // 認證目錄 |—— server.key |—— server.pem|—— proto/ |—— hello.proto // proto描述檔案 |—— hello.pb.go // proto編譯後檔案
範例程式碼
用戶端實現:client/main.go
package mainimport ( pb "go-grpc-practice/example/proto" // 引入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:50052" // OpenTLS 是否開啟TLS認證 OpenTLS = true)// customCredential 自訂認證type customCredential struct{}func (c customCredential) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) { return map[string]string{ "appid": "101010", "appkey": "i am key", }, nil}func (c customCredential) RequireTransportSecurity() bool { if OpenTLS { return true } return false}func main() { var err error var opts []grpc.DialOption if OpenTLS { // TLS串連 creds, err := credentials.NewClientTLSFromFile("../../keys/server.pem", "server name") if err != nil { grpclog.Fatalf("Failed to create TLS credentials %v", err) } opts = append(opts, grpc.WithTransportCredentials(creds)) } else { opts = append(opts, grpc.WithInsecure()) } // 使用自訂認證 opts = append(opts, grpc.WithPerRPCCredentials(new(customCredential))) conn, err := grpc.Dial(Address, opts...) if err != nil { grpclog.Fatalln(err) } defer conn.Close() // 初始化用戶端 c := pb.NewHelloClient(conn) // 調用方法 reqBody := new(pb.HelloRequest) reqBody.Name = "gRPC" r, err := c.SayHello(context.Background(), reqBody) if err != nil { grpclog.Fatalln(err) } grpclog.Println(r.Message)}
這裡我們定義了一個customCredential
結構,並實現了兩個方法GetRequestMetadata
和RequireTransportSecurity
。這是gRPC提供的自訂認證方式,每次RPC調用都會傳輸認證資訊。customCredential
其實是實現了grpc/credential
包內的PerRPCCredentials
介面。每次調用,token資訊會通過請求的metadata傳輸到服務端。下面具體看一下服務端如何擷取metadata中的資訊。
修改server/main.go中的SayHello方法:
package mainimport ( "fmt" "net" pb "go-grpc-practice/example/proto" "golang.org/x/net/context" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/credentials" // 引入grpc認證包 "google.golang.org/grpc/grpclog" "google.golang.org/grpc/metadata" // 引入grpc meta包)const ( // Address gRPC服務地址 Address = "127.0.0.1:50052")// 定義helloService並實現約定的介面type helloService struct{}// HelloService ...var HelloService = helloService{}func (h helloService) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) { // 解析metada中的資訊並驗證 md, ok := metadata.FromContext(ctx) if !ok { return nil, grpc.Errorf(codes.Unauthenticated, "無Token認證資訊") } var ( appid string appkey string ) if val, ok := md["appid"]; ok { appid = val[0] } if val, ok := md["appkey"]; ok { appkey = val[0] } if appid != "101010" || appkey != "i am key" { return nil, grpc.Errorf(codes.Unauthenticated, "Token認證資訊無效: appid=%s, appkey=%s", appid, appkey) } resp := new(pb.HelloReply) resp.Message = fmt.Sprintf("Hello %s.\nToken info: appid=%s,appkey=%s", in.Name, appid, appkey) return resp, nil}func main() { listen, err := net.Listen("tcp", Address) if err != nil { grpclog.Fatalf("failed to listen: %v", err) } // TLS認證 creds, err := credentials.NewServerTLSFromFile("../../keys/server.pem", "../../keys/server.key") if err != nil { grpclog.Fatalf("Failed to generate credentials %v", err) } // 執行個體化grpc Server, 並開啟TLS認證 s := grpc.NewServer(grpc.Creds(creds)) // 註冊HelloService pb.RegisterHelloServer(s, HelloService) grpclog.Println("Listen on " + Address + " with TLS + Token") s.Serve(listen)}
運行:
go run main.goListen on 50052 with TLS + Token
運行用戶端程式 client/main.go:
go run main.go// 認證成功結果Hello gRPCToken info: appid=101010,appkey=i am key// 認證失敗結果:rpc error: code = 16 desc = Token認證資訊無效: appID=101010, appKey=i am not key
參考
本系列範例程式碼