Golang实现结构体签名验签&grpc/gin中间件

设计目标

  • 支持RSA2加签验签(解析密钥方式:PKCS1 数字签名算法:SHA256)
  • 支持grpc拦截器加签验签,对业务代码无侵入
  • 支持gin框架中间件验签,支持客户端发送http请求设置加签信息到Header中
  • 支持服务端对接多语言客户端(签名原文为:有序JSON(ASCII码序排序Key,忽略结构体/Map中的0值和空值),RSA2加签(PKCS1+SHA256))

不足

  • HTTP请求时,application/json类型POST请求签名原文区分类型,其他类型框架无法获取值的类型,只能转化为map[string]string后进行签名
  • 为了通用性,没有在通用参数中加入timestamp, nonce等参数,不能防重放攻击

签名

签名接口

加签接口
func Sign(content, privateKey string)(sign string, err error)
 
验签接口
func Verify(content, sign, pubKey string) (err error)
 
结构体、Map等转换为JSON字符串接口
// InterfaceToSortedJSONStr 结构体、Map 转 待加签的排序的json字符串
// json按照字典序排序,值为空或者为0的忽略,不序列化为json的忽略(tag中`json:"-"`),不参与加签的字段忽略(tag中`sign:"-"`)
func InterfaceToSortedJSONStr(i interface{
   }) (str string, err error)

代码

package signature

import (
	"crypto"
	"crypto/rand"
	"crypto/rsa"
	"crypto/sha256"
	"crypto/x509"
	"encoding/base64"
	"encoding/json"
	"encoding/pem"
	"errors"
	"fmt"
	"reflect"
	"strings"
)

// Sign 加签函数
type Sign func(content, privateKey string) (sign string, err error)

// Verify 验签函数
type Verify func(content, sign, pubKey string) (err error)

// NewSigner 初始化Signer,默认RSA2签名
func NewSigner(s Sign) *Signer {
   
	if s == nil {
   
		s = rsa2Sign
	}
	return &Signer{
   
		S: s,
	}
}

var ErrPemDecode = errors.New("pem.Decode failed") // pem解析失败

func rsa2Sign(content, privateKey string) (sign string, err error) {
   
	// 1、将密钥解析成密钥实例
	block, _ := pem.Decode([]byte(privateKey))
	if block == nil {
   
		err = ErrPemDecode
		return
	}
	key, err := x509.ParsePKCS1PrivateKey(block.Bytes)
	if err != nil {
   
		return
	}

	// 2、生成签名
	hash := sha256.New()
	_, err = hash.Write([]byte(content))
	if err != nil {
   
		return
	}
	signature, err := rsa.SignPKCS1v15(rand.Reader, key, crypto.SHA256, hash.Sum(nil))
	if err != nil {
   
		return
	}

	// 3、签名base64编码
	sign = base64.StdEncoding.EncodeToString(signature)
	return
}

// Signer
type Signer struct {
   
	S Sign
}

// Sign 签名
func (s *Signer) Sign(content, privateKey string) (sign string, err error) {
   
	return s.S(content, privateKey)
}

// NewVerifier 初始化Verifier,默认RSA2验签
func NewVerifier(v Verify) *Verifier {
   
	if v == nil {
   
		v = rsa2Verify
	}
	return &Verifier{
   
		V: v,
	}
}

func rsa2Verify(content, sign, pubKey string) (err error) {
   
	// 1、签名base64解码
	signature, err := base64.StdEncoding.DecodeString(sign)
	if err != nil {
   
		return
	}

	// 2、密钥解析成公钥实例
	block, _ := pem.Decode([]byte(pubKey))
	if block == nil {
   
		err = ErrPemDecode
		return
	}
	key, err := x509.ParsePKIXPublicKey(block.Bytes)
	if err != nil {
   
		return
	}
	hash := sha256.New()
	_, err = hash.Write([]byte(content))
	if err != nil {
   
		return
	}

	// 3、验证签名
	pub := key.(*rsa.PublicKey)
	err = rsa.VerifyPKCS1v15(pub, crypto.SHA256, hash.Sum(nil), signature)
	return
}

// Verifier
type Verifier struct {
   
	V Verify
}

// Verify 验签
func (s *Verifier) Verify(content, sign, pubKey string) (err error) {
   
	return s.V(content, sign, pubKey)
}

const InvalidType = "invalid type=%v"

// InterfaceToSortedJSONStr 结构体、Map 转 待加签的排序的json字符串
// json按照字典序排序,值为空或者为0的忽略,不序列化为json的忽略(tag中`json:"-"`),不参与加签的字段忽略(tag中`sign:"-"`)
func InterfaceToSortedJSONStr(i interface{
   }) (str string, err error) {
   
	// 1、数据提取,基础类型提取值,结构体、Map等转换为有序Map
	if i == nil {
   
		err = fmt.Errorf(InvalidType, i)
		return
	}
	v, err := interfaceValExtract(i)
	if err != nil {
   
		return
	}

	// 2、字符串类型直接返回
	if vStr, ok := v.(string); ok {
   
		str = vStr
		return
	}

	// 3、ToSignMap类型 检查len
	if vMap, ok := v.(ToSignMap); ok && len(vMap) == 0 {
   
		str = ""
		return
	}

	// 4、返回json字符串
	return MarshalToStr(v)
}

// MarshalToStr 转换为json not escape html 解决不转义< >的问题
func MarshalToStr(i interface{
   }) (str string, err error) {
   
	buffer := &bytes.Buffer{
   }
	encoder := json.NewEncoder
首先,需要安装gingrpc的Go语言库。可以使用以下命令: ``` go get -u github.com/gin-gonic/gin go get -u google.golang.org/grpc ``` 接着,在代码中导入相应的库: ```go import ( "log" "github.com/gin-gonic/gin" "google.golang.org/grpc" ) ``` 然后,创建一个gin的路由实例,并在路由中使用grpc客户端调用相应的服务: ```go func main() { // 创建 gin 实例 r := gin.Default() // 连接到 grpc 服务 conn, err := grpc.Dial("localhost:50051", grpc.WithInsecure()) if err != nil { log.Fatalf("failed to connect: %v", err) } defer conn.Close() // 创建 grpc 客户端 client := pb.NewUserServiceClient(conn) // 定义 gin 路由 r.GET("/users/:id", func(c *gin.Context) { // 从 URL 参数中获取用户 ID id := c.Param("id") // 调用 grpc 服务获取用户信息 user, err := client.GetUser(context.Background(), &pb.GetUserRequest{Id: id}) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } // 返回用户信息 c.JSON(http.StatusOK, gin.H{"user": user}) }) // 启动 gin 服务 if err := r.Run(":8080"); err != nil { log.Fatalf("failed to start server: %v", err) } } ``` 以上代码中,我们创建了一个gin实例,并在路由中使用grpc客户端调用了一个名为`GetUser`的服务,该服务接收一个用户ID作为参数,并返回一个包含用户信息的结构体。我们将调用结果转换为JSON格式,并在HTTP响应中返回给客户端。 需要注意的是,以上代码中的`pb`是根据你的grpc服务定义文件自动生成的代码,你需要将其替换为你的实际代码中的包名。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值