马上就要大二了,学校给我们社团布置了制作学生报名组织系统的任务,其中涉及到账号登录,管理员登录鉴权,发放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之类的,可以根据不同的实际业务场景选择使用。