JWT中间件 | 青训营笔记

241 阅读3分钟

马上就要大二了,学校给我们社团布置了制作学生报名组织系统的任务,其中涉及到账号登录,管理员登录鉴权,发放token的知识,这个时候就用到了JWT中间件,在这里记录一下这个小项目的JWT中间件部分。

1.什么是JWT中间件?

JWT中间件是一种用于验证JSON Web Tokens的中间件,它可以在网络应用环境之间传递声明和身份信息。JSON Web Tokens是一种紧凑且自包含的方式,用于在各方之间作为JSON对象安全地传输信息。JWT中间件可以获取请求里面的token并进行验证,并将验证之后的信息携带在上下文里以供使用。

2.JWT原理

这里引用阮一峰大佬的介绍

JWT 的原理是,服务器认证以后,生成一个 JSON 对象,发回给用户,就像下面这样。

{
  "姓名": "张三",
  "角色": "管理员",
  "到期时间": "2018年7月1日0点0分"
}

以后,用户与服务端通信的时候,都要发回这个 JSON 对象。服务器完全只靠这个对象认定用户身份。为了防止用户篡改数据,服务器在生成这个对象的时候,会加上签名(详见后文)。

服务器就不保存任何 session 数据了,也就是说,服务器变成无状态了,从而比较容易实现扩展。

3.Jwt中间件和Gin项目结合使用

在开始编写程序之前,记得引入Go语言的JWT库

//以下请求都需要有管理员token才能执行  
userGroup.GET("/user/info", handler.GetUserInfoHandler) //获取用户信息(文档要求)  
userGroup.GET("/registration-information", handler.QueryHandler) //获取学生报名信息(文档要求)  
userGroup.POST("/update", handler.UpdateHandler) //更新报名信息  
userGroup.DELETE("/delete", handler.DeleteHandler) //删除报名信息  
}

接着开始写中间件,就是每一次我们发起请求都需要执行的函数

func JwtMiddleware() gin.HandlerFunc {  
return func(c *gin.Context) {  
//从请求头中获取token  
tokenStr := c.Request.Header.Get("Authorization")  
//用户不存在  
if tokenStr == "" {  
c.JSON(http.StatusOK, model.CommonResult{Code: errno.Fail.ErrCode, Message: "没有token"})  
c.Abort() //阻止执行  
return  
}  
//验证token  
tokenStruck, err := util.ParseToken(tokenStr)  
if err != nil {  
c.JSON(http.StatusOK, model.CommonResult{Code: errno.Fail.ErrCode, Message: "token不正确"})  
c.Abort() //阻止执行  
return  
}  
//token超时  
if time.Now().Unix() > tokenStruck.ExpiresAt.Time.Unix() {  
c.JSON(http.StatusOK, model.CommonResult{Code: errno.Fail.ErrCode, Message: "token过期"})  
c.Abort() //阻止执行  
return  
}  
  
c.Next()  
}  
}

结尾记得使用c.next让框架执行下一步操作

中间件部分写好以后,我们就需要写JWT生成token和解析token部分了, 首先我们要创建一个Claim结构体 我这里只用了两个字段,ID和账户名

type Claim struct {  
ID int  
Account string  
jwt.RegisteredClaims   //这里继承jwt结构体部分
}

紧接着我们完成token签发和解析token函数就可以了 SignToken

func SignToken(account string, id int) (string, error) {  
  
uc := &Claim{  
id,  
account,  
jwt.RegisteredClaims{  
IssuedAt: jwt.NewNumericDate(time.Now()),  
ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour)),  
Issuer: "CZCZCZ",  
},  
}  
token := jwt.NewWithClaims(jwt.SigningMethodHS256, uc)  
  
return token.SignedString([]byte(constant.Key))  
  
}

ParseToken

func ParseToken(tokenString string) (*Claim, error) {  
  
//ParseWithClaims三个参数,token,Claim结构体和一个将密钥byte切片传进去的匿名函数  
token, err := jwt.ParseWithClaims(tokenString, &Claim{}, func(t *jwt.Token) (interface{}, error) {  
return []byte(constant.Key), nil  
})  
if err != nil {  
return nil, err  
}  
// 从 Token 的负载里拿到 Claims,由于定制化了 Claims,故需要做类型转换  
//将返回的Claims转换成我们自己的Claim  
if uc, ok := token.Claims.(*Claim); ok {  
return uc, nil  
}  
return nil, errors.New("ParseToken error")  
}

这样我们就搞定了,下次只需要在登录账号的时候给用户签发一个带过期时间的token,再把这个token放进请求头里,就可以实现让账号保持会话登录了,这个token可以放在redis里与redis缓存相结合来使用。但JWT签发token也有不好的地方,服务器一旦签发token就要不回去了,可能会导致一些错误token的滥用。还有很多鉴权手段比如cookie和session之类的,可以根据不同的实际业务场景选择使用。