三段代码搞懂go-jwt做token登录校验的基本使用

由于本来搭建的是基于gin+go-micro+etcd的微服务架构,生成token放在了用户服务,校验则放在了api网关,因此两边代码可能重复。其实也可以共用,懒得改了😂

用户服务端

package handler

import (
	"context"
	"errors"
	"fmt"
	"github.com/dgrijalva/jwt-go"
	"math/rand"
	"micro-file-store/common"
	"micro-file-store/conf"
	"micro-file-store/databases/redisdb"
	"micro-file-store/model/user"
	userProto "micro-file-store/service/account/proto"
	"micro-file-store/util"
	"regexp"
	"time"
)
// 创建token之前需要检验用户有效性,就不放上来了

// 定义claim包含的内容
type jwtClaims struct {
	jwt.StandardClaims
	UserID   uint   `json:"user_id"`
	UserName string `json:"user_name"`
	Password string `json:"Password"`
	RedisKey string `json:"redis_key"`
	Status   uint32 `json:"Status"`
	UserType uint32 `json:"user_type"`
}

const (
	// 盐
	jwtSalt = "瞅你咋地?"
)

/**
 *@Method 获取token
 *@Params user usermodel.UserModel
 *@Return token string, err error
 */
func createToken(user usermodel.UserModel) (token string, err error) {
	var key string
	// 尝试获取上一次的redis key
	lastKey, err := redisdb.GetJWTPool().Get(user.UserName).Result()
	if err != nil {
    // 如果为空,说明未登入过或者已过期删除
		if err == redis.Nil{
			// 如果上次一的token过期了 使用username + 随机五位大写字母,作为redis查询token的key
			r := rand.New(rand.NewSource(time.Now().UnixNano()))
			adStr := make([]byte, 5)
			for i := 0; i < 5; i++ {
				b := r.Intn(26) + 65
				adStr[i] = byte(b)
			}
			key = user.UserName + string(adStr)
		}else{
			return "", err
		}
	}else{
    //如果上次一的token还未过期,继续用上一次的key
		key = lastKey
	}
	// 添加claims信息
	claims := jwtClaims{
		UserID:   user.ID,
		UserName: user.UserName,
		Password: user.Password,
		RedisKey: key,
		Status:   user.Status,
		UserType: user.UserType,
		StandardClaims: jwt.StandardClaims{
			// 签发时间
			IssuedAt: time.Now().Unix(),
			// 不早于。。。生效
			NotBefore: time.Now().Unix() - 1000,
			// 有效时间
			ExpiresAt: time.Now().Unix() + 60*60*24*7,
			Issuer:    "ironHuang",
		},
	}
	jwtToken := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	// 加盐转换为字符串
	token, err = jwtToken.SignedString([]byte(jwtSalt))
	if err != nil {
		return "", err
	}
	// 将token保存至redis
	err = saveToken(key, token, user.UserName)
	if err != nil {
		return "", err
	}
	return token, nil
}

/**
 *@Method 保存token
 *@Params redisKey, token, userName string
 *@Return error
 */
func saveToken(redisKey, token, userName string) error {
  err = redisdb.GetJWTPool().Set(redisKey, token, 60*60*24*7*1000*1000*1000).Err()
	if err != nil {
		fmt.Println(err.Error())
		return err
	}
  // 关联rediskey和username
	err := redisdb.GetJWTPool().Set(userName, redisKey, 60*60*24*7*1000*1000*1000).Err()
	if err != nil {
		fmt.Println(err.Error())
		return err
	}
	return nil
}

api网关端(关于ParseToken错误处理可以参考最后一段代码理解)

package middleware

import (
	"errors"
	"github.com/dgrijalva/jwt-go"
	"github.com/gin-gonic/gin"
	"micro-file-store/common"
	"micro-file-store/databases/redisdb"
	"net/http"
	"time"
)

const (
	// 盐
	jwtSalt = "瞅你咋地?"
)

// 定义claim
type JwtClaims struct {
	jwt.StandardClaims
	UserID   int64  `json:"user_id"`
	UserName string `json:"user_name"`
	Password string `json:"Password"`
	RedisKey string `json:"redis_key"`
	Status   uint32 `json:"Status"`
	UserType uint32 `json:"user_type"`
}

// 预设错误信息
var (
	TokenExpired     = errors.New("Token is expired")
	TokenNotValidYet = errors.New("Token not active yet")
	TokenMalformed   = errors.New("That's not even a token")
	TokenInvalid     = errors.New("Couldn't handle this token:")
)

/**
 *@Method jwt认证主函数
 *@Params
 *@Return gin.HandlerFunc
 */
func JWTAuth() gin.HandlerFunc {
	return func(ctx *gin.Context) {
		// 获取前端传回的token(传递方式不同,获取的位置也不同,根据实际情况选择)
		authToken := ctx.Request.FormValue("auth_token")
		// 无token直接返回错误
		if authToken == "" {
			ctx.JSON(http.StatusOK, gin.H{
				// 返回代码
				"code": common.StatusTokenInvalid,
				// 返回信息
				"msg": "未登录或非法访问",
			})
			// 校验失败终止后续操作
			ctx.Abort()
			return
		}
		// 解析token
		claims, err := ParseToken(authToken)
		// 错误处理
		if err != nil {
			// token过期
			if err == TokenExpired {
				ctx.JSON(http.StatusOK, gin.H{
					"code": common.StatusTokenInvalid,
					"msg":  "授权过期,请重新登录",
				})
				ctx.Abort()
				return
			}
			ctx.JSON(http.StatusOK, gin.H{
				"code": common.StatusTokenInvalid,
				"msg":  err.Error(),
			})
			ctx.Abort()
			return
		}
		if storeToken := redisdb.GetJWTPool().Get(claims.RedisKey).Val(); authToken != storeToken {
			ctx.JSON(http.StatusOK, gin.H{
				"code": common.StatusTokenInvalid,
				"msg":  "授权过期,请重新登录",
			})
			ctx.Abort()
			return
		}
		// 将claim加入上下文,便于后续使用
		ctx.Set("user_claims", claims)

		/*
			claimObj, _ := ctx.Get("user_claims")
			转成JwtClaims
			claimsObj := claimObj.(*JwtClaims)
			userId := claimsObj.UserID
			fmt.Println(userId)
		*/

		ctx.Next()
	}
}

/**
 *@Method 解析token
 *@Params token String
 *@Return *JwtClaims, error
 */
func ParseToken(tokenString string) (*JwtClaims, error) {
	token, err := jwt.ParseWithClaims(tokenString, &JwtClaims{}, func(token *jwt.Token) (interface{}, error) {
		return []byte(jwtSalt), nil
	})
	if err != nil {
		// 如果强转*jwt.ValidationError成功,对错误进行判断
		if validationError, ok := err.(*jwt.ValidationError); ok {
			/*
				当validationError中的错误信息由错误的token结构引起时,
				**************************************************
				源码vErr.Errors |= ValidationErrorExpired,
				或运算,只有都为0才为0,0000 0000|0000 0101 = 0000 0101
				由于vErr.Errors的初始值为0,所以等价于将ValidationErrorMalformed赋值给validationError的Errors,
				*****************************************************
				如果没有赋值,Errors的初始值为0,那么validationError.Errors&jwt.ValidationErrorMalformed = 0,
				赋值后造成validationError.Errors不为0,那么validationError.Errors&jwt.ValidationErrorMalformed != 0
			*/
			if validationError.Errors&jwt.ValidationErrorMalformed != 0 {
				return nil, TokenMalformed
				// 以下与上方原理相同
			} else if validationError.Errors&jwt.ValidationErrorExpired != 0 {
				return nil, TokenExpired
			} else if validationError.Errors&jwt.ValidationErrorNotValidYet != 0 {
				return nil, TokenNotValidYet
			} else {
				return nil, TokenInvalid
			}
		}
	}
	if token != nil {
		// 强转成jwtClaims
		if claims, ok := token.Claims.(*JwtClaims); ok && token.Valid {
			// 如果合法返回claims
			return claims, nil
		}
		return nil, TokenInvalid
	} else {
		return nil, TokenInvalid
	}
}

/**
 *@Method 刷新token
 *@Params token String
 *@Return string,error
 */
func RefreshToken(tokenString string) (string, error) {
	// 重写TimeFunc初始化过期时间
	jwt.TimeFunc = func() time.Time {
		return time.Unix(0, 0)
	}
	// 解析*jwt.Token
	token, err := jwt.ParseWithClaims(tokenString, &JwtClaims{}, func(token *jwt.Token) (interface{}, error) {
		return []byte(jwtSalt), nil
	})
	if err != nil {
		return "", err
	}
	// 强转成定义的jwtClaims,成功继续操作,失败返回错误
	if claims, ok := token.Claims.(JwtClaims); ok && token.Valid {
		// 设置为当前时间过期
		jwt.TimeFunc = time.Now
		// 过期时间加1小时
		claims.StandardClaims.ExpiresAt = time.Now().Local().Add(1 * time.Hour).Unix()
		// 加密
		jwtToken := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
		// 加盐转字符串
		token, err := jwtToken.SignedString([]byte(jwtSalt))
		// 更新redis,使用ExpireAt(claims.RedisKey, time.Now().Local().Add(1*time.Hour)可能会有误差
		// 可以用第二种更新过期时间
		//redisdb.GetJWTPool().ExpireAt(claims.RedisKey, time.Now().Local().Add(1*time.Hour))
		redisdb.GetJWTPool().Expire(claims.RedisKey, 60*60*1000*1000*1000)
		return token, err
	}
	return "", TokenInvalid
}

关于ParseToken错误处理原理

package main

import (
	"fmt"
	"github.com/pkg/errors"
)

// 定义一个错误码
const myValidErr = 1

// 定义一个结构体
type user struct {
	userName string
}

// 自定义错误
type myError struct {
	// 用于存放外部返回的错误
	Inner error
	// 可以理解为错误代码
	Errors uint32
	// 如果没有另外返回的错误,可以使用text中设置的错误信息
	text string
}

func (u *user) valid() error {
	// 实例化myError
	vErr := new(myError)
	// 假设如果用户名不等于lalala
	if u.userName != "lalala" {
		// 添加错误信息
		vErr.Inner = errors.New("这是一个错误信息")
		// 或运算,只有都为0才为0,0000 0000|0000 0101 = 0000 0101,
		//vErr.Errors初始值为0,这里相当于将myValidErr赋值给vErr.Errors。
		vErr.Errors |= myValidErr
	}
	return vErr
}


/* 源码builtin.go中,error是接口类型意味着可以自定义
type error interface {
	Error() string
}
*/
func (e myError) Error() string {
	if e.Inner != nil {
		return e.Inner.Error()
	} else if e.text != "" {
		return e.text
	} else {
		return "上面都没有,只能靠我拯救世界了"
	}
}

func main() {
	testUser := user{
		userName: "hahaha",
	}
	err := testUser.valid()
	// 强转成myError
	errtest := err.(*myError)

	fmt.Println(errtest.Errors)
	// 如果Inner没有信息将打印text的错误信息,如果text也没用,只能放大招了
	fmt.Println(errtest.Error())
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值