首页 > 其他分享 >用go实现JWT

用go实现JWT

时间:2024-08-05 11:29:03浏览次数:10  
标签:return err 实现 JWT JSON token func go gin

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')
    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"})
    }

标签:return,err,实现,JWT,JSON,token,func,go,gin
From: https://blog.csdn.net/2201_75639894/article/details/140922218

相关文章

  • vue连接mqtt实现收发消息组件超级详细
    vue连接mqtt实现收发消息组件超级详细基本概念:MQTT(MessageQueuingTelemetryTransport)是一种基于发布/订阅模式的轻量级消息传输协议,专为低带宽、高延迟或不稳定的网络环境设计。以下是MQTT实现收发消息的基本原理:客户端-服务器模型:MQTT基于客户端-服务器模型工作。客户......
  • vue实现3d词云组件
    vue实现3d词云组件<!--*@Description:词云组件页面*@Date:2024/3/1023:39--><template><div:style="{display:'flex',justifyContent:'center',border:'1pxsolidred',}"......
  • leaflet实现绘制省市区域边界并填充颜色
    leaflet实现绘制省市区域边界并填充颜色asyncinitMap(){this.map=L.map("map",{center:[30.998257,103.653534],zoom:11,attributionControl:false,//隐藏logozoomControl:false,crs:L.CRS.Baidu,});......
  • el-slider实现一个能拖动的时间范围选择器
    el-slider实现一个能拖动的时间范围选择器<template><divstyle="width:200px"><el-sliderv-model="workTime":step="5":max="1435":marks="marks":format-tooltip="f......
  • css实现el-select右侧箭头向上向下动画
    css实现el-select右侧箭头向上向下动画<divclass="chooseTag-tip"><i:class="['el-icon-arrow-up',tipFlag?'chooseTag-tip-up':'chooseTag-tip-down']"></i></div><script>tipFla......
  • vue实现项目部署成功之后提示用户更新
    vue实现项目部署成功之后提示用户刷新页面1.项目根目录新建version.jsrequire("fs").writeFileSync("./public/version.txt",newDate().getTime().toString()) 2.改写package.json中打包命令"scripts":{"dev":"vue-cli-serviceserve",......
  • 模拟实现 memmove --浅谈C语言
    内存移动-memmove也是拷贝函数,源字符串可能会被覆盖,但保证目标是想要的描述C库函数void*memmove(void*str1,constvoid*str2,size_tn)从str2复制n个字符到str1,但是在重叠内存块这方面,memmove()是比memcpy()更安全的方法。如果目标区域和源区域有重叠的......
  • springboot3集成低版本的mongo
    随手记问题背景当项目确定技术中为springboot3(3.3.1),jdk17,vue3后,风风火火地开搞,当搞的差不多了,要连mongo时,才发现mongo服务版本为3.2.12,版本太低,以至于springboot3无法连接。maven依赖:<dependency><groupId>org.springframework.boot</groupId>......
  • 分布式锁的实现:基于Redis的解决方案
    微服务架构中,分布式锁是确保跨多个节点或实例的线程安全的关键技术。当多个服务实例可能同时修改共享资源时,分布式锁可以保证在同一时间只有一个实例可以执行特定的操作。在本文中,我们将探讨如何使用Redis来实现一个简单的分布式锁机制。分布式锁的挑战在分布式系统中,锁必须满足......
  • MySQL 是如何实现数据的排序的?
    1.背景或许你面试的时候被问到了mysql的排序问题又或许你在学习排序算法的时候想到了数据库的排序是如何实现的呢下面重点从面试的角度来回答这个问题2.面试回答1.普通面试者回答普通面试者的回答通常是点对点的回答,如下:MySQL实现数据的排序主要通过排序算法和索引结构......