背景:   在开发设计中涉及到JWT的校验问题,但是发现一个漏洞同时也是JWT设计的理念,那就是后端不进行相关的Token存储,后端只进行相关的Token验证,同时还有就是token的生成和刷新,那么问题来了,那就是后端校验不会知道是否是最新的token

也就是说,可能已经刷新过了token,但是发现旧的token还没过期,这个时候还可以进行使用,在我看来还是有安全风险的,我想要的设计是用户退出登录或者刷新Token后,旧的token要立即失效,这样才够安全,不然一旦被黑客拿到旧的token,那么它就可以利用旧的token一直刷新使用,

后果很严重。

 

设计的理念:  添加Redis作为黑名单进行绑定,旧的Token,设计理念借鉴了下面博客:


下面是我设计的具体的流程图:(不过我没有考虑旧的token过期时间问题,所以也可以进行对存储在Redis中的数据进行相关的时间设定,也就是旧的token过期时间到了,redis中的数据自动清除,这样也可以对Redis进行相关的维护,不然时间越长Redis的压力越大。)

Java token 签名过期 jwt token过期_jwt

具体的实现:(login登录就不做处理了,因为就是正常的获取Token的业务,很简单,如果没有基础可以参考我之前的博客)

      主函数注册API接口并启动服务器

func main() {
	r := gin.Default()
	r.GET("/login", login)
	r.GET("/refresh", refresh)
	r.GET("/sayHello", sayHello)
	r.Run(":9090")

	//测试URL实例
	//http://localhost:9090/login?username=dong&password=123456
	//http://localhost:9090/verify?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1NjA1MTIyMTAsImlhdCI6MTU2MDUwODYxMCwidXNlcl9pZCI6MSwicGFzc3dvcmQiOiIxMjM0NTYiLCJ1c2VybmFtZSI6ImRvbmciLCJmdWxsX25hbWUiOiJkb25nIiwicGVybWlzc2lvbnMiOltdfQ.Esh1Zge0vO1BAW1GeR5wurWP3H1jUIaMf3tcSaUwkzA
	//http://localhost:9090/refresh?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1NjA1MTIyNDMsImlhdCI6MTU2MDUwODYxMCwidXNlcl9pZCI6MSwicGFzc3dvcmQiOiIxMjM0NTYiLCJ1c2VybmFtZSI6ImRvbmciLCJmdWxsX25hbWUiOiJkb25nIiwicGVybWlzc2lvbnMiOltdfQ.Xkb_J8MWXkwGUcBF9bpp2Ccxp8nFPtRzFzOBeboHmg0
}

       以刷新token,让旧Token失效的方式(退出登录的方式相同,这里面对redis存储的数据进行了时间的处理,也就是会根据过期时间将数据彻底清空掉)

/**
 * @Description: 功能描述(刷新JWT的token)
 * @Date : 2020/11/12
 */
func refresh(c *gin.Context) {
	strToken := c.Query("token")
	claims, err := verifyAction(strToken)
	if err != nil {
		c.String(http.StatusNotFound, err.Error())
		return
	}
	// 获取失效时间,填充到Redis中。
	a := int(claims.ExpiresAt - time.Now().Unix())  //剩余过期时间
	utils.Set(strToken, "1", a)  //存在Redis中的数据,对应的过期时间为:a表示秒
	claims.ExpiresAt = time.Now().Unix() + (claims.ExpiresAt - claims.IssuedAt) // 失效的时间
	signedToken, err := getToken(claims)
	if err != nil {
		c.String(http.StatusNotFound, err.Error())
		return
	}
	c.String(http.StatusOK, signedToken)
}

     具体的业务实现:(会进行判断Redis中的是否存在该数据,如果不存在则表明没有加入黑名单,否则就是加入了黑名单)

/**
 * @Description: 功能描述(业务测试,用于真正的开发业务验证是否用户合法,然后执行Hello业务)
 * @Date : 2020/11/12
 */
func sayHello(c *gin.Context) {
	strToken := c.Query("token")
	fmt.Println("token=", strToken)
	flag := utils.GetStringValue(strToken)
	fmt.Println("flag =", flag)
	if flag == "1" {
		c.JSON(http.StatusBadRequest, errors.New(ErrorReason_ReLogin))
		c.Abort()
		return
	}
	claim, err := verifyAction(strToken)
	if err != nil {
		c.String(http.StatusNotFound, err.Error())
		return
	}
	if claim.Role == "admin" {
		fmt.Println("该用户是管理员可以执行相关的业务")
		fmt.Println("--------------执行Hello业务--------------")
	}

	c.JSON(http.StatusOK, claim)
}