JWT是一种跨域认证解决方案,规定了一种Token实现方式,目前多用于前后端分离项目
在之前的一些web项目中,我们通常使用的是Cookie-Session
模式实现用户认证。相关流程大致如下:
这种方案依赖于客户端(浏览器)保存Cookie,并且需要在服务端存储用户的session数据。
web应用可能是前后端分开部署在不同的端口,有时候我们还需要支持第三方登录,这下Cookie-Session
的模式就有些力不从心了
JWT就是一种基于Token的轻量级认证模式,服务端认证通过后,会生成一个JSON对象,经过签名后得到一个Token(令牌)再发回给用户,用户后续请求只需要带上这个Token,服务端解密之后就能获取该用户的相关信息了
我们在这里直接使用jwt-go
这个库来实现我们生成JWT和解析JWT的功能
jwt包自带的jwt.StandardClaims只包含了官方字段,我们需要根据自己的需求来决定JWT中保存哪些数据,比如我们规定在JWT中要存储username
信息,那么我们就定义一个Claims
结构体如下:
type Claims struct {
UserId uint
jwt.StandardClaims
}
然后我们定义JWT的过期时间,以及jwt加密的密钥
var jwtKey = []byte("a_secret_crect")
const TokenExpireDuration = time.Hour * 2
根据定义的结构体,创建声明,指定token过期时间,发放时间,发放人
使用指定的签名方法创建签名对象
使用密钥签名并获得完整的编码后的字符串token
claims := &Claims{
UserId: user.ID,
StandardClaims: jwt.StandardClaims{
//token过期时间
ExpiresAt: expireTime.Unix(),
//发放时间
IssuedAt: time.Now().Unix(),
//发放人
Issuer: "zhaoheng",
Subject: "user token",
},
}
// 使用指定的签名方法创建签名对象
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
//使用指定的secret签名并获得完整的编码后的字符串token
tokenString, err := token.SignedString(jwtKey)
使用jwt.ParseWithClaims函数,将字符串token传进去
func ParseToken(tokenString string) (*jwt.Token, *Claims, error) {
claims := &Claims{}
token, err := jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (i interface{}, err error) {
return jwtKey, nil
})
return token, claims, err
}
登录控制器
//发放token
token, err := common.ReleaseToken(user)
func AuthMiddleWare() gin.HandlerFunc {
return func(ctx *gin.Context) {
//假设Token放在Header的Authorization中,并使用Bearer开头
tokenString := ctx.GetHeader("Authorization")
if tokenString == "" || !strings.HasPrefix(tokenString, "Bearer ") {
ctx.JSON(http.StatusUnauthorized, gin.H{"code": 401, "msg": "权限不足"})
ctx.Abort()
return
}
//去掉Bearer
tokenString = tokenString[7:]
//使用之前定义好的解析JWT的函数来解析它
token, claims, err := common.ParseToken(tokenString)
if err != nil || !token.Valid {
ctx.JSON(http.StatusUnauthorized, gin.H{"code": 401, "msg": "权限不足"})
ctx.Abort()
return
}
//验证通过,获取claim中的userid
userId := claims.UserId
DB := common.GetDB()
var user model.User
DB.First(&user, userId)
if user.ID == 0 {
ctx.JSON(http.StatusUnauthorized, gin.H{"code": 401, "msg": "权限不足"})
ctx.Abort()
return
}
//如果用户存在,将user信息写入上下文
//后续的处理函数可以用过c.Get("user")来获取当前请求的用户信息
ctx.Set("user", user)
ctx.Next()
}
}
r.GET("/api/auth/info", middleware.AuthMiddleWare(), controller.Info)
func Info(ctx *gin.Context) {
user, _ := ctx.Get("user")
ctx.JSON(200, gin.H{"code": 200, "data": gin.H{"user": user}})
}