JWT验证,以及限制一个设备登陆

简介

jwt,即json-web-token,是一种轻量级的认证的令牌,通常用于身份认证和授权。由三部分组成:头部(Header)、载荷(Payload)和签名(Signature)。

  • 头部(Header):JWT 的头部包含了两部分信息:令牌类型(typ)和签名算法(alg),用 Base64 编码后的字符串表示。
  • 载荷(Payload):JWT 的载荷包含了一些声明(Claims),用来表示一些实体(主题、发行者、过期时间等)和一些元数据。需要注意的是,载荷中的信息是可以被解码的,因此不要在 JWT 中存储敏感信息。
  • 签名(Signature):JWT 的签名用于验证消息的完整性和真实性。签名的过程需要使用头部和载荷中的信息,以及一个秘钥,然后通过指定的算法进行签名。
package jwt

import (
	"errors"
	"github.com/dgrijalva/jwt-go"
	"time"
)

const TokenExpireDuration = time.Hour * 2

var mySecret = []byte("夏天夏天悄悄过去")

type MyClaims struct {
	UserID   int64  `json:"user_id"`
	Username string `json:"username"`
	jwt.StandardClaims
}

// GenToken 生成JWT,一般在登录成功时,返回给客户端,客户端拿到token之后保存在请求头中,从route中-》
func GenToken(userID int64, username string) (string, error) {
	//创建一个我们自己的声明的数据
	c := MyClaims{
		UserID:   userID,
		Username: "username",
		StandardClaims: jwt.StandardClaims{
			ExpiresAt: time.Now().Add(TokenExpireDuration).Unix(),
			Issuer:    "bluebell",
		},
	}
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, c)
	return token.SignedString(mySecret)
}
//解析token
func ParseToken(tokenString string) (*MyClaims, error) {
	mc := new(MyClaims)
	token, err := jwt.ParseWithClaims(tokenString, mc, func(token *jwt.Token) (interface{}, error) {
		return mySecret, nil
	})
	if err != nil {
		return nil, err
	}
	if token.Valid {
		return mc, nil
	}
	return nil, errors.New("invalid token")
}

中间件:

package midllewares

import (
	"github.com/gin-gonic/gin"
	"net/http"
	"strings"
	"web_app/pkg/jwt"
)

const ContextUserIDKey = "userID"
const ContextName = "Username"

// jwtauthmiddleware 基于jwt认证的中间件
func JWTAuthMiddleware() func(c *gin.Context) {
	//客户端携带token有三种方式,1.放在请求头2.放在请求体3.放在URL
	//这里假设token放在Header的authorization中,并使用Berer开头
	//这里具体实现方式,要依据实际业务情况决定
	return func(c *gin.Context) {
		authHeader := c.Request.Header.Get("Authorization")
		if authHeader == "" {
			c.JSON(http.StatusOK, gin.H{
				"msg": "No Authorization Header",
			})
			c.Abort()
			return
		}
		//按空格分割
		parts := strings.SplitN(authHeader, " ", 2)
		if !(len(parts) == 2 && parts[0] == "Bearer") {
			c.JSON(http.StatusOK, gin.H{
				"msg": "Invalid Authorization Header",
			})
			c.Abort()
			return
		}
		mc, err := jwt.ParseToken(parts[1])
		if err != nil {
			c.JSON(http.StatusOK, gin.H{
				"msg": "无效的token",
			})
			c.Abort()
			return
		}
		c.Set(ContextName, mc.Username)
		c.Set(ContextUserIDKey, mc.UserID)
		c.Next()
	}

}

限制一个设备登陆

这里用的方案是使用redis+jwt,每次登陆完,将用户的username和token存到redis中,如果有新的登陆,将会覆盖原来的token。当用户在A端第一次登陆后,生成的token会存到redis中返回给用户,用户拿着这个token来访问特定的资源时,会和redis中的token进行比较,如果一致,才允许放行。而如果在用户在B端又进行了登陆,那么新的token将会覆盖旧的token,当用户在A端拿着旧的token来访问时,与redis中的token明显不一致,此时会拒绝访问。

package redis

import (
	"context"
	"errors"
	"fmt"
	"github.com/go-redis/redis/v8"
	"github.com/spf13/viper"
	"go.uber.org/zap"
	"time"
)

var NotExistToken = errors.New("不存在的key")

//存redis
func StorgeUserIdToken(token, username string) (err error) {
	ctx := context.Background()
  //同样获取token存活时间,将redis的token时间设为一致
	duration := time.Duration(viper.GetInt("jwt.tokenExpire"))
	//存进redis
	if err = rdb.Set(ctx, username, token, duration).Err(); err != nil {
		zap.L().Error("insert username into redis error", zap.Error(err))
		fmt.Printf("insert username into redis error:%v\n", err)
	}
	return
}

//从redis取token
func GetJwtToken(username string) (token string, err error) {
	ctx := context.Background()
	token, err = rdb.Get(ctx, username).Result()
  
	if err == redis.Nil {
		return "", NotExistToken
	}
	if err != nil {
		zap.L().Error("search redis error", zap.Error(err))
		return
	}
	return
}

然后在刚刚的中间件加上以下代码

	//获取token
	token, err := redis.GetJwtToken(mc.Username)
	//如果token不存在,判定需要登陆
	if err == redis.NotExistToken {
		controller.ResponseError(c, controller.CodeNeedLogin)
		c.Abort()
		return
	}
	
	//比较token,不一致则判定已在另一端登陆
	if part[1] != token {
		controller.ResponseError(c, controller.CodelimitLogin)
		c.Abort()
		return
	}

	c.Set(CtxUserIDKey, mc.Userid)
	c.Next()

  • 4
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值