用go实现JWT

JWT

ISON Web Token,通过数字签名的方式,以JSON 对象为载体,在不同的服务终端之间安 全的传输信息。

JWT 最常见的场景就是授权认证,一旦用户登录,后续每个请求都将包含JWT,系统在每次处理用户请求的之前,都要先进行JWT安全校验,通过之后再进行处理

三部分组成:

  • Header

     {
     'type':'jwt', -- token 的类型
     'alg':"HS256" -- 加密方式
     }

  • Payload(载荷)

     {
     "sub":'1234567890',
     "name":'john',
     "admin":true
     }

  • Signature

     var encodedstring = base64Ur1Encode(header)+"."+base64Ur1Encode(payload)
     ​
     var signature = HMACSHA256(encodedstring,'secret')

具体后端代码实现:

         

C:.
│  go.mod
│  go.sum
│  main.go
│
├─.idea
│      .gitignore
│      modules.xml
│      test.iml
│      workspace.xml
│
└─app
        auth.go
        handler.go
        jwt.go
        jwtBucket.go
        middleware.go
        Rdb.go
        removeJWT.go
        start.go

  • Start.go
    func Start() {
    	r := gin.Default()
    
    	r.POST("/login", LoginHandler)
    
    	auth := r.Group("/auth")
    	auth.Use(AuthMiddleware())
    	auth.Use(RateLimiterMiddleware())
    	auth.Use(JWTAuthMiddleware())
    
    	{
    		auth.GET("/protected", ProtectedHandler)
    	}
    
    	err := r.Run(":8080")
    	if err != nil {
    		panic("启动失败")
    		return
    	}
    }
    middleware.go
    
    var tokenBucket = NewTokenBucket(10, 1*time.Second) // 10 tokens, refill every second
    
    func RateLimiterMiddleware() gin.HandlerFunc {
    	return func(c *gin.Context) {
    		if !tokenBucket.Take() {
    			c.JSON(http.StatusTooManyRequests, gin.H{"error": "rate limit exceeded"})
    			c.Abort()
    			return
    		}
    		c.Next()
    	}
    }
    
    func JWTAuthMiddleware() gin.HandlerFunc {
    	return func(c *gin.Context) {
    		authHeader := c.GetHeader("Authorization")
    		if authHeader == "" {
    			c.JSON(http.StatusUnauthorized, gin.H{"error": "Authorization header is missing"})
    			// 停止当前中间件链的执行,并阻止后续的中间件和处理函数的执行
    			c.Abort()
    			return
    		}
    		// 去掉Bearer前缀,获取完整的token字符串
    		tokenString := strings.TrimPrefix(authHeader, "Bearer ")
    		token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (interface{}, error) {
    			return secretKey, nil
    		})
    		// 判断err以及令牌是否有效
    		if err != nil || !token.Valid {
    			c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid token"})
    			c.Abort()
    			return
    		}
    		//检查令牌是否在 Redis 中存在
    		_, err = rdb.Get(context.Background(), tokenString).Result()
    		if errors.Is(err, redis.Nil) {
    			c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid token"})
    			c.Abort()
    			return
    		} else if err != nil {
    			c.JSON(http.StatusInternalServerError, gin.H{"error": "server error"})
    			c.Abort()
    			return
    		}
    
    
    		if claims, ok := token.Claims.(*Claims); ok {
    			c.Set("username", claims.Username)
    		} else {
    			c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid token claims"})
    			c.Abort()
    			return
    		}
    
    		c.Next()
    	}
    }
    
    func AuthMiddleware() gin.HandlerFunc {
    	return func(c *gin.Context) {
    		token, err := c.Cookie("token")
    		if err != nil {
    			c.JSON(http.StatusUnauthorized, gin.H{"error": "no token provided"})
    			c.Abort()
    			return
    		}
    
    		fmt.Println(token)
    		//将令牌添加到请求头中
    		c.Request.Header.Set("Authorization", "Bearer "+token)
    		c.Next()
    	}
    }
    jwtBucket.go
    type TokenBucket struct {
    	capacity     int           // 令牌桶的容量,即桶中可以持有的最大令牌数
    	tokens       int           // 桶中当前的令牌数
    	fillInterval time.Duration // 令牌填充的间隔时间
    	mu           sync.Mutex    // 互斥锁,防止并发访问时的数据竞争
    	lastFill     time.Time     // 上次填充令牌的时间
    }
    
    func NewTokenBucket(capacity int, fillInterval time.Duration) *TokenBucket {
    	return &TokenBucket{
    		capacity:     capacity,
    		tokens:       capacity,
    		fillInterval: fillInterval,
    		lastFill:     time.Now(),
    	}
    }
    
    func (tb *TokenBucket) Take() bool {
    	tb.mu.Lock()
    	defer func() {
    		tb.mu.Unlock()
    	}()
    
    	now := time.Now()
    	// 当前时间和上一次填充的时间差
    	elapsed := now.Sub(tb.lastFill)
    
    	if elapsed >= tb.fillInterval {
    		// 计算可以填充的令牌数
    		fillTokens := int(elapsed / tb.fillInterval)
    		// 更新桶中令牌数
    		tb.tokens += fillTokens
    		// 如果桶中令牌数大于容量,则将令牌数设置为容量
    		if tb.tokens > tb.capacity {
    			tb.tokens = tb.capacity
    		}
    		// 更新上次填充的时间
    		tb.lastFill = now
    	}
    	// 检查是否还有令牌可以取
    	if tb.tokens > 0 {
    		tb.tokens--
    		return true
    	}
    	return false
    }
    auth.go
    var secretKey = []byte("mySecretKey")
    
    type LoginRequest struct {
    	Username string `json:"username"`
    	Password string `json:"password"`
    }
    
    type Claims struct {
    	Username string `json:"username"`
    	jwt.RegisteredClaims
    }
    
    func generateToken(username string) (string, error) {
    	claims := &Claims{
    		Username: username,
    		RegisteredClaims: jwt.RegisteredClaims{
    			Issuer:    "Flipped1001",                                      // 发行者
    			Subject:   "test",                                             // 主题"
    			ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour * 24)), // 设置过期时间
    			NotBefore: jwt.NewNumericDate(time.Now()),                     // 设置token生效开始时间
    			IssuedAt:  jwt.NewNumericDate(time.Now()),                     // 设置发行时间
    		},
    	}
    	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
    	tokenString,err := token.SignedString(secretKey) // 把密钥写进去
    	if err != nil {
    		return "", err
    	} // 把密钥写进去
    
    	// 存储令牌到 Redis
    	err = rdb.Set(context.Background(), tokenString, username, 3600*time.Second).Err()
    	if err != nil {
    		return "", err
    	}
    
    	return tokenString, nil
    }
    
    func LoginHandler(c *gin.Context) {
    	var req LoginRequest
    	// ShouldBindJSON直接将json数据解析到req
    	if err := c.ShouldBindJSON(&req); err != nil {
    		c.JSON(400, gin.H{"error": "Invalid request"})
    		return
    	}
    
    	// 模拟用户认证
    	if req.Username == "Flipped" && req.Password == "021001" {
    		token, err := generateToken(req.Username)
    		if err != nil {
    			c.JSON(500, gin.H{"error": "Failed to generate token"})
    			return
    		}
    		c.SetCookie("token", token, 3600, "/", "localhost", false, true)
    		c.JSON(http.StatusOK, gin.H{"message": "login successful"})
    		return
    	} else {
    		c.JSON(401, gin.H{"error": "Unauthorized"})
    	}
    }
    handler.go
    func ProtectedHandler(c *gin.Context) {
    	username, _ := c.Get("username")
    	c.JSON(200, gin.H{"message": "Hello " + username.(string)})
    }
    Rdb.go
    var (
    	rdb *redis.Client
    )
    
    func init() {
    	rdb = redis.NewClient(&redis.Options{
    		Addr: "localhost:6379",
    	})
    }
    removeJWT.go
    func RevokeToken(c *gin.Context) {
    	tokenString, err := c.Cookie("token")
    	if err != nil {
    		c.JSON(http.StatusUnauthorized, gin.H{"error": "no token provided"})
    		return
    	}
    
    	// 从 Redis 中删除令牌
    	err = rdb.Del(context.Background(), tokenString).Err()
    	if err != nil {
    		c.JSON(http.StatusInternalServerError, gin.H{"error": "server error"})
    		return
    	}
    
    	// 删除 cookie
    	c.SetCookie("token", "", -1, "/", "localhost", false, true)
    	c.JSON(http.StatusOK, gin.H{"message": "token revoked"})
    }
  • 21
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值