只会讲 JWT 的原理,很显然是不够的。
所以实战点的东西来了,当面试官让你把 RSA 签名算法整合到 JWT 里面,该怎么处理呢?
RSA加密算法是一种非对称加密算法,在公开密钥加密和电子商业中被广泛使用。
我们见得最多的是对称加密算法,比如 DES 这类,即加密和解密的 Key 是一样的。
就像现实生活中的钥匙,一把钥匙对应一把锁。
而非对称加密算法则不是这样。
他有两把钥匙,其中只有一把钥匙(私钥)能加密,另一把钥匙(公钥)只能解密,但是不能加密。
所以我们只需要管理好私钥,公钥可以开放出去。
接收者不用担心在传输中被别人篡改了呀,同时接受者也能验证数据是否是这公钥对应的私钥签发的。
要让我们的 JWT 支持 RSA 签名,那第一步必须先要生产有 RSA 的公私钥。
这里我们需要用到官方的 crypto 包里面的库,直接上代码吧:
// 生成证书到本地
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 库是:
github.com/dgrijalva/jwt-go
如果你使用的 Go Mod 管理包,只需要执行:
go get github.com/dgrijalva/jwt-go
上一篇我们讲了 JWT 的原理,最关键的就是最后一部分的签名。
这个 JWT 库支持很多种签名算法,这里我们把他配置成 RSA 算法,直接上代码:
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 ,现在就可以直接使用了。
直接上代码:
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
}
关键代码解释:
生产 Token 完毕了,剩下的关键步骤就是校验了。
当我们收到前端传来的 Token 怎么校验呢?
直接上代码:
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 Token。
但是如果你不做特殊处理,比如在传统的 JWT 上加入一些其他数据,用比较稳定的三方库还是比较省事的。