当前位置: 首页 > 工具软件 > micro-auth > 使用案例 >

[从RPC到Go-Micro 伍]给gRPC加安全认证

祝宾白
2023-12-01

授权认证

gRPC中默认支持两种授权方式,分别是:SSL/TLS认证方式、基于Token的认证方式。

SSL/TLS认证方式

SSL全称是Secure Sockets Layer,又被称之为安全套接字层,是一种标准安全协议,用于在通信过程中建立客户端与服务器之间的加密链接。
TLS的全称是Transport Layer Security,TLS是SSL的升级版。在使用的过程中,往往习惯于将SSL和TLS组合在一起写作SSL/TLS。
简而言之,SSL/TLS是一种用于网络通信中加密的安全协议。

SSL/TLS工作原理

使用SSL/TLS协议对通信连接进行安全加密,是通过非对称加密的方式来实现的。所谓非对称加密方式又称之为公钥加密,密钥对由公钥和私钥两种密钥组成。私钥和公钥成对存在,先生成私钥,通过私钥生成对应的公钥。公钥可以公开,私钥进行妥善保存。

在加密过程中:客户端想要向服务器发起链接,首先会先向服务端请求要加密的公钥。获取到公钥后客户端使用公钥将信息进行加密,服务端接收到加密信息,使用私钥对信息进行解密并进行其他后续处理,完成整个信道加密并实现数据传输的过程。

制作证书

可以自己在本机计算机上安装openssl,并生成相应的证书。

openssl ecparam -genkey -name secp384r1 -out server.key
openssl req -new -x509 -sha256 -key server.key -out server.pem -days 3650

openssl在windows中安装参考链接: https://www.cnblogs.com/dshvv/p/12271280.html

服务接口定义

syntax = "proto3";
package message;


message RequestArgs {
    int32 args1 = 1;
    int32 args2 = 2;
}

message Response {
    int32 code = 1;
    string message = 2;
}

//服务
service MathService {
    //服务
    rpc AddMethod (RequestArgs) returns (Response) {
    };
}

实现服务端

package main

import (
	"GoCode/example/gRpcSSLDemo/message"
	"context"
	"fmt"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials"
	"google.golang.org/grpc/grpclog"
	"net"
)

type MathServiceImp struct {
}

func (m *MathServiceImp) AddMethod(ctx context.Context, request *message.RequestArgs) (*message.Response, error) {
	result := request.Args1 + request.Args2
	fmt.Println(result)
	var response = &message.Response{}
	response.Code = 1
	response.Message = fmt.Sprintf("计算结果是: %d", result)
	return response, nil
}

func main() {
	// TLS认证
	creds, err := credentials.NewServerTLSFromFile("./example/gRpcSSLDemo/ssl证书/server.pem", "./example/gRpcSSLDemo/ssl证书/server.key")
	if err != nil {
		grpclog.Fatal("加载证书文件夹失败", err)
	}
	// 实例化grpc server 开启TLS认证
	server := grpc.NewServer(grpc.Creds(creds))

	message.RegisterMathServiceServer(server, new(MathServiceImp))

	lis, err := net.Listen("tcp", ":9000")
	if err != nil {
		panic(err)
	}

	_ = server.Serve(lis)
}

实现客户端

func main() {
	// TLS连接
	creds, err := credentials.NewClientTLSFromFile("./example/gRpcSSLDemo/ssl证书/server.pem", "cd") // 这里的第二个参数是在生成key时的common name

	if err != nil {
		panic(err.Error())
	}

	//1、Dail连接
	conn, err := grpc.Dial("localhost:9000", grpc.WithTransportCredentials(creds))
	if err != nil {
		panic(err.Error())
	}
	defer conn.Close()

	serviceClient := message.NewMathServiceClient(conn)

	addArgs := message.RequestArgs{Args1: 3, Args2: 5}

	response, err := serviceClient.AddMethod(context.Background(), &addArgs)
	if err != nil {
		grpclog.Fatal(err.Error())
		return
	}

	fmt.Println(response.GetCode(), response.GetMessage())

}

基于Token的认证方式

介绍

在web应用的开发过程中,我们往往还会使用另外一种认证方式进行身份验证,那就是:Token认证。基于Token的身份验证是无状态,不需要将用户信息服务存在服务器或者session中。

认证过程

基于Token认证的身份验证主要过程是:客户端在发送请求前,首先向服务器发起请求,服务器返回一个生成的token给客户端。客户端将token保存下来,用于后续每次请求时,携带着token参数。服务端在进行处理请求之前,会首先对token进行验证,只有token验证成功了,才会处理并返回相关的数据。

gRPC的自定义Token认证

在gRPC中,允许开发者自定义自己的认证规则,通过

grpc.WithPerRPCCredentials()

设置自定义的认证规则。WithPerRPCCredentials方法接收一个PerRPCCredentials类型的参数,进一步查看可以知道PerRPCCredentials是一个接口,定义如下:

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

因此,开发者可以实现以上接口,来定义自己的token信息。

在这里,我们自定义的token认证结构体如下:

//token认证
type TokenAuthentication struct {
	AppKey    string
	AppSecret string
}

func (ta *TokenAuthentication) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) {
	return map[string]string{
		"appid":    ta.AppKey,
		"appkey": ta.AppSecret,
	}, nil
}

//是否基于TLS认证进行安全传输
func (a *TokenAuthentication) RequireTransportSecurity() bool {
	return true
}

需要注意的是,RequestMetaData方法中的appid和appkey字段均需要保持小写,不能大写。RequireTransportSecurity方法用于设置是否基于tls认证进行安全传输。

在客户端进行连接时,我们将自定义的token认证信息作为参数进行传入。

auth := TokenAuthentication{
		AppKey:    "chensy",
		AppSecret: "123",
}
conn, err := grpc.Dial("localhost:9000", grpc.WithTransportCredentials(creds), grpc.WithPerRPCCredentials(&auth))
if err != nil {
	panic(err.Error())
}

服务端

type MathServiceImp struct {
}

func (m *MathServiceImp) AddMethod(ctx context.Context, request *message.RequestArgs) (*message.Response, error) {
	//通过metadata
	md, exist := metadata.FromIncomingContext(ctx)
	if !exist {
		return nil, status.Errorf(codes.Unauthenticated, "无Token认证信息")
	}

	var appKey string
	var appSecret string

	if key, ok := md["appid"]; ok {
		appKey = key[0]
	}

	if secret, ok := md["appkey"]; ok {
		appSecret = secret[0]
	}

	if appKey != "chensy" || appSecret != "123" {
		return nil, status.Errorf(codes.Unauthenticated, "Token 不合法")
	}

	result := request.Args1 + request.Args2
	fmt.Println(result)
	var response = &message.Response{}
	response.Code = 1
	response.Message = fmt.Sprintf("计算结果是: %d", result)
	return response, nil
}


func main() {

	// TLS认证
	creds, err := credentials.NewServerTLSFromFile("./example/ssl证书/server.pem", "./example/ssl证书/server.key")
	if err != nil {
		grpclog.Fatal("加载证书文件夹失败", err)
	}
	// 实例化grpc server 开启TLS认证
	server := grpc.NewServer(grpc.Creds(creds))

	message.RegisterMathServiceServer(server, new(MathServiceImp))

	lis, err := net.Listen("tcp", ":9000")
	if err != nil {
		panic(err)
	}

	_ = server.Serve(lis)
}

客户端


//token认证
type TokenAuthentication struct {
	AppKey    string
	AppSecret string
}

func (ta *TokenAuthentication) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) {
	return map[string]string{
		"appid":    ta.AppKey,
		"appkey": ta.AppSecret,
	}, nil
}

//是否基于TLS认证进行安全传输
func (ta *TokenAuthentication) RequireTransportSecurity() bool {
	return true
}


func main() {
	// TLS连接
	creds, err := credentials.NewClientTLSFromFile("./example/ssl证书/server.pem", "cd")

	if err != nil {
		panic(err.Error())
	}

	token := TokenAuthentication{
		AppKey: "chensy",
		AppSecret: "1234",
	}

	//1、Dail连接
	conn, err := grpc.Dial("localhost:9000", grpc.WithTransportCredentials(creds), grpc.WithPerRPCCredentials(&token))
	if err != nil {
		panic(err.Error())
	}
	defer conn.Close()

	serviceClient := message.NewMathServiceClient(conn)

	addArgs := message.RequestArgs{Args1: 3, Args2: 5}

	response, err := serviceClient.AddMethod(context.Background(), &addArgs)
	if err != nil {
		grpclog.Fatal(err.Error())
		return
	}

	fmt.Println(response.GetCode(), response.GetMessage())
}

参考链接

Golang - 100天从新手到大师

源码地址

源码地址

 类似资料: