Go gRPC 系列八:對 RPC 方法作自定義認證

前言

你們好,我是煎魚,在前面的章節中,咱們介紹了兩種(證書算一種)可全局認證的方法:git

  1. TLS 證書認證
  2. 基於 CA 的 TLS 證書認證
  3. Unary and Stream interceptor

而在實際需求中,經常會對某些模塊的 RPC 方法作特殊認證或校驗。今天將會講解、實現這塊的功能點github

課前知識

type PerRPCCredentials interface {
    GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error)
    RequireTransportSecurity() bool
}
複製代碼

在 gRPC 中默認定義了 PerRPCCredentials,它就是本章節的主角,是 gRPC 默認提供用於自定義認證的接口,它的做用是將所需的安全認證信息添加到每一個 RPC 方法的上下文中。其包含 2 個方法:golang

  • GetRequestMetadata:獲取當前請求認證所需的元數據(metadata)
  • RequireTransportSecurity:是否須要基於 TLS 認證進行安全傳輸

目錄結構

新建 simple_token_server/server.go 和 simple_token_client/client.go,目錄結構以下:安全

go-grpc-example
├── client
│   ├── simple_client
│   ├── simple_http_client
│   ├── simple_token_client
│   └── stream_client
├── conf
├── pkg
├── proto
├── server
│   ├── simple_http_server
│   ├── simple_server
│   ├── simple_token_server
│   └── stream_server
└── vendor
複製代碼

gRPC

Client

package main

import (
	"context"
	"log"

	"google.golang.org/grpc"

	"github.com/EDDYCJY/go-grpc-example/pkg/gtls"
	pb "github.com/EDDYCJY/go-grpc-example/proto"
)

const PORT = "9004"

type Auth struct {
	AppKey    string
	AppSecret string
}

func (a *Auth) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) {
	return map[string]string{"app_key": a.AppKey, "app_secret": a.AppSecret}, nil
}

func (a *Auth) RequireTransportSecurity() bool {
	return true
}

func main() {
	tlsClient := gtls.Client{
		ServerName: "go-grpc-example",
		CertFile:   "../../conf/server/server.pem",
	}
	c, err := tlsClient.GetTLSCredentials()
	if err != nil {
		log.Fatalf("tlsClient.GetTLSCredentials err: %v", err)
	}

	auth := Auth{
		AppKey:    "eddycjy",
		AppSecret: "20181005",
	}
	conn, err := grpc.Dial(":"+PORT, grpc.WithTransportCredentials(c), grpc.WithPerRPCCredentials(&auth))
	...
}
複製代碼

在 Client 端,重點實現 type PerRPCCredentials interface 所需的方法,關注兩點便可:bash

  • struct Auth:GetRequestMetadata、RequireTransportSecurity
  • grpc.WithPerRPCCredentials

Server

package main

import (
	"context"
	"log"
	"net"

	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/metadata"
	"google.golang.org/grpc/status"

	"github.com/EDDYCJY/go-grpc-example/pkg/gtls"
	pb "github.com/EDDYCJY/go-grpc-example/proto"
)

type SearchService struct {
	auth *Auth
}

func (s *SearchService) Search(ctx context.Context, r *pb.SearchRequest) (*pb.SearchResponse, error) {
	if err := s.auth.Check(ctx); err != nil {
		return nil, err
	}
	return &pb.SearchResponse{Response: r.GetRequest() + " Token Server"}, nil
}

const PORT = "9004"

func main() {
	...
}

type Auth struct {
	appKey    string
	appSecret string
}

func (a *Auth) Check(ctx context.Context) error {
	md, ok := metadata.FromIncomingContext(ctx)
	if !ok {
		return status.Errorf(codes.Unauthenticated, "自定義認證 Token 失敗")
	}

	var (
		appKey    string
		appSecret string
	)
	if value, ok := md["app_key"]; ok {
		appKey = value[0]
	}
	if value, ok := md["app_secret"]; ok {
		appSecret = value[0]
	}

	if appKey != a.GetAppKey() || appSecret != a.GetAppSecret() {
		return status.Errorf(codes.Unauthenticated, "自定義認證 Token 無效")
	}

	return nil
}

func (a *Auth) GetAppKey() string {
	return "eddycjy"
}

func (a *Auth) GetAppSecret() string {
	return "20181005"
}
複製代碼

在 Server 端就更簡單了,實際就是調用 metadata.FromIncomingContext 從上下文中獲取 metadata,再在不一樣的 RPC 方法中進行認證檢查app

驗證

從新啓動 server.go 和 client.go,獲得如下結果:ui

$ go run client.go
2018/10/05 20:59:58 resp: gRPC Token Server
複製代碼

修改 client.go 的值,製造二者不一致,獲得無效結果:google

$ go run client.go
2018/10/05 21:00:05 client.Search err: rpc error: code = Unauthenticated desc = invalid token
exit status 1
複製代碼

一個個加太麻煩

我相信你確定會問一個個加,也太麻煩了吧?有這個想法的你,應當把 type PerRPCCredentials interface 作成一個攔截器(interceptor)spa

總結

本章節比較簡單,主要是針對 RPC 方法的自定義認證進行了介紹,若是是想作全局的,建議是觸類旁通從攔截器下手哦。code

若是有任何疑問或錯誤,歡迎在 issues 進行提問或給予修正意見,若是喜歡或對你有所幫助,歡迎 Star,對做者是一種鼓勵和推動。

個人公衆號

image

參考

本系列示例代碼

相關文章
相關標籤/搜索