Golang gRPC實踐 連載四 gRPC認證

gRPC 默認提供了兩種認證方式:git

  • 基於SSL/TLS認證方式github

  • 遠程調用認證方式golang

兩種方式能夠混合使用app

TLS認證示例

這裏直接擴展hello項目,實現TLS認證機制tcp

首先須要準備證書,在hello目錄新建keys目錄用於存放證書文件。ide

證書製做

製做私鑰 (.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.protoproto/hello.pb.go文件不須要改動ui

修改服務端代碼: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)
}

運行:google

go run main.go

Listen on 127.0.0.1:50052 with TLS

服務端在實例化grpc Server時,可配置多種選項,TLS認證是其中之一code

客戶端添加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)
}

運行:server

go run main.go

Hello 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 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結構,並實現了兩個方法GetRequestMetadataRequireTransportSecurity。這是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

參考

本系列示例代碼

相關文章
相關標籤/搜索