gin 基于JWT实现token令牌功能

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,601评论 18 139
  • 今天的文章介绍一种适用于restful+json的API认证方法,这个方法是基于jwt,并且加入了一些从oauth...
    茶客furu声阅读 22,274评论 9 49
  • 对于灵魂来说,所有的繁荣,都是幻象。咖啡需要糖满足味蕾,也需要苦涩留下回味。生活需要欢闹调剂坎坷,也需要孤独让人深...
  • 今天开始了第一次学习游泳,感觉…还是有点怕水啊! 在水中进行了十次憋气蹲下,十次不捏住鼻子的蹲下,十次在水里吐泡泡...
    胡萝卜猫阅读 195评论 2 0
  • 抬眼西望 日已落到山后 在天与山际间还残留着余晖。 近阳处的天空 好似一幅抽象派油画 色彩斑斓 而近目处的云影 则...
    启蒙的零件阅读 382评论 0 1