Jwt鉴权

在应用中通过使用jwt来进行接口的限定访问,可以增加接口的安全性。

  1. 安装gin-jwt

    go get github.com/appleboy/gin-jwt
    
  2. 导入jin-jwt,并命名为:jwt

    import jwt "github.com/appleboy/gin-jwt"
    
  3. 实例化GinJWTMiddleware结构体,完整的结构体字段可以查看对应的结构体

    func JwtMiddleware() *jwt.GinJWTMiddleware {
        return &jwt.GinJWTMiddleware{
            Realm:            "forum", //展示给用户的字段名称
            Key:              []byte(config.Config("JWT_KEY", "helloginjwt")), //用户加密的密钥
            Timeout:          time.Hour, //过期时间
            MaxRefresh:       time.Hour, //允许客户端刷新的最大时间
            Authenticator:    authCallback, //回调函数,用于验证用户是否存在
            Authorizator:     authPrivCallback, //回调函数,用户验证用户的权限
            Unauthorized:     unAuthFunc, //验证失败的错误信息返回
            TokenLookup:      "header: Authorization, query: token, cookie: jwt", //请求携带token的请求头字段名称
            TokenHeadName:    "Bearer",//token前缀
            TimeFunc:         time.Now,//提供当前时间
            SigningAlgorithm: "HS256",//加密算法,必填,否则会导致错误
            PayloadFunc:      payloadFunc,//回调函数,添加具体的字段到加密中
        }
    }
    
  4. 在路由中使用

        auth := r.Group("/auth")  //添加分组
        jwt := middleware.JwtMiddleware() //实例化jwt对象
        r.POST("/login", jwt.LoginHandler) //登录函数
        auth.Use(jwt.MiddlewareFunc()) //应用中间件
        {
            auth.GET("/refresh_token", jwt.RefreshHandler)
        }
    

实际使用

用户注册

  1. 创建路由

    r.POST("/register", action.Register)
    
  2. 注册用户,并返回token,简洁起见,去除多余非必要的环节

    
        //实例化jwt对象
        j := middleware.JwtMiddleware()
        uid := strconv.Itoa(int(u.ID))
    
        //生成token, u为创建的用户对象
        token, expire, err := j.TokenGenerator(uid, u)
        if err != nil {
            return
        }
        //返回对应字段
        m := make(map[string]interface{})
        m["expire"] = expire
        m["token"] = token
        resp.Success("注册成功", m)
        c.JSON(200, resp)
        return
    
  3. TokenGenerator函数

    func (mw *GinJWTMiddleware) TokenGenerator(userID string, data interface{}) (string, time.Time, error) {
        //do some thing ...
        if mw.PayloadFunc != nil {
            for key, value := range mw.PayloadFunc(data) {
                claims[key] = value
            }
        }
    
        expire := mw.TimeFunc().UTC().Add(mw.Timeout)
        claims["id"] = userID
        claims["exp"] = expire.Unix()
        claims["orig_iat"] = mw.TimeFunc().Unix()
        tokenString, err := mw.signedString(token)
        if err != nil {
            return "", time.Time{}, err
        }
    
        return tokenString, expire, nil
    }
    

    注意到if mw.PayloadFunc != nil这个语句,如果在实例中,我们没有实现对应的PayloadFunc,那么将会无法添加额外的字段到加密数据中

  4. 实现PayloadFunc 函数

    func payloadFunc(data interface{}) jwt.MapClaims {
        c := jwt.MapClaims{}
        u, ok := data.(model.User)
        if ok {
            c["id"] = u.ID
            c["email"] = u.Email
            c["status"] = u.Status
        }
        return c
    }
    

    在这里,我添加了对应的用户的id,email,status字段到加密的数据中

  5. 使用jwt解密的效果如下

    //HEADER
        {
        "alg": "HS256",
        "typ": "JWT"
        }
    
    //Body
        {
        "email": "gru.mo@126.com",
        "exp": 1568696457,
        "id": "29",
        "orig_iat": 1568692857,
        "status": 1
        }
    

用户登录

  1. 创建路由

    auth := r.Group("/auth")
    jwt := middleware.JwtMiddleware()
    r.POST("/login", jwt.LoginHandler)
    
  2. LoginHandler函数实现

    func (mw *GinJWTMiddleware) LoginHandler(c *gin.Context) {
    
        // some code ...
    
        if mw.Authenticator == nil {
            mw.unauthorized(c, http.StatusInternalServerError, mw.HTTPStatusMessageFunc(ErrMissingAuthenticatorFunc, c))
            return
        }
    
        data, error := mw.Authenticator(c)
    
        if error != nil {
            mw.unauthorized(c, http.StatusUnauthorized, mw.HTTPStatusMessageFunc(error, c))
            return
        }
    
        // Create the token
        token := jwt.New(jwt.GetSigningMethod(mw.SigningAlgorithm))
        claims := token.Claims.(jwt.MapClaims)
    
        if mw.PayloadFunc != nil {
            for key, value := range mw.PayloadFunc(data) {
                claims[key] = value
            }
        }
    
        expire := mw.TimeFunc().Add(mw.Timeout)
        claims["exp"] = expire.Unix()
        claims["orig_iat"] = mw.TimeFunc().Unix()
        tokenString, err := mw.signedString(token)
    }
    
  3. 实现Authenticator回调函数

    //验证用户是否存在
    func authCallback(c *gin.Context) (interface{}, error) {
    
        //依据邮箱和密码确定用户是否已注册
        db := model.Db()
        user := model.User{}
        c.BindJSON(&user)
        password := []byte(user.Password)
        record := db.Where("email=?", user.Email).First(&user).RecordNotFound()
        if record {
            err := errors.New("用户名错误")
            return nil, err
        }
        //对比密码是否一致
        hashPassword := []byte(user.Password)
        err := bcrypt.CompareHashAndPassword(hashPassword, password)
        if err != nil {
            err = errors.New("密码错误")
            return nil, err
        }
        return user, nil
    }
    
  4. 请求的返回结构如下:

    {
        "code": 200,
        "expire": "2019-09-17T17:24:14+08:00",
        "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImdydS5tb0AxMjYuY29tIiwiZXhwIjoxNTY4NzEyMjU0LCJpZCI6MjksIm9yaWdfaWF0IjoxNTY4NzA4NjU0LCJzdGF0dXMiOjB9.KNFKxto6takFjr1v8ORtXQBNSQooLhf8phd97alXEyk"
    }
    

刷新验证口令

  1. 刷新口令的函数已经实现,只需要注册路由即可

        auth := r.Group("/auth")
        jwt := middleware.JwtMiddleware()
        r.POST("/login", jwt.LoginHandler)
        auth.Use(jwt.MiddlewareFunc())
        {
            auth.GET("/refresh_token", jwt.RefreshHandler)
        }
    

查看完整文件

参考资料