前言
在Golang语言中,jwt-go库提供了一些jwt编码和验证的工具,因此我们很容易使用该库来实现token认证。另外,gin框架中支持用户自定义middleware,我们可以很好的将jwt相关的逻辑封装在middleware中,然后对具体的接口进行认证。
用户登陆 -> token
定义路由

v1 := router.Group("/apis/v1/"){v1.POST("/register", controller.RegisterUser) // 完成用户的注册v1.POST("/login", controller.Login) // 实现用户的登陆}
请求处理
用户校验
// 登陆逻辑校验isPass, user, err := model.LoginCheck(loginReq)// LoginCheck验证func LoginCheck(login LoginReq) (bool, User, error) {//...var user UserdbErr := DB.Where("name = ?", login.Name).Find(&user).Error//...if login.Name == user.Name && login.Pwd == user.Pwd {//...}//...}
token 生成器
// 构造SignKey: 签名和解签名需要使用一个值j := md.NewJWT()// 构造用户claims信息(负荷)claims := middleware.CustomClaims{user.Name,user.Email,jwtgo.StandardClaims{NotBefore: int64(time.Now().Unix() - 1000), // 签名生效时间ExpiresAt: int64(time.Now().Unix() + 3600), // 签名过期时间Issuer: "bgbiao.top", // 签名颁发者},}// 根据claims生成token对象token, err := j.CreateToken(claims)
返回 token
c.JSON(http.StatusOK, gin.H{"status": 0,"msg": "登陆成功","data": data, // 存放userId, userName, token...})
自定义中间件
创建 token
// 创建Token(基于用户的基本信息claims)// 使用HS256算法进行token生成// 使用用户基本信息claims以及签名key(signkey)生成tokenfunc (j *JWT) CreateToken(claims CustomClaims) (string, error) {// https://gowalker.org/github.com/dgrijalva/jwt-go#Token// 返回一个token的结构体指针token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)return token.SignedString(j.SigningKey)}
token -> 用户信息
定义路由
// secure v1sv1 := router.Group("/apis/v1/auth/")sv1.Use(middleware.JWTAuth()){sv1.GET("/userInfo", controller.GetUserInfo)}
请求过滤
获取 token
token := c.Request.Header.Get("Authorization")
解析 token
// token 解析func (j *JWT) ParserToken(tokenString string) (*CustomClaims, error) {// https://gowalker.org/github.com/dgrijalva/jwt-go#ParseWithClaims// 输入用户自定义的Claims结构体对象,token,以及自定义函数来解析token字符串为jwt的Token结构体指针// Keyfunc是匿名函数类型: type Keyfunc func(*Token) (interface{}, error)// func ParseWithClaims(tokenString string, claims Claims, keyFunc Keyfunc) (*Token, error) {}token, err := jwt.ParseWithClaims(tokenString, &CustomClaims{}, func(token *jwt.Token) (interface{}, error) {return j.SigningKey, nil})if err != nil {// https://gowalker.org/github.com/dgrijalva/jwt-go#ValidationError// jwt.ValidationError 是一个无效token的错误结构if ve, ok := err.(*jwt.ValidationError); ok {// ValidationErrorMalformed是一个uint常量,表示token不可用if ve.Errors&jwt.ValidationErrorMalformed != 0 {return nil, TokenMalformed// ValidationErrorExpired表示Token过期} else if ve.Errors&jwt.ValidationErrorExpired != 0 {return nil, TokenExpired// ValidationErrorNotValidYet表示无效token} else if ve.Errors&jwt.ValidationErrorNotValidYet != 0 {return nil, TokenNotValidYet} else {return nil, TokenInvalid}}}// 将token中的claims信息解析出来和用户原始数据进行校验// 做以下类型断言,将token.Claims转换成具体用户自定义的Claims结构体if claims, ok := token.Claims.(*CustomClaims); ok && token.Valid {return claims, nil}return nil, TokenInvalid}func ParseWithClaims(tokenString string, claims Claims, keyFunc Keyfunc) (*Token, error) {return new(Parser).ParseWithClaims(tokenString, claims, keyFunc)}
请求响应
claims := c.MustGet("claims").(*md.CustomClaims)if claims != nil {c.JSON(http.StatusOK, gin.H{"status": 0,"msg": "token有效","data": claims,})}
jwt-go 源码分析
github.com/dgrijalva/jwt-go
我在这里通过dubug调试,去了解了jwt-go toekn 的实现过程。挑出一些函数分享一下:
tolen 结构体
// A JWT Token. Different fields will be used depending on whether you're// creating or parsing/verifying a token.type Token struct {Raw string // The raw token. Populated when you Parse a tokenMethod SigningMethod // The signing method used or to be usedHeader map[string]interface{} // The first segment of the tokenClaims Claims // The second segment of the tokenSignature string // The third segment of the token. Populated when you Parse a tokenValid bool // Is the token valid? Populated when you Parse/Verify a token}
返回一个token的结构体指针
func NewWithClaims(method SigningMethod, claims Claims) *Token {return &Token{Header: map[string]interface{}{"typ": "JWT","alg": method.Alg(),},Claims: claims,Method: method,}}
token 签名
SignKey: 签名信息应该设置成动态从库中获取,提高 token 的安全性。
// Get the complete, signed tokenfunc (t *Token) SignedString(key interface{}) (string, error) {var sig, sstr stringvar err errorif sstr, err = t.SigningString(); err != nil {return "", err}if sig, err = t.Method.Sign(sstr, key); err != nil {return "", err}return strings.Join([]string{sstr, sig}, "."), nil}
token 解析
会对token进行解析,信息存放在claim.
func ParseWithClaims(tokenString string, claims Claims, keyFunc Keyfunc) (*Token, error) {return new(Parser).ParseWithClaims(tokenString, claims, keyFunc)}
token.go Structure

总结
在go中使用jwt,可以引入 github.com/dgrijalva/jwt-go,这个包是官方推荐的也是使用比较多的。在项目中集成 jwt-go 需要注意jwt并不是完全安全的,如果劫持者获取到多个token,有可能通过规则知道你的签名,会解密得到用户信息。因此SignKey 应该设置成动态从库中获取,其次必须设置一个过期时间,在token过期的时候可以自动更新token, 当然用户是不知道的,也就是说不用重复登陆。
