Loading

gRPC-认证

客户端和服务端之间的调用,可以加入证书,实现调用的安全性

TLS是建立在TCP协议之上的协议,服务于应用层,前身是SSL,实现了将应用层的报文进行加密后再交由TCP进行传输的功能,生产环境可以购买证书或者使用一些平台发放的免费证书

自签证书

  1. 安装openssl

  2. 生成私钥文件

    openssl genrsa -des3 -out ca.key 2048      
    
    • genrsa:这是 OpenSSL 的子命令,用于生成 RSA 私钥。
    • -des3:表明使用 DES3 对称加密算法对生成的私钥进行加密。在生成私钥时,你会被要求输入一个密码,之后每次使用该私钥时都需要输入此密码。
    • -out ca.key:指定将生成的私钥保存到名为ca.key的文件中。
    • 2048:指定生成的 RSA 私钥的位数为 2048 位。位数越大,私钥就越安全,不过生成和使用私钥时的计算量也会相应增加
  3. 创建证书请求

    openssl req -new -key ca.key -out ca.csr     
    
    • req:OpenSSL 的子命令,用于处理证书签名请求相关操作。
    • -new:表明要创建一个新的证书签名请求。
    • -key ca.key:指定用于签名请求的私钥文件。这里使用之前生成的ca.key文件,该私钥对应的公钥会被包含在 CSR 中。由于ca.key是使用 DES3 加密的,执行此命令时会要求输入之前设置的密码。
    • -out ca.csr:指定将生成的证书签名请求保存到名为ca.csr的文件中
  4. 生成证书公钥

     openssl x509 -req -days 365 -in ca.csr -signkey ca.key -out ca.crt    
    
    • x509:OpenSSL 的子命令,用于处理 X.509 格式的证书。
    • -req:表明输入的是一个证书签名请求(CSR),需要对其进行签名处理。
    • -days 365:指定生成的证书的有效期为 365 天。你可以根据需要调整这个数值。
    • -in server.csr:指定输入的证书签名请求文件,这里使用之前生成的 server.csr 文件。
    • -signkey server.key:指定用于签名的私钥文件,这里使用之前生成的 server.key 文件。由于 server.key 是使用 DES3 加密的,执行此命令时会要求输入之前设置的密码。
    • -out server.crt:指定将生成的数字证书保存到名为 server.crt 的文件中
    1. SAN证书
    • 老版本的go生成到上面的证书就可以,新版本对安全有了更高要求,使用SNA证书,SAN是 SSL 标准 x509 中定义的一个扩展。使用了 SAN 字段的 SSL 证书,可以扩展此证书支持的域名,使得一个证书可以支持多个不同域名的解析

    • 生成SAN证书,需要找到openssl安装目录下的的openssl.cnf文件,复制到项目里,然后修改对应的配置

      # openssl.cnf文件 ,找到对应的配置条目修改
      
      copy_extensions = copy  # 原先是注释掉的,把注释解开
      
      
      req_extensions = v3_req  # 原先是注释掉的,把注释解开
      
      
      # 找到 [v3_req] 在下面新加
      subjectAltName = @alt_names
      
      # 添加 [v3_req] 最后添加[alt_names]标签和字段
      [alt_names]
      DNS.1 = *.xxx.com  # 配置要验证的域名,可以配置多个 DNS.2 = xxx
      
      
      
      
    • 修改完cnf文件后生成SNA证书

      1. openssl genpkey -algorithm RSA -out server.key  # 生成私钥
        
      2. # 生成请求,更改使用openssl.cnf文件路径   
        # -config 指定使用的 .cnf文件路径,-extensions指定使用的扩展
         openssl req -new -nodes -key server.key -out server.csr -days 3650 -config ./openssl.cnf -extensions v3_req    
        
      3. # 生成公钥,和自签证书一致
        openssl x509 -req -days 365 -in server.csr -signkey server.key -out server.crt  
        
      4. # 生成SAN证书
        openssl x509 -req -days 365 -in server.csr -out server.pem -CA ca.crt -CAkey ca.key -CAcreateserial -extfile ./openssl.cnf -extensions v3_req
        
      5. 生成SNA证书的相关参数

        • key: 服务器上的私钥文件,用于对发送给客户端数据的加密,以及对从客户端接收到数据的解密。
        • csr: 证书签名请求文件,用于提交给证书颁发机构(CA)对证书签名。
        • crt: 由证书颁发机构(CA)签名后的证书,或者是开发者自签名的证书,包含证书持有人的信息,持有人的公钥,以及签署者的签名等信息。
        • pem: 是基于Base64编码的证书格式,扩展名包括PEM、CRT和CER

单向认证

单向认证也被叫做客户端认证服务器,在这种认证方式里,仅一方(通常是客户端)对另一方(通常是服务器)的身份进行验证,而服务器无需对客户端的身份进行验证

  1. 客户端发起请求:客户端向服务器发送连接请求。
  2. 服务器发送证书:服务器接收到请求后,将自己的数字证书发送给客户端。
  3. 客户端验证证书:客户端接收到证书后,使用权威证书颁发机构(CA)的公钥验证证书的有效性,包括证书是否过期、是否被吊销等。若证书有效,客户端就认为服务器身份是可信的。
  4. 建立连接:客户端验证通过后,双方建立通信连接
服务端应用证书
package main

import (
	"go_rpc_demo/service"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials"
	"log"
	"net"
)

func main() {
 
	// 读取证书  pem路径和key路径
	creds, cred_err := credentials.NewServerTLSFromFile("cert/server.pem", "cert/server.key")
	if cred_err != nil {
		log.Fatal("证书生成错误", cred_err)
	}

	// 创建grpc服务实例的时候,应用读取到的证书
	rpcServer := grpc.NewServer(grpc.Creds(creds))

	// RegisterUserInfoServiceServer是是由 Protobuf 编译器根据.ptoto定义的服务自动生成的函数
	// 调用RegisterUserInfoServiceServer ,将第三步的接口的实现注册到rpcServer
	service.RegisterUserInfoServiceServer(rpcServer, service.UserService)

	// 在tpc协议的8002端口开启监听,返回一个 net.Listener 类型的对象和可能出现的错误
	listener, err := net.Listen("tcp", ":8002")
	if err != nil {
		log.Fatal("启动监听出错", err)
	}

	// 调用 rpcServer 的 Serve 方法,让 gRPC 服务器开始在 listener 所监听的端口上提供服务。此方法会一直阻塞,直至服务器关闭
	err = rpcServer.Serve(listener)
	if err != nil {
		log.Fatal("启动服务出错")
	}

}

客户端应用证书
import (
	"context"
	"go_rpc_demo/service"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials"
	"log"
)

func main() {

	// 客户端读取证书,参数:证书路径,要覆盖的服务器域名域名(和openssl.cnf里面的dns.x配置的一样)
	creds, creds_err := credentials.NewClientTLSFromFile("cert/server.pem", "*.com")

	if creds_err != nil {
		log.Fatal("证书错误", creds_err)
	}

	// 创建一个连接 ,连接到8002端口的grpc服务器
	// 第二个参数传入读取到的证书
	conn, err := grpc.Dial(":8002", grpc.WithTransportCredentials(creds))

	if err != nil {
		log.Fatal("连接出错", err)
	}

	// 执行完关闭连接

	defer conn.Close()

	// NewUserInfoServiceClient是由 Protobuf 编译器根据服务名字生成的
	// 创建客户端,传入对应的连接
	client := service.NewUserInfoServiceClient(conn)
	// 请求参数
	request := &service.UserInfoRequest{UserId: 1}
	// 通过客户端调用对应的方法,像调用本地方法一样
	infoResponse, err := client.GetUserInfo(context.Background(), request)
	if err != nil {
		log.Fatal("请求失败", err)
	}
	// 请求结果
	log.Println(infoResponse)

}

双向认证

双向认证又称相互认证,通信双方都需要对对方的身份进行验证,即客户端要验证服务器的身份,同时服务器也要验证客户端的身份。

  1. 客户端发起请求:客户端向服务器发送连接请求。
  2. 服务器发送证书:服务器接收到请求后,将自己的数字证书发送给客户端。
  3. 客户端验证服务器证书:客户端使用 CA 的公钥验证服务器证书的有效性。
  4. 客户端发送证书:若服务器证书验证通过,客户端将自己的数字证书发送给服务器。
  5. 服务器验证客户端证书:服务器使用 CA 的公钥验证客户端证书的有效性。
  6. 建立连接:双方证书都验证通过后,建立安全的通信连接
服务端
import (
	"crypto/tls"
	"crypto/x509"
	"go_rpc_demo/service"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials"
	"io/ioutil"
	"log"
	"net"
)

func main() {
	// 从证书相关文件中读取和解析信息,得到证书公钥、秘钥对
	cert, err := tls.LoadX509KeyPair("cert/server.pem", "cert/server.key")

	if err != nil {
		log.Fatal("证书错误", err)
	}

	// 创建一个新的 空的 certPool
	certPool := x509.NewCertPool()

	// 读取ca证书
	ca, err := ioutil.ReadFile("cert/ca.crt")

	if err != nil {
		log.Fatal("ca证书错误", err)
	}

	// 尝试解析传入的PEM证书,如果解析成功会将其添加到pool中
	certPool.AppendCertsFromPEM(ca)

	// 构建基于 TLS 的 TransportCredentials 选项
	creds := credentials.NewTLS(&tls.Config{
		// 设置证书链,允许包含一个或多个
		Certificates: []tls.Certificate{cert},
		// 要求必须校验客户端的证书。可以根据实际情况选用以下参数
		ClientAuth: tls.RequireAndVerifyClientCert,
		// 设置根证书的集合,校验方式使用 ClientAuth 中设定的模式
		ClientCAs: certPool,
	})

	// 创建grpc服务实例
	rpcServer := grpc.NewServer(grpc.Creds(creds))

	// RegisterUserInfoServiceServer是是由 Protobuf 编译器根据.ptoto定义的服务自动生成的函数
	// 调用RegisterUserInfoServiceServer ,将第三步的接口的实现注册到rpcServer
	service.RegisterUserInfoServiceServer(rpcServer, service.UserService)

	// 在tpc协议的8002端口开启监听,返回一个 net.Listener 类型的对象和可能出现的错误
	listener, err := net.Listen("tcp", ":8002")
	if err != nil {
		log.Fatal("启动监听出错", err)
	}

	// 调用 rpcServer 的 Serve 方法,让 gRPC 服务器开始在 listener 所监听的端口上提供服务。此方法会一直阻塞,直至服务器关闭
	err = rpcServer.Serve(listener)
	if err != nil {
		log.Fatal("启动服务出错")
	}

}

客户端

双向认证客户端也需要生成对应的公钥和私钥

func main() {

	// 从证书文件读取,获得证书公钥、秘钥对
	cert, err := tls.LoadX509KeyPair("client/cert/client.pem", "client/cert/client.key")

	if err != nil {
		log.Fatal("从证书文件读取,获得证书公钥、秘钥对 错误", err)
	}
	// 创建一个新的、空的 CertPool

	certPool := x509.NewCertPool()
	ca, err := ioutil.ReadFile("client/cert/ca.crt")
	if err != nil {
		log.Fatal("加载ca出错", err)
	}

	// 尝试解析所传入的 PEM 编码的证书。如果解析成功会将其加到 CertPool 中,便于后面的使用
	if !certPool.AppendCertsFromPEM(ca) {
		log.Fatal("解析ca出错", err)
	}
	// 构建基于 TLS 的 TransportCredentials 选项
	creds := credentials.NewTLS(&tls.Config{
		// 设置证书链,允许包含一个或多个
		Certificates: []tls.Certificate{cert},
		// 要求必须校验客户端的证书。可以根据实际情况选用以下参数
		ServerName: "*.com",
		RootCAs:    certPool,
	})

	// 创建一个连接 ,连接到8002端口的grpc服务器
	// 第二个参数传入读取到的证书
	conn, err := grpc.Dial(":8002", grpc.WithTransportCredentials(creds))

	if err != nil {
		log.Fatal("连接出错", err)
	}

	// 执行完关闭连接

	defer conn.Close()

	// NewUserInfoServiceClient是由 Protobuf 编译器根据服务名字生成的
	// 创建客户端,传入对应的连接
	client := service.NewUserInfoServiceClient(conn)
	// 请求参数
	request := &service.UserInfoRequest{UserId: 1}
	// 通过客户端调用对应的方法,像调用本地方法一样
	infoResponse, err := client.GetUserInfo(context.Background(), request)
	if err != nil {
		log.Fatal("请求失败", err)
	}
	// 请求结果
	log.Println(infoResponse)

}

token 认证

服务端

服务端添加用户名密码的登录验证

func main()  {
  // 拦截器
	var authInterceptor grpc.UnaryServerInterceptor
	authInterceptor = func(
		ctx context.Context,
		req interface{},
		info *grpc.UnaryServerInfo,
		handler grpc.UnaryHandler,
	) (resp interface{}, err error) {
		//拦截请求,验证 Token
		err = Auth(ctx)
		if err != nil {
			return
		}
		// 继续处理请求
		return handler(ctx, req)
	}
	server := grpc.NewServer(grpc.UnaryInterceptor(authInterceptor))
	service.RegisterProdServiceServer(server,service.ProductService)

	listener, err := net.Listen("tcp", ":8002")
	if err != nil {
		log.Fatal("服务监听端口失败", err)
	}
	err = server.Serve(listener)
	if err != nil {
		log.Fatal("服务、启动失败", err)
	}
	fmt.Println("启动成功")
}


func Auth(ctx context.Context) error {
	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
}


客户端
type Authentication struct {
    User     string
    Password string
}


// 客户端需要实现 PerRPCCredentials 接口
func (a *Authentication) GetRequestMetadata(context.Context, ...string) (
    map[string]string, error,
) {
    return map[string]string{"user": a.User, "password": a.Password}, nil
}

// // GetRequestMetadata 方法返回认证需要的必要信息,RequireTransportSecurity 方法表示是否启用安全链接
func (a *Authentication) RequireTransportSecurity() bool {
    return false
}

user := &auth.Authentication{
		User: "admin",
		Password: "admin",
	}
	conn, err := grpc.Dial(":8002", grpc.WithTransportCredentials(insecure.NewCredentials()),grpc.WithPerRPCCredentials(user))
	

posted @ 2025-03-24 18:29  木子七  阅读(79)  评论(0)    收藏  举报