探索jwt-go jwt令牌生成与校验库

87 阅读2分钟

注:本文为学习时记录的笔记,内容尚浅,后续有时间可能会完善

jwt-go框架学习记录

1.安装与配置

安装jwt-go库(5.0版本)

go get -u github.com/golang-jwt/jwt/v5

2.基本使用

1.生成jwt令牌,NewWithClaims

自定义claim,可在其中加入各种信息

// CustomClaims 自定义 Claims
type CustomClaims struct {
    jwt.RegisteredClaims //导入官方默认的claims
    //也可在这定义一些其他信息,如用户名称,用户id等
}

使用自定义的claim生成jwt令牌

//生成基本jwt令牌
token = jwt.NewWithClaims(
    jwt.SigningMethodHS256, //签名方式
    CustomClaims{
       RegisteredClaims: jwt.RegisteredClaims{
          //过期时间
          ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Duration(global.App.Config.Jwt.JwtTtl) * time.*Second*)),
          //这里我们把用户id存到默认claim的ID中
          ID:        user.GetUid(),
          //jwt令牌发布者名称(防止不同应用发布的令牌混用)
          Issuer:    GuardName,
          //生效时间
          NotBefore: jwt.NewNumericDate(time.Now().Add(-1 * time.*Second*)),
       },
    },
)

//使用自定义的密钥转换为字符串,解析时需要用到该密钥,其中tokenStr就是返回给客户端的令牌
tokenStr, err := token.SignedString([]byte(global.App.Config.Jwt.Secret))

2.解析jwt令牌,ParseWithClaims

解析字符串令牌

// Token 解析校验
token, err := jwt.ParseWithClaims(tokenStr, &service.CustomClaims{}, func(token *jwt.Token) (interface{}, error) {
    // 返回加密时的密钥
    return []byte(global.App.Config.Jwt.Secret), nil
})

获取claim对象

claims := token.Claims.(*service.CustomClaims)
//然后就可以获取到claim中保存的对应信息啦

3.jwt令牌黑名单策略

由于jwt令牌在是存储在客户端,服务端只负责解析,故服务端无法主动注销jwt令牌,这里我们可以使用黑名单策略来解决注销问题,将列入黑名单的jwt令牌存入redis,每次校验jwt令牌时如果存在黑名单内,则校验不通过。

我们还添加了黑名单宽限时间,防止用户jwt令牌加入黑名单后,短时间内又使用该令牌发起请求导致1请求失败的问题

简单实现
// JoinBlackList token 加入黑名单
func (jwtService *jwtService) JoinBlackList(token *jwt.Token) (err error) {
	nowUnix := time.Now().Unix()
	timer := time.Duration(token.Claims.(*CustomClaims).ExpiresAt.Unix()-nowUnix) * time.Second
	// 将 token 剩余时间设置为缓存有效期,并将当前时间作为缓存 value 值
	err = global.App.Redis.SetNX(context.Background(), jwtService.getBlackListKey(token.Raw), nowUnix, timer).Err()
	return
}

// IsInBlacklist token 是否在黑名单中
func (jwtService *jwtService) IsInBlacklist(tokenStr string) bool {
	joinUnixStr, err := global.App.Redis.Get(context.Background(), jwtService.getBlackListKey(tokenStr)).Result()
	joinUnix, err := strconv.ParseInt(joinUnixStr, 10, 64)
	if joinUnixStr == "" || err != nil {
		return false
	}
	// JwtBlacklistGracePeriod 为黑名单宽限时间,避免并发请求失效
	if time.Now().Unix()-joinUnix < global.App.Config.Jwt.JwtBlacklistGracePeriod {
		return false
	}
	return true
}

//每次在校验token时就需要调用IsInBlacklist方法判断是否在黑名单内,如果在,则该令牌已失效

4.jwt令牌续签问题

在令牌有效期内进行有效操作刷新令牌过期时间,对用户操作更加友好

简单实现
// token 续签
if claims.ExpiresAt.Time.Unix()-time.Now().Unix() < global.App.Config.Jwt.RefreshGracePeriod {
    // 使用了锁机制来确保只有一个续签操作在运行
    lock := global.Lock("refresh_token_lock", global.App.Config.Jwt.JwtBlacklistGracePeriod)
    if lock.Get() {
       err, user := service.JwtService.GetUserInfo(GuardName, claims.ID)
       if err != nil {
          global.App.Log.Error(err.Error())
          lock.Release()
       } else {
          tokenData, _, _ := service.JwtService.CreateToken(GuardName, user)
          c.Header("new-token", tokenData.AccessToken)
          c.Header("new-expires-in", strconv.Itoa(tokenData.ExpiresIn))
          _ = service.JwtService.JoinBlackList(token)
       }
    }
}