1. 前言
前情回顧
之前我們演示的客戶端和服務端之間是沒有使用證書的,不是很安全。下面演示一下,服務呼叫之間加入自籤的證書驗證。
生產環境以網上購買的證書為準
2. 生成自簽證書
2.1 MAC生成自簽證書的教程連結:
2.2 Windows生成自簽證書的教程
- 登入如下連結
- 下載如下的openssl工具
- 安裝到某個目錄
- 進入安裝目錄的bin資料夾下
- 在所在的資料夾開啟cmd,並輸入
openssl
- 然後再執行如下的操作, 生成私鑰檔案
- 會生成一個
server.key
genrsa -des3 -out server.key 2048
- 建立證書請求
- 會生成一個
server.csr
genrsa -des3 -out server.key 2048
- 為了演示簡單,刪除私鑰中的密碼
- 會生成一個
server_no_password.key
rsa -in server.key -out server_no_password.key
- 生成公鑰檔案
- 會生成
server.crt
x509 -req -days 365 -in server.csr -signkey server_no_password.key -out server.crt
3. 改造服務端使用自簽證書
3.1 複製證書至程式碼下
在服務端的目錄下新建一個keys資料夾,並且上之前生成的server_no_password.key
和server.crt
複製到目錄下
3.2 改造程式碼新增證書認證
改造之前上一節的服務端程式碼,具體改造的部分如下標紅的部分
完整的服務端程式碼:
package main
import (
"gomicro-quickstart/grpc_server/service"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"log"
"net"
)
func main() {
// 1. 引用證書
tls, err := credentials.NewServerTLSFromFile("grpc_server/keys/server.crt", "grpc_server/keys/server_no_password.key")
if err != nil {
log.Fatal("服務端獲取證書失敗: ", err)
}
// 2. new一個grpc的server,並且加入證書
rpcServer := grpc.NewServer(grpc.Creds(tls))
// 3. 將剛剛我們新建的ProdService註冊進去
service.RegisterProdServiceServer(rpcServer, new(service.ProdService))
// 4. 新建一個listener,以tcp方式監聽8082埠
listener, err := net.Listen("tcp", ":8082")
if err != nil {
log.Fatal("服務監聽埠失敗", err)
}
// 5. 執行rpcServer,傳入listener
_ = rpcServer.Serve(listener)
}
3.1 執行程式碼並檢視客戶端的訪問錯誤
執行server
服務端
這時候我們同樣執行起來client
,發現會報如下的錯,因為我們的服務端使用證書加密了
4. 改造客戶端程式碼(單向認證)
4.1 複製公鑰給客戶端
這裡為了剛入門演示方便,採用了單向認證。
- 在客戶端程式碼下新建keys資料夾
- 將server.crt複製到keys資料夾下
4.2 改造客戶端程式碼
較上一章的程式碼的改動如下圖:
完整的客戶端程式碼如下:
package main
import (
"context"
"fmt"
"gomicro-quickstart/grpc_client/service"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"log"
)
func main() {
// 1. 新增公鑰證書的引用, codepie.fun是之前生成證書的時候填寫的common name
tls, err := credentials.NewClientTLSFromFile("grpc_client/keys/server.crt", "codepie.fun")
if err != nil {
log.Fatal("客戶端獲取證書失敗: ", err)
}
// 2. 新建連線,埠是服務端開放的8082埠
conn, err := grpc.Dial(":8082", grpc.WithTransportCredentials(tls))
if err != nil {
log.Fatal(err)
}
// 退出時關閉連結
defer conn.Close()
// 3. 呼叫Product.pb.go中的NewProdServiceClient方法
productServiceClient := service.NewProdServiceClient(conn)
// 4. 直接像呼叫本地方法一樣呼叫GetProductStock方法
resp, err := productServiceClient.GetProductStock(context.Background(), &service.ProductRequest{ProdId: 233})
if err != nil {
log.Fatal("呼叫gRPC方法錯誤: ", err)
}
fmt.Println("呼叫gRPC方法成功,ProdStock = ", resp.ProdStock)
}
4.3 執行客戶端檢視結果
單向認證成功,服務呼叫成功