Go gRPC進階-TLS認證+自定義方法認證(七)
前言
前面篇章的gRPC都是明文傳輸的,容易被篡改數據。本章將介紹如何為gRPC添加安全機制,包括TLS證書認證和Token認證。
TLS證書認證
什麼是TLS
TLS(Transport Layer Security,安全傳輸層),TLS是建立在傳輸層
TCP協議之上的協議,服務於應用層,它的前身是SSL(Secure Socket Layer,安全套接字層),它實現了將應用層的報文進行加密後再交由TCP進行傳輸的功能。
TLS的作用
TLS協議主要解決如下三個網路安全問題。
- 保密(message privacy),保密通過加密encryption實現,所有資訊都加密傳輸,第三方無法嗅探;
- 完整性(message integrity),通過MAC校驗機制,一旦被篡改,通訊雙方會立刻發現;
- 認證(mutual authentication),雙方認證,雙方都可以配備證書,防止身份被冒充;
生成私鑰
生成RSA私鑰:openssl genrsa -out server.key 2048
生成RSA私鑰,命令的最後一個參數,將指定生成密鑰的位數,如果沒有指定,默認512
生成ECC私鑰:openssl ecparam -genkey -name secp384r1 -out server.key
生成ECC私鑰,命令為橢圓曲線密鑰參數生成及操作,本文中ECC曲線選擇的是secp384r1
生成公鑰
openssl req -new -x509 -sha256 -key server.key -out server.pem -days 3650
openssl req:生成自簽名證書,-new指生成證書請求、-sha256指使用sha256加密、-key指定私鑰文件、-x509指輸出證書、-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) []:go-grpc-example
Email Address []:[email protected]
服務端構建TLS證書並認證
func main() {
// 監聽本地埠
listener, err := net.Listen(Network, Address)
if err != nil {
log.Fatalf("net.Listen err: %v", err)
}
// 從輸入證書文件和密鑰文件為服務端構造TLS憑證
creds, err := credentials.NewServerTLSFromFile("../pkg/tls/server.pem", "../pkg/tls/server.key")
if err != nil {
log.Fatalf("Failed to generate credentials %v", err)
}
// 新建gRPC伺服器實例,並開啟TLS認證
grpcServer := grpc.NewServer(grpc.Creds(creds))
// 在gRPC伺服器註冊我們的服務
pb.RegisterSimpleServer(grpcServer, &SimpleService{})
log.Println(Address + " net.Listing whth TLS and token...")
//用伺服器 Serve() 方法以及我們的埠資訊區實現阻塞等待,直到進程被殺死或者 Stop() 被調用
err = grpcServer.Serve(listener)
if err != nil {
log.Fatalf("grpcServer.Serve err: %v", err)
}
}
credentials.NewServerTLSFromFile
:從輸入證書文件和密鑰文件為服務端構造TLS憑證grpc.Creds
:返回一個ServerOption,用於設置伺服器連接的憑證。
完整server.go程式碼
客戶端配置TLS連接
var grpcClient pb.SimpleClient
func main() {
//從輸入的證書文件中為客戶端構造TLS憑證
creds, err := credentials.NewClientTLSFromFile("../pkg/tls/server.pem", "go-grpc-example")
if err != nil {
log.Fatalf("Failed to create TLS credentials %v", err)
}
// 連接伺服器
conn, err := grpc.Dial(Address, grpc.WithTransportCredentials(creds))
if err != nil {
log.Fatalf("net.Connect err: %v", err)
}
defer conn.Close()
// 建立gRPC連接
grpcClient = pb.NewSimpleClient(conn)
}
credentials.NewClientTLSFromFile
:從輸入的證書文件中為客戶端構造TLS憑證。grpc.WithTransportCredentials
:配置連接級別的安全憑證(例如,TLS/SSL),返回一個DialOption,用於連接伺服器。
完整client.go程式碼
到這裡,已經完成TLS證書認證了,gRPC傳輸不再是明文傳輸。此外,添加自定義的驗證方法能使gRPC相對更安全。下面以Token認證為例,介紹gRPC如何添加自定義驗證方法。
Token認證
客戶端發請求時,添加Token到上下文context.Context
中,伺服器接收到請求,先從上下文中獲取Token驗證,驗證通過才進行下一步處理。
客戶端請求添加Token到上下文中
type PerRPCCredentials interface {
GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error)
RequireTransportSecurity() bool
}
gRPC 中默認定義了 PerRPCCredentials
,是提供用於自定義認證的介面,它的作用是將所需的安全認證資訊添加到每個RPC方法的上下文中。其包含 2 個方法:
GetRequestMetadata
:獲取當前請求認證所需的元數據RequireTransportSecurity
:是否需要基於 TLS 認證進行安全傳輸
接下來我們實現這兩個方法
// Token token認證
type Token struct {
AppID string
AppSecret string
}
// GetRequestMetadata 獲取當前請求認證所需的元數據(metadata)
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 true
}
然後再客戶端中調用Dial時添加自定義驗證方法進去
//構建Token
token := auth.Token{
AppID: "grpc_token",
AppSecret: "123456",
}
// 連接伺服器
conn, err := grpc.Dial(Address, grpc.WithTransportCredentials(creds), grpc.WithPerRPCCredentials(&token))
完整client.go程式碼
服務端驗證Token
首先需要從上下文中獲取元數據,然後從元數據中解析Token進行驗證
// Check 驗證token
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_token" || appSecret != "123456" {
return status.Errorf(codes.Unauthenticated, "Token無效: app_id=%s, app_secret=%s", appID, appSecret)
}
return nil
}
metadata.FromIncomingContext
:從上下文中獲取元數據
完整server.go程式碼
總結
本篇介紹如何為gRPC添加TLS證書認證和自定義認證,從而讓gRPC更安全。然而上面Token認證的程式碼中,服務端驗證Token時,需要在每個方法前添加Token驗證,這樣比較麻煩。下篇將介紹如何使用gRPC攔截器,取消掉這個重複步驟。
教程源碼地址://github.com/Bingjian-Zhu/go-grpc-example
參考: