只知道 JWT 的原理,很显然是不够的。

所以实战点的东西来了,当老大让你把 RSA 签名算法整合到 JWT 里面,该怎么处理呢?

什么是 RSA?

RSA 加密算法是一种非对称加密算法,在公开密钥加密和电子商业中被广泛使用。

我们见得最多的是对称加密算法,比如 DES 这类,即加密和解密的 Key 是一样的。

就像现实生活中的钥匙,一把钥匙对应一把锁。

而非对称加密算法则不是这样。

他有两把钥匙,其中只有一把钥匙(私钥)能加密,另一把钥匙(公钥)只能解密,但是不能加密。

所以我们只需要管理好私钥,公钥可以开放出去。

这样有什么好处呢?

接收者不用担心在传输中被别人篡改了呀,同时接受者也能验证数据是否是这公钥对应的私钥签发的。

这么好的算法,我们先要把他用到我们的 JWT 里面去。

JWT 里面配置使用 RSA 进行签名

要让我们的 JWT 支持 RSA 签名,那第一步必须先要生产有 RSA 的公私钥。

生成私钥和公钥

这里我们需要用到官方的 crypto 包里面的库,直接上代码吧:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// 生成证书到本地
func CreateRootCert(prvKeyPath,pubKeyPath string, bits int) error {
	// 生成私钥
	privateKey, err := rsa.GenerateKey(rand.Reader, bits)
	if err!=nil { return err }
	// 解析成CS1二进制
	privateKeyDer := x509.MarshalPKCS1PrivateKey(privateKey)
	privateKeyBlock := pem.Block{
		Type:    "RSA PRIVATE KEY",
		Headers: nil,
		Bytes:   privateKeyDer,
	}
	// 编码到内存
	privateKeyPem := pem.EncodeToMemory(&privateKeyBlock)
	// 写到本地
	err=ioutil.WriteFile(prvKeyPath, privateKeyPem, 0600)
	if err != nil {return err}

	//获取公钥
	publicKey := privateKey.PublicKey
	// 解析
	publicKeyDer, err := x509.MarshalPKIXPublicKey(&publicKey)
	if err!=nil { return err }
	publicKeyBlock := pem.Block{
		Type:    "PUBLIC KEY",
		Headers: nil,
		Bytes:   publicKeyDer,
	}
	publicKeyPem := pem.EncodeToMemory(&publicKeyBlock)
	err = ioutil.WriteFile(pubKeyPath, publicKeyPem, 0600)
	if err!=nil { return err }
	return nil
}

到此也就在本地生成了公私钥。

配置 JWT 签名算法

这里我们使用的 JWT 库是:

github.com/dgrijalva/jwt-go

如果你使用的 Go Mod 管理包,只需要执行:

1
go get github.com/dgrijalva/jwt-go

上一篇我们讲了 JWT 的原理,最关键的就是最后一部分的签名。

这个 JWT 库支持很多种签名算法,这里我们把他配置成 RSA 算法,直接上代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
var (
	verifyKey *rsa.PublicKey
	signKey   *rsa.PrivateKey
)

func InitJWT(privKeyPath, pubKeyPath string)  {
	signBytes, err := ioutil.ReadFile(privKeyPath)
	if err != nil {
		log.Fatal(err)
	}

	signKey, err = jwt.ParseRSAPrivateKeyFromPEM(signBytes)
	if err != nil {
		log.Fatal(err)
	}

	verifyBytes, err := ioutil.ReadFile(pubKeyPath)
	if err != nil {
		log.Fatal(err)
	}

	verifyKey, err = jwt.ParseRSAPublicKeyFromPEM(verifyBytes)
	if err != nil {
		log.Fatal(err)
	}
}

JWT 的使用

生成 JWT 实例

前面部分我们配置好了 JWT ,现在就可以直接使用了。

直接上代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
type JWT struct {
	UserRole   int    `json:"user_role"`
	UserID     int64  `json:"user_id"`
	UserEmail  string `json:"user_email"`
	Expiration int64  `json:"expiration"`
	Token      string `json:"token"`
}
type User struct {
	Role int
	Id 	int64
	UserEmail string
}
func NewJWT(user *User) (JWT,error) {
	method := jwt.GetSigningMethod("RS256")   //[1]
	exp := time.Now().Add(time.Minute * 3600).Unix()
	claims := jwt.MapClaims{
		"UserRole": user.Role,
		"UserID": user.Id,
		"UserEmail": user.UserEmail,
		"exp": exp,
	}
	token := jwt.NewWithClaims(method, claims)
	tokenString,err := token.SignedString(signKey) //[2]
	if err!=nil {
		fmt.Println(err)
	}
	return JWT{
		UserRole:   user.Role,
		UserID:     user.Id,
		UserEmail:  user.UserEmail,
		Expiration: exp,
		Token:      tokenString,
	}, nil
}

关键代码解释:

  • [1] 这个是指定我们的签名算法,他支持 RS256,RS384 等算法,所以这里不是随便写的。

20220124070812_jpjpbz.png

  • [2]这里的 signKey 就是上一段代码里面加载的私钥。

校验 Token

生产 Token 完毕了,剩下的关键步骤就是校验了。

当我们收到前端传来的 Token 怎么校验呢?

直接上代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
func ValidateJWT(t string) (*jwt.Token,error) {
	token,err := jwt.Parse(t, func(token *jwt.Token) (interface{}, error) {
		return verifyKey,nil
	})
	switch err.(type) {
	case nil:
		if !token.Valid {
			return token, fmt.Errorf("Invalid token: %s\n", token.Raw)
		}
		return token, nil
	case *jwt.ValidationError:
		validationErr := err.(*jwt.ValidationError)

		switch validationErr.Errors {
		case jwt.ValidationErrorExpired:
			return token, fmt.Errorf("Expired token: %s\n", token.Raw)
		default:
			return token, fmt.Errorf("Token validation error: %s\n", token.Raw)
		}
	default:
		return token, fmt.Errorf("Unable to parse token: %s\n", token.Raw)
	}
}

这部分代码,我们一般把他放在中间件里面。

写在最后的话

有必要用 JWT 库么?

这个问题其实各有各的看法。

当我们知道了 JWT 的原理后,其实我们是可以根据原理实现一个库,去生成 JWT Token。

但是如果你不做特殊处理,比如在传统的 JWT 上加入一些其他数据,用比较稳定的三方库还是比较省事的。