RSA算法 token生成与解析

本文演示两种方式,一种是把密钥文件放在配置文件中,一种是把密钥文件本身放入项目或者容器中。

下面两种的区别在于私钥公钥的初始化, init方法,需要哪种取哪种。

一、通过文件读取

首先是密钥文件(privateKey),放在如config目录下。
-----BEGIN RSA PRIVATE KEY-----
MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAMu4IDG1XU6a7bXo
4V1jSnbKk9Eum2WguAyq+maCRcP9JoHlE/HmhPOjl91aN5gDHw3pgB7QMMkPkuyY
0aG9UiIo7PbBgjXsNBErprKa8G7GKhDN4B3m8jxEi1NLtCk2H8AEf8H/deGFXCde
fjQx0NcTbJ8STfbsqrLhOq2xzAgMBAAECgYEAg1kZMNOd8IOFxqb7P2o4ZbUh
b1rciL8CS/CleBiAgOgkvtWDcZFOoYQV83sqoxFIIYEuwS88dTZcZb32U5EsdYEx
JvJwAAYnzpch/YAz0llvXSHzZwNfGGvs4qt0d74bFpPfveli82wSKMlykeajP2Ro
RQpOniTYOWrJ01UHdUECQQDt1KTj/Xs5BNmEZAkJVmGekQROADk+ztceAe9UMj/J
s5xECdXVwuFh2Rm62MMQNNoW2Pjz4Y5NqhjRu0MMZnlTAkEA20hZsgA78aqTO7s+
+y/CLgP3Cd7uG/5RkcmjBWq2eXkt6wmazZl0BMYb7vshblnMjFXJwuOmfBJl7rTr
1fg8YQJAEo4Jg0QObgdj1QFc9x6HJTDZLiC0VqMag1vRSTdWZK0fnutJhJDctp6S
dFJe/Y+yCCBLY/OP/50qrIo4k+oWwwJAIn8hTTVoOL6C5xSv9cgvnhmVlYHyp4i8
wFieQs3k4vtDVARwzANmExIvdssfGUMbQMCGOxihKkeirYjcyQ6CQQJAbsbpzCjD
wd9JCogmTu/xYqtL898ek7LeNkhgIY2KhYtlptxlHfzgLBUgiSTNTcD1YWtSSp6u
A5ImxrryDYPmfg==
-----END RSA PRIVATE KEY-----
处理私钥,生成token与解析
package main

import (
	"crypto/rsa"
	"crypto/x509"
	"encoding/base64"
	"errors"
	"github.com/golang-jwt/jwt/v4"
	"io/ioutil"
	"log"
	"time"
)

var (
	publicKey  *rsa.PublicKey
	privateKey *rsa.PrivateKey
)

func main() {
	token, _ := createToken(privateKey)
	println("-------------")
	println(token)
	println("-------------")
	a1, _ := getSubFromToken(token)
	println(a1)
}


func init() {
	publicKeyByte, err := ioutil.ReadFile("公钥的路径/public.key")
	if err != nil {
		log.Println(err.Error())
	}
	publicKey, err = jwt.ParseRSAPublicKeyFromPEM(publicKeyByte)
	privateKeyByte, err := ioutil.ReadFile("私钥的路径/private.key")
	if err != nil {
		log.Println(err.Error())
	}
	privateKey, _ = jwt.ParseRSAPrivateKeyFromPEM(privateKeyByte)
}

// createToken 生成一个RS256验证的Token
// Token里面包括的值,可以自己根据情况添加,
func createToken(privateKey *rsa.PrivateKey) (tokenStr string, err error) {

	expireTime := time.Now().Add(7 * 24 * time.Hour)

	var audi = jwt.ClaimStrings{
		"zwmgc",
	}
	var expir = jwt.NewNumericDate(expireTime)

	claim := jwt.RegisteredClaims{
		Audience:  audi,
		ExpiresAt: expir, //过期时间
		//IssuedAt:  time.Now().Unix(),
		Issuer:  "my-sdk",                              // 签名颁发者
		Subject: "23258bd202e451a988234c2145d754a", //签名主题
	}

	// jwt.SigningMethodHS256
	token := jwt.NewWithClaims(jwt.SigningMethodRS256, claim)
	tokenStr, _ = token.SignedString(privateKey)
	return
}

// getSubFromToken 获取Token的主题(也可以更改获取其他值)
// 参数tokenStr指的是 从客户端传来的待验证Token
// 验证Token过程中,如果Token生成过程中,指定了iat与exp参数值,将会自动根据时间戳进行时间验证
func getSubFromToken(tokenStr string) (sub string, err error) {
	// 基于公钥验证Token合法性
	token, err := jwt.Parse(tokenStr, func(token *jwt.Token) (interface{}, error) {
		// 基于JWT的第一部分中的alg字段值进行一次验证
		if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok {
			return nil, errors.New("验证Token的加密类型错误")
		}
		return publicKey, nil
	})
	if err != nil {
		return
	}
	if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
		return claims["sub"].(string), nil
	}
	return "", errors.New("token无效或者无对应值")
}

这里使用到jwt.RegisteredClaims这个包来进行token生成,在最新的jwt包中,官方加了删除线,表示这个方法不建议再使用了。 这时候如果选择使用jwt.StandardClaims会发现一个问题,就是内部的audience字段从一个string变成了一个数组。那么如何兼容原来的string呢?可以使用下面这个

token := jwt.NewWithClaims(jwt.SigningMethodRS256, jwt.MapClaims{
		"aud": "your audience",
		"exp": time.Now().Add(time.Hour * 24).Unix(), // Token过期时间,目前是24小时
		"iss": "my-sdk",                           // 颁发者
		"sub": credential.Subject,                    // 主题
	})

二、 通过配置文件或者字符串

package main

import (
	"crypto/rsa"
	"crypto/x509"
	"encoding/base64"
	"errors"
	"github.com/golang-jwt/jwt/v4"
	"io/ioutil"
	"log"
	"time"
)

var (
	publicKey  *rsa.PublicKey
	privateKey *rsa.PrivateKey
)

func main() {
	token, _ := createToken(privateKey)
	println("-------------")
	println(token)
	println("-------------")
	a1, _ := getSubFromToken(token)
	println(a1)
}

func init() {
	// 初始化 public Key
	var publicKeyStr = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDLuCAxtV1Omu216OFdY0p2ypPR\n" +
		"LptloLgMqvpmgkXD/SaB5RPx5oTzo5fdWjeYAx8N6YAe0DDJD5LsmNGhvVIiKOz2\n" +
		"wYI17DQRK6aymvBuxioQzeAd5vI8RBH/B/3XhhVwnXn40MdDQxA3E\n" +
		"2yfEk327Kqy4TqtscwIDAQAB"
	publicKeyBinary := make([]byte, base64.StdEncoding.DecodedLen(len(publicKeyStr)))
	publicKeyLen, _ := base64.StdEncoding.Decode(publicKeyBinary, []byte(publicKeyStr))
	publicKeyBinary = publicKeyBinary[:publicKeyLen]
	pubInterface, _ := x509.ParsePKIXPublicKey(publicKeyBinary)
	publicKey = pubInterface.(*rsa.PublicKey)

	// 初始化 private Key
	var privateKeyStr = "MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAMu4IDG1XU6a7bXo\n" +
		"4V1jSnbKk9Eum2WguAyq+maCRcP9JoHlE/HmhPOjl91aN5gDHw3pgB7QMMkPkuyY\n" +
		"0aG9UiIo7PbBgjXsNBErprKa8G7GKhDN4B3m8jxEi1NLtCk2H8AEf8H/deGFXCde\n" +
		"fjQx0NDEDcTbJ8STfbsqrLhOq2YEAg1kZMNOd8IOFxqb7P2o4ZbUh\n" +
		"b1rciL8CS/CleBiAgOgkvtWDcZFOoYQV83sqoxFIIYEuwS88dTZcZb32U5EsdYEx\n" +
		"JvJwAAYnzpch/YAz0llvXSHzZwNfGGvs4qt0d74bFpPfveli82wSKMlykeajP2Ro\n" +
		"RQpOniTYOWrJ01UHdUECQQDt1KTj/Xs5BNmEZAkJVmGekQROADk+ztceAe9UMj/J\n" +
		"s5xECdXVwuFh2Rm62MMQNNoW2Pjz4Y5NqhjRu0MMZnlTAkEA20hZsgA78aqTO7s+\n" +
		"+y/CLgP3Cd7uG/5RkcmjBWq2eXkt6wmazZl0BMYb7vshblnMjFXJwuOmfBJl7rTr\n" +
		"1fg8YQJAEo4Jg0QObgdj1QFc9x6HJTDZLiC0VqMag1vRSTdWZK0fnutJhJDctp6S\n" +
		"dFJe/Y+yCCBLY/OP/50qrIo4k+oWwwJAIn8hTTVoOL6C5xSv9cgvnhmVlYHyp4i8\n" +
		"wFieQs3k4vtDVARwzANmExIvdssfGUMbQMCGOxihKkeirYjcyQ6CQQJAbsbpzCjD\n" +
		"wd9JCogmTu/xYqtL898ek7LeNkhgIY2KhYtlptxlHfzgLBUgiSTNTcD1YWtSSp6u\n" +
		"A5ImxrryDYPmfg=="
	privateKeyBinary := make([]byte, base64.StdEncoding.DecodedLen(len(privateKeyStr)))
	privateKeyLen, _ := base64.StdEncoding.Decode(privateKeyBinary, []byte(privateKeyStr))

	privateKeyBinary = privateKeyBinary[:privateKeyLen]
	privateInterface, _ := x509.ParsePKCS8PrivateKey(privateKeyBinary)
	privateKey = privateInterface.(*rsa.PrivateKey)
}


// createToken 生成一个RS256验证的Token
// Token里面包括的值,可以自己根据情况添加,
func createToken(privateKey *rsa.PrivateKey) (tokenStr string, err error) {

	expireTime := time.Now().Add(7 * 24 * time.Hour)

	var audi = jwt.ClaimStrings{
		"zwmgc",
	}
	var expir = jwt.NewNumericDate(expireTime)

	claim := jwt.RegisteredClaims{
		Audience:  audi,
		ExpiresAt: expir, //过期时间
		//IssuedAt:  time.Now().Unix(),
		Issuer:  "my-sdk",                              // 签名颁发者
		Subject: "23258bd202e451a988234c2145d754a", //签名主题
	}

	// jwt.SigningMethodHS256
	token := jwt.NewWithClaims(jwt.SigningMethodRS256, claim)
	tokenStr, _ = token.SignedString(privateKey)
	return
}

// getSubFromToken 获取Token的主题(也可以更改获取其他值)
// 参数tokenStr指的是 从客户端传来的待验证Token
// 验证Token过程中,如果Token生成过程中,指定了iat与exp参数值,将会自动根据时间戳进行时间验证
func getSubFromToken(tokenStr string) (sub string, err error) {
	// 基于公钥验证Token合法性
	token, err := jwt.Parse(tokenStr, func(token *jwt.Token) (interface{}, error) {
		// 基于JWT的第一部分中的alg字段值进行一次验证
		if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok {
			return nil, errors.New("验证Token的加密类型错误")
		}
		return publicKey, nil
	})
	if err != nil {
		return
	}
	if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
		return claims["sub"].(string), nil
	}
	return "", errors.New("token无效或者无对应值")
}