一、什么是SSL/TLS?
傳輸層安全性協(xié)議(Transport Layer Security,縮寫作 TLS ),其前身安全套接層(Secure Sockets Layer,縮寫作 SSL )是一種安全協(xié)議,目的是為互聯(lián)網(wǎng)通信提供安全及數(shù)據(jù)完整性保障。根據(jù)傳輸層安全協(xié)議的規(guī)范,客戶端與服務(wù)端的連接安全應(yīng)該具備連接是私密的或連接是可靠的一種以上的特性。
SSL/TLS 協(xié)議通過 X.509 證書的數(shù)字文檔將網(wǎng)站的公司實(shí)體信息綁定到加密密鑰,每一個(gè)密鑰對都有一個(gè)私有密鑰和一個(gè)公有密鑰。私有密鑰是獨(dú)有的,一般位于服務(wù)器上,用于解密由公有密鑰加密過的信息,公有密鑰是公開的,與服務(wù)器進(jìn)行交互的每個(gè)人都可以持有公有密鑰,用公有密鑰加密的信息只能由私有密鑰來解密。
SSL/TLS 協(xié)議提供以下的服務(wù):
認(rèn)證用戶與服務(wù)器,確保數(shù)據(jù)發(fā)送到正確的客戶端和服務(wù)器;
加密數(shù)據(jù)以防止數(shù)據(jù)在傳輸過程中被竊取;
維護(hù)數(shù)據(jù)的完整性,確保數(shù)據(jù)在傳輸過程中不被改變。
二、什么是 SAN?
SAN(Subject Alternative Name) 是 SSL 標(biāo)準(zhǔn) x509 中定義的一個(gè)擴(kuò)展,使用了 SAN 字段的 SSL 證書,可以擴(kuò)展此證書支持的域名,使得一個(gè)證書可以支持多個(gè)不同域名的解析。
三、gRPC 的 SSL/TLS 加密認(rèn)證的實(shí)現(xiàn)
該部分內(nèi)容基于上一個(gè)博客gRPC-入門示例的代碼,進(jìn)行進(jìn)一步實(shí)現(xiàn)!如圖所示,回顧上一個(gè)博客中g(shù)RPC入門示例代碼,可以看出客戶端和服務(wù)端之間無任務(wù)加密認(rèn)證的過程,具有不安全性。因此,接下來的內(nèi)容將在原有代碼基礎(chǔ)上,分別實(shí)現(xiàn)SSL/TLS的單向認(rèn)證、雙向認(rèn)證、Token認(rèn)證的過程,增加安全性。
1、生成自簽證證書
CA 是一個(gè)受信任的實(shí)體,它管理和發(fā)布用于公共網(wǎng)絡(luò)中安全通信的安全證書和公鑰。由該受信任的實(shí)體所簽署或頒發(fā)的證書稱為 CA 簽名的證書。需要使用根證書CA來生成服務(wù)端、客戶端證書。
使用 OpenSSL 開源工具集創(chuàng)建一個(gè) CA 根證書
根證書(root certificate)是屬于根證書頒發(fā)機(jī)構(gòu)(CA)的公鑰證書??梢酝ㄟ^驗(yàn)證 CA 的簽名從而信任 CA ,任何人都可以得到 CA 的證書(含公鑰),用以驗(yàn)證它所簽發(fā)的證書(客戶端、服務(wù)端)。
1、去openssl的官網(wǎng)下載openssl,安裝完成后,在終端運(yùn)行以下命令,可以看到相應(yīng)的版本號。
C:\Users\xxxxx>openssl -version
OpenSSL 3.2.1 30 Jan 2024 (Library: OpenSSL 3.2.1 30 Jan 2024)
2、在原有項(xiàng)目工程下,新建一個(gè)目錄cert目錄,進(jìn)入該目錄下,打開終端運(yùn)行以下命令:
openssl genrsa -out ca.key 2048
最終,在cert目錄將會生成ca.key的文件。
3、在cert目錄下,新建 ca.conf文件,并且寫入以下內(nèi)容:
[ req ]
default_bits = 2048
distinguished_name = req_distinguished_name
[ req_distinguished_name ]
countryName = Country Name (2 letter code)
countryName_default = CN
stateOrProvinceName = State or Province Name (full name)
stateOrProvinceName_default = ChongQing
localityName = Locality Name (eg, city)
localityName_default = ChongQing
organizationName = Organization Name (eg, company)
organizationName_default = JiangShui
commonName = CommonName (e.g. server FQDN or YOUR name)
commonName_max = 64
commonName_default = jiangshui
然后運(yùn)行在終端運(yùn)行以下命令,創(chuàng)建ca證書簽發(fā)請求,得到ca.csr
openssl req -new -sha256 -out ca.csr -key ca.key -config ca.conf
如下圖所示,運(yùn)行命令后,填寫內(nèi)容均默認(rèn),全部按下enter即可,全部確認(rèn)完畢后,cert目錄下將會生成對應(yīng)ca.csr文件。
4、在終端下運(yùn)行以下命令,通過ca.key和ca.csr生成ca根證書,得到ca.crt
openssl x509 -req -days 3650 -in ca.csr -signkey ca.key -out ca.crt
2、單向認(rèn)證
生成服務(wù)端證書
1、運(yùn)行以下命令,生成服務(wù)端私鑰,server.key
openssl genrsa -out server.key 2048
2、在cert目錄下,新建server.conf文件,并且寫入以下內(nèi)容:
[ req ]
default_bits = 2048
distinguished_name = req_distinguished_name
[ req_distinguished_name ]
countryName = Country Name (2 letter code)
countryName_default = CN
stateOrProvinceName = State or Province Name (full name)
stateOrProvinceName_default = ChongQing
localityName = Locality Name (eg, city)
localityName_default = ChongQing
organizationName = Organization Name (eg, company)
organizationName_default = JiangShui
commonName = CommonName (e.g. server FQDN or YOUR name)
commonName_max = 64
commonName_default = jiangshui
[ req_ext ]
# 添加subjectAltName
subjectAltName = @alt_names
# 文件末尾添加. www.p-pp.cn代表允許的ServerName,自己隨便寫
[alt_names]
DNS.1 = www.jiangshui.cn
DNS.2 = www.libai.cn
IP = 127.0.0.1
然后運(yùn)行在終端運(yùn)行以下命令,創(chuàng)建證書簽發(fā)請求,得到server.csr
openssl req -new -sha256 -out server.csr -key server.key -config server.conf
3、在終端運(yùn)行以下命令,用CA證書和server.key、server.csr生成服務(wù)端證書(公鑰),得到server.pem
openssl x509 -req -days 3650 -CA ca.crt -CAkey ca.key -CAcreateserial -in server.csr -out server.pem -extensions req_ext -extfile server.conf
4、完成以上步驟后,查看cert目錄,是否存在以下圖片中所示的文件,如果對應(yīng)上,則說明生成各文件完畢。
修改服務(wù)端代碼
修改后服務(wù)端grpc_server.go代碼如下:
package main
import (
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/grpclog"
"grpc1/service"
"net"
)
func main() {
//引入TLS認(rèn)證相關(guān)文件(傳入公鑰和私鑰)
//配置 TLS認(rèn)證相關(guān)文件
creds, err := credentials.NewServerTLSFromFile("./cert/server.pem", "./cert/server.key")
if err != nil {
grpclog.Fatal("添加證書失敗", err)
}
//獲取grpc服務(wù)端,添加TLS認(rèn)證
rpcServer := grpc.NewServer(grpc.Creds(creds))
//服務(wù)端映射(注冊)到結(jié)構(gòu)體,調(diào)用product_grpc.pb.go中的RegisterProductServiceServer方法
service.RegisterProductServiceServer(rpcServer, service.ProductService)
// 建立tcp端口監(jiān)聽
listener, err := net.Listen("tcp", ":8002")
if err != nil {
grpclog.Fatal("啟動監(jiān)聽出錯(cuò)", err)
}
//服務(wù)端監(jiān)聽tcp連接
err = rpcServer.Serve(listener)
if err != nil {
grpclog.Fatal("啟動服務(wù)出錯(cuò)", err)
}
}
如圖所示,此時(shí),啟動服務(wù)端代碼,然后啟動客戶端調(diào)用服務(wù)端方法查詢庫存,出現(xiàn)失敗。
修改客戶端代碼
修改后服務(wù)端grpc_client.go代碼如下:
package main
import (
"context"
"fmt"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/grpclog"
"grpc1/service"
)
func main() {
//先創(chuàng)建證書池,讀取并解析公開證書,創(chuàng)建啟用 TLS 的證書(公鑰域名)
creds, err := credentials.NewClientTLSFromFile("./cert/server.pem", "www.jiangshui.cn")
if err != nil {
grpclog.Fatal("加載pem失敗", err)
}
//建立連接并添加傳輸憑證
conn, err := grpc.Dial(":8002", grpc.WithTransportCredentials(creds))
if err != nil {
grpclog.Fatal("did not connect", err)
}
//關(guān)閉連接
defer conn.Close()
//調(diào)用product_grpc.pb.go中的NewProductServiceClient方法建立客戶端
prodClient := service.NewProductServiceClient(conn)
request := &service.ProductRequest{
ProdId: 123,
}
//遠(yuǎn)程調(diào)用方法GetProductStock
stockResponse, err := prodClient.GetProductStock(context.Background(), request)
if err != nil {
grpclog.Fatal("查詢庫存失敗", err)
}
fmt.Println("查詢成功", stockResponse.ProdStock)
}
如圖所示,此時(shí),啟動服務(wù)端代碼,然后啟動客戶端調(diào)用服務(wù)端方法查詢庫存,查詢成功,實(shí)現(xiàn)了一個(gè)單向認(rèn)證。
3、雙向認(rèn)證
雙向認(rèn)證需要在單向認(rèn)證的基礎(chǔ)上,繼續(xù)生成客戶端證書,然后對客戶端和服務(wù)端代碼進(jìn)一步整改,才能完成雙向認(rèn)證。本人在實(shí)施雙向認(rèn)證的時(shí)候,也遇到了許多問題。當(dāng)我利用OpenSSL 開源工具集生成自簽證證書,然后按步驟生成完客戶端和服務(wù)端公私鑰,最后修改客戶端和服務(wù)端雙向認(rèn)證代碼并且運(yùn)行測試后,始終出現(xiàn)以下問題:
FATAL: 查詢庫存出錯(cuò)rpc error: code = Unavailable desc = connection error: desc = "transport: authentication handshake failed: tls: failed to verify certificate:
x509: certificate signed by unknown authority (possibly because of \"x509: invalid signature: parent certificate cannot sign this kind of certificate\" while trying to verify candid
ate authority certificate \"jiangshui.com\")"
該錯(cuò)誤在我多次嘗試解決該bug,甚至直接搬用別人的代碼直接使用也未能解決!其中包括參考了許多博客的教程以及碼神之路的教程,都未能解決相關(guān)問題!
參考博客1:gRPC教程 — TLS單向認(rèn)證、雙向認(rèn)證、Token認(rèn)證、攔截器
參考博客2:【gRPC】自簽CA、服務(wù)端和客戶端雙向認(rèn)證
參考博客3:碼神之路教程
大家可以使用以上博客教程中的其中方法先進(jìn)行嘗試,看看如果能夠測試成功,自然皆大歡喜!如果測試失敗,出現(xiàn)與我相同錯(cuò)誤!接下來,我將提供一種與以上博客不同的方法來實(shí)現(xiàn)雙向認(rèn)證過程!與上述博客中描述的方法不同,此處我不在利用OpenSSL 開源工具集生成自簽證書以及服務(wù)端、客戶端的公私鑰。此處,我將直接使用go語言代碼和相關(guān)庫實(shí)現(xiàn)自簽證書以及服務(wù)端、客戶端的公私鑰生成。
用go代碼生成自簽證書和客戶端、服務(wù)端公私鑰
在cert文件目錄新建一個(gè)cert.go文件,并且在文件中寫入以下代碼:
package main
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"math/big"
"net"
"os"
"time"
)
func main() {
// 生成CA證書
caCert, caPrivateKey, err := generateCACertificate()
if err != nil {
panic(err)
}
// 生成服務(wù)端證書
err = generateCert("server.crt", "server.key", caCert, caPrivateKey, "localhost", true)
if err != nil {
panic(err)
}
// 生成客戶端證書
err = generateCert("client.crt", "client.key", caCert, caPrivateKey, "Client", false)
if err != nil {
panic(err)
}
}
func generateCACertificate() (*x509.Certificate, *ecdsa.PrivateKey, error) {
caPrivateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
return nil, nil, err
}
serialNumber, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128))
if err != nil {
return nil, nil, err
}
caTemplate := x509.Certificate{
SerialNumber: serialNumber,
Subject: pkix.Name{
Organization: []string{"Example CA"},
},
NotBefore: time.Now(),
NotAfter: time.Now().AddDate(10, 0, 0), // 10 years
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth},
BasicConstraintsValid: true,
IsCA: true,
}
caCertBytes, err := x509.CreateCertificate(rand.Reader, &caTemplate, &caTemplate, &caPrivateKey.PublicKey, caPrivateKey)
if err != nil {
return nil, nil, err
}
caCert, err := x509.ParseCertificate(caCertBytes)
if err != nil {
return nil, nil, err
}
// Save the CA certificate
saveCert("ca.crt", caCertBytes)
// Save the CA private key
savePrivateKey("ca.key", caPrivateKey)
return caCert, caPrivateKey, nil
}
func generateCert(certFilename, keyFilename string, caCert *x509.Certificate, caPrivateKey *ecdsa.PrivateKey, commonName string, isServer bool) error {
privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
return err
}
serialNumber, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128))
if err != nil {
return err
}
certTemplate := x509.Certificate{
SerialNumber: serialNumber,
Subject: pkix.Name{
CommonName: commonName,
Organization: []string{"Example Org"},
},
NotBefore: time.Now(),
NotAfter: time.Now().AddDate(1, 0, 0), // 1 year
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
BasicConstraintsValid: true,
}
// 為證書添加SANs字段
certTemplate.DNSNames = append(certTemplate.DNSNames, "localhost")
certTemplate.IPAddresses = append(certTemplate.IPAddresses, net.ParseIP("127.0.0.1"))
if isServer {
certTemplate.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}
}
certBytes, err := x509.CreateCertificate(rand.Reader, &certTemplate, caCert, &privateKey.PublicKey, caPrivateKey)
if err != nil {
return err
}
saveCert(certFilename, certBytes)
savePrivateKey(keyFilename, privateKey)
return nil
}
func saveCert(filename string, certBytes []byte) {
certPEMFile, err := os.Create(filename)
if err != nil {
panic(err)
}
defer certPEMFile.Close()
pem.Encode(certPEMFile, &pem.Block{Type: "CERTIFICATE", Bytes: certBytes})
}
func savePrivateKey(filename string, privateKey *ecdsa.PrivateKey) {
keyFile, err := os.Create(filename)
if err != nil {
panic(err)
}
defer keyFile.Close()
privBytes, err := x509.MarshalECPrivateKey(privateKey)
if err != nil {
panic(err)
}
pem.Encode(keyFile, &pem.Block{Type: "EC PRIVATE KEY", Bytes: privBytes})
}
代碼中的caTemplate、certTemplate部分類似于OpenSSL 開源工具集生成證書,相關(guān)部分可以根據(jù)自己的需求進(jìn)行修改!此外,證書保存的路徑根據(jù)自己的具體情況,進(jìn)行修改!或者生成完證書后,自己將證書文件移動到對應(yīng)的目錄下。
此處圖片中的內(nèi)容則是類似于OpenSSL 開源工具集生成SAN證書部分,添加SANs字段,DNSName可以根據(jù)自身需求填寫(添加多個(gè))。
修改服務(wù)端代碼
server_grpc.go
package main
import (
"crypto/tls"
"crypto/x509"
"fmt"
"google.golang.org/grpc/grpclog"
"grpc2/service"
"io/ioutil"
"net"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
)
func main() {
// 證書認(rèn)證-雙向認(rèn)證
// 從證書相關(guān)文件中讀取和解析信息,得到證書公鑰、密鑰對
cert, err := tls.LoadX509KeyPair("./cert/server.crt", "./cert/server.key")
if err != nil {
grpclog.Fatalf("failed to load server certificate: %v", err)
}
caCert, err := ioutil.ReadFile("./cert/ca.crt")
if err != nil {
grpclog.Fatalf("failed to read ca certificate: %v", err)
}
// 創(chuàng)建一個(gè)新的、空的證書池
caCertPool := x509.NewCertPool()
// 附加來自 CA 的證書
if !caCertPool.AppendCertsFromPEM(caCert) {
grpclog.Fatalf("failed to append ca certs")
}
// 構(gòu)建基于 TLS 的 TransportCredentials 選項(xiàng)
config := &tls.Config{
Certificates: []tls.Certificate{cert}, // 服務(wù)器的證書和私鑰
ClientAuth: tls.RequireAndVerifyClientCert, //啟用并要求客戶端證書驗(yàn)證
ClientCAs: caCertPool, //用于驗(yàn)證客戶端證書的 CA 證書
}
listener, err := net.Listen("tcp", ":50051")
if err != nil {
grpclog.Fatalf("failed to listen: %v", err)
}
rpcServer := grpc.NewServer(grpc.Creds(credentials.NewTLS(config)))
// 在此注冊您的 gRPC 服務(wù)處理程序
service.RegisterProductServiceServer(rpcServer, service.ProductService)
if err != nil {
grpclog.Fatal("啟動監(jiān)聽出錯(cuò)", err)
}
err = rpcServer.Serve(listener)
if err != nil {
grpclog.Fatal("啟動服務(wù)出錯(cuò)", err)
}
fmt.Println("啟動grpc服務(wù)端成功")
}
修改客戶端代碼
client_grpc.go
package main
import (
"context"
"crypto/tls"
"crypto/x509"
"fmt"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/grpclog"
"grpc2/service"
"io/ioutil"
)
func main() {
// 證書認(rèn)證-雙向認(rèn)證
// 從證書相關(guān)文件中讀取和解析信息,得到證書公鑰、密鑰對
clientCert, err := tls.LoadX509KeyPair("./cert/client.crt", "./cert/client.key")
if err != nil {
grpclog.Fatalf("failed to load client certificate: %v", err)
}
// 創(chuàng)建一個(gè)新的、空的證書池
certPool := x509.NewCertPool()
ca, err := ioutil.ReadFile("./cert/ca.crt")
if err != nil {
grpclog.Fatalf("could not read ca certificate: %v", err)
}
// 附加來自 CA 的證書
if ok := certPool.AppendCertsFromPEM(ca); !ok {
grpclog.Fatalf("failed to append ca certs")
}
// 構(gòu)建基于 TLS 的 TransportCredentials 選項(xiàng)
config := &tls.Config{
Certificates: []tls.Certificate{clientCert}, // 客戶端的證書和私鑰
ServerName: "localhost", //驗(yàn)證DNSName
RootCAs: certPool, // CA證書用于驗(yàn)證服務(wù)器證書
}
conn, err := grpc.Dial("localhost:50051", grpc.WithTransportCredentials(credentials.NewTLS(config)))
if err != nil {
grpclog.Fatalf("failed to dial server: %v", err)
}
defer conn.Close()
// 現(xiàn)在您可以使用 `conn` 創(chuàng)建客戶端并進(jìn)行 gRPC 調(diào)用
prodClient := service.NewProductServiceClient(conn)
request := &service.ProductRequest{
ProdId: 123,
}
stockResponse, err := prodClient.GetProductStock(context.Background(), request)
if err != nil {
grpclog.Fatal("查詢庫存出錯(cuò)", err)
}
fmt.Println("查詢成功", stockResponse.ProdStock)
}
客戶端和服務(wù)端代碼修改完成后,啟動服務(wù)端,運(yùn)行客戶端進(jìn)行測試,此時(shí)不再出現(xiàn)之前所遇到的錯(cuò)誤。如下圖所示,成功調(diào)用到獲取庫存的方法,查詢庫存成功!
4、token認(rèn)證
gRPC為每個(gè)gRPC方法調(diào)用提供了Token認(rèn)證支持,可以基于用戶傳入的Token判斷用戶是否登陸、以及權(quán)限等。
服務(wù)端添加用戶名密碼的校驗(yàn)
server_grpc.go
package main
import (
"context"
"crypto/tls"
"crypto/x509"
"fmt"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/grpclog"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
"grpc2/service"
"io/ioutil"
"net"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
)
func main() {
// 證書認(rèn)證-雙向認(rèn)證
// 從證書相關(guān)文件中讀取和解析信息,得到證書公鑰、密鑰對
cert, err := tls.LoadX509KeyPair("./cert/server.crt", "./cert/server.key")
if err != nil {
grpclog.Fatalf("failed to load server certificate: %v", err)
}
caCert, err := ioutil.ReadFile("./cert/ca.crt")
if err != nil {
grpclog.Fatalf("failed to read ca certificate: %v", err)
}
// 創(chuàng)建一個(gè)新的、空的證書池
caCertPool := x509.NewCertPool()
// 附加來自 CA 的證書
if !caCertPool.AppendCertsFromPEM(caCert) {
grpclog.Fatalf("failed to append ca certs")
}
// 構(gòu)建基于 TLS 的 TransportCredentials 選項(xiàng)
config := &tls.Config{
Certificates: []tls.Certificate{cert}, // 服務(wù)器的證書和私鑰
ClientAuth: tls.RequireAndVerifyClientCert, //啟用并要求客戶端證書驗(yàn)證
ClientCAs: caCertPool, //用于驗(yàn)證客戶端證書的 CA 證書
}
listener, err := net.Listen("tcp", ":50051")
if err != nil {
grpclog.Fatalf("failed to listen: %v", err)
}
//實(shí)現(xiàn)token認(rèn)證,需要合法的用戶名和密碼
//實(shí)現(xiàn)一個(gè)攔截器
var authInterceptor grpc.UnaryServerInterceptor
authInterceptor = func(
ctx context.Context,
req any,
info *grpc.UnaryServerInfo,
handler grpc.UnaryHandler,
) (resp any, err error) {
//攔截普通方法請求,驗(yàn)證Token
err = Auth(ctx)
if err != nil {
return
}
return handler(ctx, req)
}
//添加在服務(wù)端攔截器
rpcServer := grpc.NewServer(grpc.Creds(credentials.NewTLS(config)), grpc.UnaryInterceptor(authInterceptor))
// 在此注冊您的 gRPC 服務(wù)處理程序
service.RegisterProductServiceServer(rpcServer, service.ProductService)
if err != nil {
grpclog.Fatal("啟動監(jiān)聽出錯(cuò)", err)
}
err = rpcServer.Serve(listener)
if err != nil {
grpclog.Fatal("啟動服務(wù)出錯(cuò)", err)
}
fmt.Println("啟動grpc服務(wù)端成功")
}
func Auth(ctx context.Context) error {
//拿到傳輸?shù)挠脩裘兔艽a
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
return fmt.Errorf("missing credentials")
}
var user string
var password string
if val, ok := md["user"]; ok {
user = val[0]
}
if val, ok := md["password"]; ok {
password = val[0]
}
if user != "admin" || password != "admin" {
return status.Errorf(codes.Unauthenticated, "token不合法")
}
return nil
}
此時(shí),啟動客戶端遠(yuǎn)程調(diào)用方法失敗,出現(xiàn)token不合法的提示,如下圖所示
修改客戶端代碼,以適應(yīng)token認(rèn)證
客戶段需要攜帶token進(jìn)行傳入,使得服務(wù)端能進(jìn)行正確驗(yàn)證,因此需要修改客戶端代碼,其中主要修改以下代碼:
//客戶端攜帶token傳入
conn, err := grpc.Dial("localhost:50051", grpc.WithTransportCredentials(credentials.NewTLS(config)), grpc.WithPerRPCCredentials(token))
通過grpc.WithPerRPCCredentials(token)函數(shù)攜帶token。通過追蹤該函數(shù),可得知該函數(shù)接收的參數(shù)為一個(gè)接口,因此客戶端傳入的token,需要定義一個(gè)結(jié)構(gòu)體將數(shù)據(jù)傳入,并且將該結(jié)構(gòu)體需要實(shí)現(xiàn)grpc.PerRPCCredentials接口。
type PerRPCCredentials interface {
// 返回需要認(rèn)證的必要信息
GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error)
// 是否使用安全鏈接(TLS)
RequireTransportSecurity() bool
}
實(shí)現(xiàn)grpc.PerRPCCredentials接口
在原來項(xiàng)目的基礎(chǔ)上,新建auth文件目錄,并且新建auth.go文件,寫入以下代碼實(shí)現(xiàn)相關(guān)接口:
package auth
import "context"
type Authentication struct {
User string
Password string
}
func (a *Authentication) GetRequestMetadata(context.Context, ...string) (
map[string]string, error,
) {
return map[string]string{"user": a.User, "password": a.Password}, nil
}
func (a *Authentication) RequireTransportSecurity() bool {
return false
}
修改客戶端代碼
client_grpc.go
package main
import (
"context"
"crypto/tls"
"crypto/x509"
"fmt"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/grpclog"
"grpc2/grpc_client/auth"
"grpc2/service"
"io/ioutil"
)
func main() {
// 證書認(rèn)證-雙向認(rèn)證
// 從證書相關(guān)文件中讀取和解析信息,得到證書公鑰、密鑰對
clientCert, err := tls.LoadX509KeyPair("./cert/client.crt", "./cert/client.key")
if err != nil {
grpclog.Fatalf("failed to load client certificate: %v", err)
}
// 創(chuàng)建一個(gè)新的、空的證書池
certPool := x509.NewCertPool()
ca, err := ioutil.ReadFile("./cert/ca.crt")
if err != nil {
grpclog.Fatalf("could not read ca certificate: %v", err)
}
// 附加來自 CA 的證書
if ok := certPool.AppendCertsFromPEM(ca); !ok {
grpclog.Fatalf("failed to append ca certs")
}
// 構(gòu)建基于 TLS 的 TransportCredentials 選項(xiàng)
config := &tls.Config{
Certificates: []tls.Certificate{clientCert}, // 客戶端的證書和私鑰
ServerName: "localhost", //驗(yàn)證DNSName
RootCAs: certPool, // CA證書用于驗(yàn)證服務(wù)器證書
}
token := &auth.Authentication{
User: "admin",
Password: "admin",
}
//客戶端攜帶token傳入
conn, err := grpc.Dial("localhost:50051", grpc.WithTransportCredentials(credentials.NewTLS(config)), grpc.WithPerRPCCredentials(token))
if err != nil {
grpclog.Fatalf("failed to dial server: %v", err)
}
defer conn.Close()
// 現(xiàn)在您可以使用 `conn` 創(chuàng)建客戶端并進(jìn)行 gRPC 調(diào)用
prodClient := service.NewProductServiceClient(conn)
request := &service.ProductRequest{
ProdId: 123,
}
stockResponse, err := prodClient.GetProductStock(context.Background(), request)
if err != nil {
grpclog.Fatal("查詢庫存出錯(cuò)", err)
}
fmt.Println("查詢成功", stockResponse.ProdStock)
}
最后,啟動服務(wù)端,運(yùn)行客戶端代碼進(jìn)行測試。當(dāng)攜帶的token與服務(wù)端要求的不一致時(shí),出現(xiàn)下圖所示的token不合法,查詢失敗。文章來源:http://www.zghlxwxcb.cn/news/detail-845754.html
當(dāng)攜帶的token與服務(wù)端要求的一致時(shí),出現(xiàn)下圖所示查詢成功!文章來源地址http://www.zghlxwxcb.cn/news/detail-845754.html
到了這里,關(guān)于gRPC — SSL/TLS單向認(rèn)證、雙向認(rèn)證、Token認(rèn)證的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!