token 我的理解是一种凭证,客户端请求时携带此凭证才能有效访问需要验证凭证的服务端接口,而且token可以加密携带客户端的一些信息,比如基本的信息是有效期,生效日期,可以看作是令牌。加密后是一串字符串

基于JWT的Token认证机制实现

JSON Web Token(JWT)是一个非常轻巧的规范。这个规范允许我们使用JWT在用户和服务器之间传递安全可靠的信息。

JWT的组成

一个JWT实际上就是一个字符串,它由三部分组成,头部、载荷与签名。

gin怎样集成Jwt

gin可以自定义中间件,所以集成jwt可以以中间件的方式引入,jwt在goLang上已有好几个现成的开源库,我用的是jwt-go,具体使用可以查看github.com/dgrijalva/jwt-go

例子 实现用户登录时获取token和一个需要token验证的接口

JwtDemo/middleware/jwt/jwt.go 负责token生成,验证,解析token。验证token时,我这里提取token是从请求头里提取。

package jwt

import (
    "errors"
    "log"
    "net/http"
    "time"

    "github.com/dgrijalva/jwt-go"
    "github.com/gin-gonic/gin"
)

// JWTAuth 中间件,检查token
func JWTAuth() gin.HandlerFunc {
    return func(c *gin.Context) {
        token := c.Request.Header.Get("token")
        if token == "" {
            c.JSON(http.StatusOK, gin.H{
                "status": -1,
                "msg":    "请求未携带token,无权限访问",
            })
            c.Abort()
            return
        }

        log.Print("get token: ", token)

        j := NewJWT()
        // parseToken 解析token包含的信息
        claims, err := j.ParseToken(token)
        if err != nil {
            if err == TokenExpired {
                c.JSON(http.StatusOK, gin.H{
                    "status": -1,
                    "msg":    "授权已过期",
                })
                c.Abort()
                return
            }
            c.JSON(http.StatusOK, gin.H{
                "status": -1,
                "msg":    err.Error(),
            })
            c.Abort()
            return
        }
        // 继续交由下一个路由处理,并将解析出的信息传递下去
        c.Set("claims", claims)
    }
}

// JWT 签名结构
type JWT struct {
    SigningKey []byte
}

// 一些常量
var (
    TokenExpired     error  = errors.New("Token is expired")
    TokenNotValidYet error  = errors.New("Token not active yet")
    TokenMalformed   error  = errors.New("That's not even a token")
    TokenInvalid     error  = errors.New("Couldn't handle this token:")
    SignKey          string = "newtrekWang"
)

// 载荷,可以加一些自己需要的信息
type CustomClaims struct {
    ID    string `json:"userId"`
    Name  string `json:"name"`
    Phone string `json:"phone"`
    jwt.StandardClaims
}

// 新建一个jwt实例
func NewJWT() *JWT {
    return &JWT{
        []byte(GetSignKey()),
    }
}

// 获取signKey
func GetSignKey() string {
    return SignKey
}

// 这是SignKey
func SetSignKey(key string) string {
    SignKey = key
    return SignKey
}

// CreateToken 生成一个token
func (j *JWT) CreateToken(claims CustomClaims) (string, error) {
    token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
    return token.SignedString(j.SigningKey)
}

// 解析Tokne
func (j *JWT) ParseToken(tokenString string) (*CustomClaims, error) {
    token, err := jwt.ParseWithClaims(tokenString, &CustomClaims{}, func(token *jwt.Token) (interface{}, error) {
        return j.SigningKey, nil
    })
    if err != nil {
        if ve, ok := err.(*jwt.ValidationError); ok {
            if ve.Errors&jwt.ValidationErrorMalformed != 0 {
                return nil, TokenMalformed
            } else if ve.Errors&jwt.ValidationErrorExpired != 0 {
                // Token is expired
                return nil, TokenExpired
            } else if ve.Errors&jwt.ValidationErrorNotValidYet != 0 {
                return nil, TokenNotValidYet
            } else {
                return nil, TokenInvalid
            }
        }
    }
    if claims, ok := token.Claims.(*CustomClaims); ok && token.Valid {
        return claims, nil
    }
    return nil, TokenInvalid
}

// 更新token
func (j *JWT) RefreshToken(tokenString string) (string, error) {
    jwt.TimeFunc = func() time.Time {
        return time.Unix(0, 0)
    }
    token, err := jwt.ParseWithClaims(tokenString, &CustomClaims{}, func(token *jwt.Token) (interface{}, error) {
        return j.SigningKey, nil
    })
    if err != nil {
        return "", err
    }
    if claims, ok := token.Claims.(*CustomClaims); ok && token.Valid {
        jwt.TimeFunc = time.Now
        claims.StandardClaims.ExpiresAt = time.Now().Add(1 * time.Hour).Unix()
        return j.CreateToken(*claims)
    }
    return "", TokenInvalid
}

JwtDemo/api/api.go 路由处理,定义所有的restful api接口,我这里就简单写了注册,登录和一个需要token认证的接口。简单来说就是登录时候,判断数据库里是否有此用户,如果有,则生成token,返回给客户端,没有没有则登录失败。需要token认证的接口的处理过程就是先经过jwt的JwtAuth中间件验证,如果验证不通过,则直接提示客户端原因,不再交由api里的路由处理,如果验证通过,则继续交由api里的路由处理。

有几个兄弟说我的model里的代码没贴,我觉得这个在本文不重要,model是负责数据管理,处理数据的增删改查,可能你们的项目所实现的方案都不一样,比如我的model里具体实现是用的boltDb,是一个轻量的嵌入式键值对型数据库。

如需查看此demo的model详细实现方式,请看最后给的github链接。

package api

import (
    myjwt "JwtDemo/middleware/jwt"
    "JwtDemo/model"
    "log"
    "net/http"
    "time"

    jwtgo "github.com/dgrijalva/jwt-go"
    "github.com/gin-gonic/gin"
)

// 注册信息
type RegistInfo struct {
    // 手机号
    Phone string `json:"mobile"`
    // 密码
    Pwd string `json:"pwd"`
}

// Register 注册用户
func RegisterUser(c *gin.Context) {
    var registerInfo RegistInfo
    if c.BindJSON(&registerInfo) == nil {
        err := model.Register(registerInfo.Phone, registerInfo.Pwd)
        if err == nil {
            c.JSON(http.StatusOK, gin.H{
                "status": 0,
                "msg":    "注册成功!",
            })
        } else {
            c.JSON(http.StatusOK, gin.H{
                "status": -1,
                "msg":    "注册失败" + err.Error(),
            })
        }
    } else {
        c.JSON(http.StatusOK, gin.H{
            "status": -1,
            "msg":    "解析数据失败!",
        })
    }
}

// LoginResult 登录结果结构
type LoginResult struct {
    Token string `json:"token"`
    model.User
}

// Login 登录
func Login(c *gin.Context) {
    var loginReq model.LoginReq
    if c.BindJSON(&loginReq) == nil {
        isPass, user, err := model.LoginCheck(loginReq)
        if isPass {
            generateToken(c, user)
        } else {
            c.JSON(http.StatusOK, gin.H{
                "status": -1,
                "msg":    "验证失败," + err.Error(),
            })
        }
    } else {
        c.JSON(http.StatusOK, gin.H{
            "status": -1,
            "msg":    "json 解析失败",
        })
    }
}

// 生成令牌
func generateToken(c *gin.Context, user model.User) {
    j := &myjwt.JWT{
        []byte("newtrekWang"),
    }
    claims := myjwt.CustomClaims{
        user.Id,
        user.Name,
        user.Phone,
        jwtgo.StandardClaims{
            NotBefore: int64(time.Now().Unix() - 1000), // 签名生效时间
            ExpiresAt: int64(time.Now().Unix() + 3600), // 过期时间 一小时
            Issuer:    "newtrekWang",                   //签名的发行者
        },
    }

    token, err := j.CreateToken(claims)

    if err != nil {
        c.JSON(http.StatusOK, gin.H{
            "status": -1,
            "msg":    err.Error(),
        })
        return
    }

    log.Println(token)

    data := LoginResult{
        User:  user,
        Token: token,
    }
    c.JSON(http.StatusOK, gin.H{
        "status": 0,
        "msg":    "登录成功!",
        "data":   data,
    })
    return
}

// GetDataByTime 一个需要token认证的测试接口
func GetDataByTime(c *gin.Context) {
    claims := c.MustGet("claims").(*myjwt.CustomClaims)
    if claims != nil {
        c.JSON(http.StatusOK, gin.H{
            "status": 0,
            "msg":    "token有效",
            "data":   claims,
        })
    }
}

main.go 路由分发,就是在程序的入口给每个路径设置路由处理

package main

import (
    "github.com/gin-gonic/gin"

    "JwtDemo/api"
    "JwtDemo/middleware/jwt"
)

func main() {
    r := gin.Default()
    r.POST("/login", api.Login)
    r.POST("/register", api.RegisterUser)

    taR := r.Group("/data")
    taR.Use(jwt.JWTAuth())

    {
        taR.GET("/dataByTime", api.GetDataByTime)
    }
    r.Run(":8080")
}

验证功能

注册


注册用户

登录


用户登录

请求需要token的接口

携带刚才的token请求

携带token请求结果

未携带token

未携带token请求结果

无效token,随意改动刚才token的一个字符请求

无效token请求结果

完整demo

如果觉得有用,请顺手点个star吧
github.com/Wangjiaxing123/JwtDemo

作者:newtrek
链接:https://www.jianshu.com/p/1f9915818992
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

最后编辑: Simon  文档更新时间: 2021-08-30 20:59   作者:Simon