1 需求
之前做后台都是用的Session机制来做认证,最近在用Gin重构之前SpringBoot项目,网上看了几个开源Gin项目发现都是用的JWT来做认证,所以打算尝试一下在Gin中使用JWT做认证。Token机制相对于Session机制来说有许多优点,不需要服务端存储数据可以减小服务端的开销;但Token也有一些问题,在签发后服务端就不能改变其状态,不能主动让其失效、不能延长有效期。这里记录一下Gin中JWT的基本使用、以及一个简单的续签方案。
本文的完整demo结构如下(完整示例代码见Github),middleware中拦截请求验证Token有效性,utils中对jwt的生成、续签和验证做封装:
|——gin_jwt
| |——middleware
| |——jwt.go
| |——model
| |——user.go
| |——utils
| |——jwt.go
| |——go.mod
| |——main.go
2 Gin中使用JWT
看了几个开源Gin项目和一些博客都使用的是dgrijalva/jwt-go这个jwt库,但是这个库4年前就停止维护了,目前相关人员维护了一个新的jwt库golang-jwt/jwt,这里使用这个新的jwt库。首先使用如下命令下载jwt库:
go get -u github.com/golang-jwt/jwt/v4
假设我需要在Token中保存用户ID、用户名和Email,自定义如下Claims结构体:
type UserInfo struct {
Id int
UserName string
Email string
}
type MyClaims struct {
User model.UserInfo
jwt.StandardClaims // 标准Claims结构体,可设置8个标准字段
}
2.1 生成Token
生成标准claim时过期时间一般不用太长,这里设置的两个小时后过期。生成Token时调用jwt库的NewWithClaims即可,传入签名算法和claims结构体,签名算法用得最多的是HS256。
const TokenExpireDuration = time.Hour * 2
var MySecret = []byte("yoursecret") // 生成签名的密钥
// 登录成功后调用,传入UserInfo结构体
func GenerateToken(userInfo model.UserInfo) (string, error) {
expirationTime := time.Now().Add(TokenExpireDuration) // 两个小时有效期
claims := &MyClaims{
User: userInfo,
StandardClaims: jwt.StandardClaims{
ExpiresAt: expirationTime.Unix(),
Issuer: "yourname",
},
}
// 生成Token,指定签名算法和claims
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
// 签名
if tokenString, err := token.SignedString(MySecret); err != nil {
return "", err
} else {
return tokenString, nil
}
}
2.2 校验Token
将前端传来的token字符串传入解析校验函数,校验函数调用ParseWithClaims进行解析,此时有两种解析方法,一种是将解析结果保存到claims变量中,另一种是从ParseWithClaims返回的Token结构体中取出Claims结构体。这里选择第一种,若token字符串合法但过期claims也会有数据,err会提示token过期。
func ParseToken(tokenString string) (*MyClaims, error) {
claims := &MyClaims{
}
_, err := jwt.ParseWithClaims(to