# gRPC認證
gRPC 默認提供了兩種認證方式:
* 基于SSL/TLS認證方式
* 遠程調用認證方式
兩種方式可以混合使用
# TLS認證示例
[https://github.com/dollarkillerx/GRPC-Study/tree/master/demo2](https://github.com/dollarkillerx/GRPC-Study/tree/master/demo2)
這里直接擴展hello項目,實現TLS認證機制
首先需要準備證書,在hello目錄新建keys目錄用于存放證書文件。
## 證書制作
### 制作私鑰 (.key)
~~~
# Key considerations for algorithm "RSA" ≥ 2048-bit
openssl 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]: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) []:server name
Email 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 main
import (
"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.go
Listen on 127.0.0.1:50052 with TLS
~~~
服務端在實例化grpc Server時,可配置多種選項,TLS認證是其中之一
### 客戶端添加TLS認證:client/main.go
~~~
package main
import (
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.go
Hello gRPC
~~~
客戶端添加TLS認證的方式和服務端類似,在創建連接`Dial`時,同樣可以配置多種選項,后面的示例中會看到更多的選項。
# Token認證示例
再進一步,繼續擴展hello-tls項目,實現TLS + Token認證機制
[https://github.com/dollarkillerx/GRPC-Study/tree/master/demo3](https://github.com/dollarkillerx/GRPC-Study/tree/master/demo3)
## 目錄結構
~~~
$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 main
import (
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 main
import (
"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.go
Listen on 50052 with TLS + Token
~~~
運行客戶端程序 client/main.go:
~~~
go run main.go
// 認證成功結果
Hello gRPC
Token info: appid=101010,appkey=i am key
// 認證失敗結果:
rpc error: code = 16 desc = Token認證信息無效: appID=101010, appKey=i am not key
~~~