golang 实现带令牌限流的JWT demo
demo里提供了三个接口,认证取token,刷新token,获取信息,token过期前也会在header里写上新token(便于客户端更换)
package mainimport ("fmt""net/http""sync""time""github.com/gin-gonic/gin""github.com/golang-jwt/jwt/v5"
)const (TOKEN_SECRET_KEY = "secret" // 密钥TOKEN_EXPIRE_TIME = 2 * time.Hour // 2小时过期TOKEN_REFRESH_TIME = 10 * time.Minute // 接近过期时会在header里面加上新token,客户端可以识别也可以自行拉取新
)var tb *TokenBucketfunc init() {tb = NewTokenBucket(100, 5000)
}func main() {// 创建一个Gin引擎r := gin.Default()// 限流r.Use(rateMiddWare(tb))// 登录接口r.POST("/login", loginHandler)// 受保护接口v1 := r.Group("/v1").Use(authReqMiddWare()){v1.GET("/user", userHandler)v1.GET("/refresh-token", refreshTokenHandler)}// 监听并在8080端口上启动服务r.Run(":8080")
}/*** 登陆* curl 127.0.0.1:8080/login -X POST* return {"token":"eyJhbGciO..."}*/
func loginHandler(c *gin.Context) {// TODO 验证账户密码// account + passwd 需要从db中拉取信息校验// 获取用户信息userId := "123"userName := "test 123"// 签名JWTtokenString, err := generateJWTToken(userId, userName)if err != nil {c.JSON(http.StatusInternalServerError, gin.H{"message": "Failed to generate token"})return}// 返回JWT给客户端c.JSON(http.StatusOK, gin.H{"token": tokenString})
}/*** 用户信息* curl 127.0.0.1:8080/v1/user -H "token:eyJhbGciO..."* return {"UserId":"123","UserName":"test 123","exp":1694741333,"nbf":1694734133,"iat":1694734133}*/
func userHandler(c *gin.Context) {claims, bool := c.Get("claims")if bool {// TODO 其他用户信息可以用UID查 缓存 和 数据库// findbyId()c.JSON(http.StatusOK, claims)return}c.JSON(http.StatusOK, gin.H{"message": "not found"})
}/*** 刷新token* curl 127.0.0.1:8080/v1/refresh-token -H "token:eyJhbGciO..."* return {"token":"eyJhbGciO..."}*/func refreshTokenHandler(c *gin.Context) {claims, bool := c.Get("claims")if !bool {c.JSON(http.StatusOK, gin.H{"message": "not found claims"})return}fmt.Println(claims)val, ok := claims.(*jwtClaims)if !ok {c.JSON(http.StatusOK, gin.H{"message": "not found"})return}tokenString, err := generateJWTToken(val.UserId, val.UserName)if err != nil {c.JSON(http.StatusInternalServerError, gin.H{"message": "Failed to generate token"})return}c.JSON(http.StatusOK, gin.H{"token": tokenString})return
}//jwt
type jwtClaims struct {UserId stringUserName stringjwt.RegisteredClaims // jwt中标准格式
}/*** 校验token* 如果想从服务端控制发出的token,可以通过redis标记也能达到让指定token提前过期的目的*/
func authReqMiddWare() gin.HandlerFunc {return func(c *gin.Context) {// 读取TOKENtokenStr := c.GetHeader("token")if tokenStr == "" {c.JSON(http.StatusForbidden, gin.H{"message": "Token not exist"})c.Abort()return}// 解析tokentoken, err := jwt.ParseWithClaims(tokenStr, &jwtClaims{}, func(token *jwt.Token) (interface{}, error) {return []byte(TOKEN_SECRET_KEY), nil})if err != nil {c.JSON(http.StatusForbidden, gin.H{"message": err.Error()})c.Abort()return}claims, ok := token.Claims.(*jwtClaims)// 这里默认会检查ExpiresAt是否过期if ok && token.Valid { now := time.Now()// 检查过期时间,对快要过期的添加http header `refresh-token`if t := claims.ExpiresAt.Time.Add(-TOKEN_REFRESH_TIME); t.Before(now) {tokenString, err := generateJWTToken(claims.UserId, claims.UserName)if err != nil {c.JSON(http.StatusInternalServerError, gin.H{"message": "Failed to generate token"})c.Abort()return}c.Header("refresh-token", tokenString) //}c.Set("claims", claims)}}
}// 生成JWT token
func generateJWTToken(userId, userName string) (string, error) {now := time.Now()claims := jwtClaims{UserId: userId,UserName: userName,RegisteredClaims: jwt.RegisteredClaims{ExpiresAt: &jwt.NumericDate{Time: time.Now().Add(TOKEN_EXPIRE_TIME)}, // 过期时间IssuedAt: jwt.NewNumericDate(now), // 签发时间NotBefore: jwt.NewNumericDate(now), // 生效时间},}token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)return token.SignedString([]byte(TOKEN_SECRET_KEY))
}// 限流
func rateMiddWare(tb *TokenBucket) gin.HandlerFunc {return func(c *gin.Context) {if !tb.AllowRequest() {c.JSON(http.StatusTooManyRequests, gin.H{"message": http.StatusText(http.StatusTooManyRequests)})c.Abort()return}}
}// 令牌
type TokenBucket struct {cap int // 桶容量rate float64 // 每秒生产个数tokenNum int // 当前计数lastTime time.Time // 上一个产生时间mu sync.Mutex
}func NewTokenBucket(cap int, rate float64) *TokenBucket {return &TokenBucket{cap: cap,rate: rate,tokenNum: cap,lastTime: time.Now(),}
}// 拿令牌
func (tb *TokenBucket) AllowRequest() bool {tb.mu.Lock()defer tb.mu.Unlock()now := time.Now()second := now.Sub(tb.lastTime).Seconds() // 计算经过多少秒newTokens := int(second * tb.rate) // 计算产生的令牌数量if newTokens > 0 {tb.tokenNum = tb.tokenNum + newTokensif tb.tokenNum > tb.cap { // 不能超过容量tb.tokenNum = tb.cap}tb.lastTime = now}if tb.tokenNum > 0 {tb.tokenNum--return true}return false
}
相关文章:
golang 实现带令牌限流的JWT demo
demo里提供了三个接口,认证取token,刷新token,获取信息,token过期前也会在header里写上新token(便于客户端更换) package mainimport ("fmt""net/http""sync""time&qu…...
【web开发】9、Django(4)ajax请求
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 一、Ajax是什么?二、使用步骤二、订单管理 提示:以下是本篇文章正文内容,下面案例可供参考 一、Ajax是什么? Ajax&…...

消息队列中,如何保证消息的顺序性?
本文选自:advanced-java 作者:yanglbme 问:如何保证消息的顺序性? 面试官心理分析 其实这个也是用 MQ 的时候必问的话题,第一看看你了不了解顺序这个事儿?第二看看你有没有办法保证消息是有顺序的…...
Shell别名的使用方法及管理技巧
文章目录 1. 引言1.1 概述1.2 目的1.3 适用范围 2. Shell和别名2.1 Shell简介2.2 别名的作用2.3 别名的语法 3. 创建别名3.1 临时别名3.2 永久别名 4. 别名的应用4.1 简化命令4.2 自定义命令4.3 提高工作效率 5. 管理别名5.1 查看别名5.2 修改别名5.3 删除别名 6. 实例演示6.1 …...

C/C++选择题好题分享
...

kafka副本机制
目录 前言 副本定义 副本角色 In-sync Replicas(ISR) 参考资料 前言 现在的很多的分布式系统都支持副本的机制,比如Mysql就有副本的机制,一般使用副本有如下特性和好处。 提供数据冗余。即使系统部分组件失效,系…...

服务注册发现_actuator微服务信息完善
SpringCloud体系里的,服务实体向eureka注册时,注册名默认是IP名:应用名:应用端口名。 问题: 自定义服务在Eureka上的实例名怎么弄呢 在服务提供者pom中配置Actuator依赖 <!-- actuator监控信息完善 --> <dependency><groupId…...

常见列表字典排序
一、列表排序 demoList [1, 3, 2, 4, 9 ,7]res sorted(demoList) # 默认升序# 降序 # res sorted(demoList, reverseTrue)print(res)二、字典排序 demoDict {"篮球": 5, "排球": 9, "网球": 6, "足球": 3}# sorted排序 res so…...

【Acwing1027】方格取数(动态规划)题解
题目描述 思路分析 错误思路: 贪心法,先走一次求出最大值,把走过的路上面的数值清零,然后用同样的方法再走一遍求最大值,然后让这两个最大值相加就是最后的结果。 很多人在看到这个题目的时候会有上面的思路&#x…...
合并区间:解决区间重叠问题的高效算法
合并区间:解决区间重叠问题的高效算法 leetcode 56. 合并区间 合并区间是一个常见的编程问题,通常涉及到一组区间,你需要将重叠的区间合并成更大的区间。这篇博客将介绍这个问题的背景,然后解释一个高效的解决方案,同…...

万字总结HTML超文本标记语言
一、前言:什么是网页? 网站是指在因特网上根据一定的规则,使用 HTML 等制作的用于展示特定内容相关的网页集合。网页是网站中的一“页”,通常是 HTML 格式的文件,它要通过浏览器来阅读。 网页是构成网站的基本元素,它通常由图片、链接、文字、声音、视频等元素组成。通常…...

Java线程池是如何保证核心线程不被销毁的
来源: Java线程池是如何保证核心线程不被销毁的_朝 花 拾 夕的博客-CSDN博客 对于Java中 Thread 对象,同一个线程对象调用 start 方法后,会在执行完run 后走向终止(TERMINATED)状态,也就是说一个线程对象是不可以通过多…...
新课程标准培养学生“高考物理关键能力”的实践研究课题文献综述
目录 一、高考物理能力的要求与评估标准 二、高考物理关键能力的定义与内涵...

急救车工业路由器应用提升急救效率:车联网、数据采集与远程诊疗
急救车作为医院里医疗急救过程中的重要组成部分,在智慧医疗物联网领域中急救车应用4G工业路由器实现网络部署与数据采集,通过工业4G路由器能够实时采集到病患的生理数据、救护现场音频与视频、GPS定位以及车辆运行状态等重要信息。这些数据将被传输到医疗…...
【操作系统】聊聊CPU上下文切换实操
如何查看系统的上下文切换情况 上一篇文章我们说了过多的上下文切换,会把CPU时间消耗在寄存器、内核栈以及虚拟内存等数据的保存和恢复上,那么当出现系统的上下文切换过多的时候,我们如果通过监控指标查看呢。 vmstat 是一个常用的系统性能…...

【java】【SpringBoot】【四】原理篇 bean、starter、核心原理
目录 一、自动配置 1、bean加载方式(复习) 1.1 加载方式-xml方式生命bean 1.2 加载方式-xml注解方式声明bean 1.3 注解方式声明配置类 1.4 FactoryBean 1.5 proxyBeanMethod属性 1.6 使用Import注解导入 1.7 使用上下文对象在容器初始化完毕后注…...

【精品资源】Java毕业设计攻略:从选题到答辩,一站式指南
导读: Java毕业设计是计算机科学与技术专业学生展示其编程能力、问题解决能力和创新思维的重要环节。这篇博客将为您提供一站式的Java毕业设计攻略,帮助您从选题到答辩,顺利完成毕业设计。 一、选题阶段 寻找灵感: 探讨热门技术如…...

文件高效批量重命名,轻松重命名不同类型的文件名并隐藏编号
你是否曾经因为文件名混乱而感到困扰?你是否希望有一种方法可以快速、简单地管理你的文件名?如果你的答案是肯定的,那么我们的产品——文件重命名工具,将是你的完美解决方案! 首先我们要进入文件批量改名高手主页面&a…...

接口的定义与实现
一个c,代表类(class)。 一个c再加上两竖线,代表抽象类。 一个i,代表接口(interface)。 package com.mypackage.oop.demo12;//接口都需要有一个实现类 public interface UserService {//接口中定…...

浅谈低压绝缘监测及定位系统在海上石油平台的研究与应用
安科瑞 华楠 摘要:海上石油平台低压系统与陆地电力系统有很大区别,其属于中性点绝缘系统,在出现单相接地故障时,系统允许带故障正常运行2 h,保证海上重要电气设备不会立即关停。现以渤海某海上平台为例,其…...

VB.net复制Ntag213卡写入UID
本示例使用的发卡器:https://item.taobao.com/item.htm?ftt&id615391857885 一、读取旧Ntag卡的UID和数据 Private Sub Button15_Click(sender As Object, e As EventArgs) Handles Button15.Click轻松读卡技术支持:网站:Dim i, j As IntegerDim cardidhex, …...

Swift 协议扩展精进之路:解决 CoreData 托管实体子类的类型不匹配问题(下)
概述 在 Swift 开发语言中,各位秃头小码农们可以充分利用语法本身所带来的便利去劈荆斩棘。我们还可以恣意利用泛型、协议关联类型和协议扩展来进一步简化和优化我们复杂的代码需求。 不过,在涉及到多个子类派生于基类进行多态模拟的场景下,…...

理解 MCP 工作流:使用 Ollama 和 LangChain 构建本地 MCP 客户端
🌟 什么是 MCP? 模型控制协议 (MCP) 是一种创新的协议,旨在无缝连接 AI 模型与应用程序。 MCP 是一个开源协议,它标准化了我们的 LLM 应用程序连接所需工具和数据源并与之协作的方式。 可以把它想象成你的 AI 模型 和想要使用它…...

现代密码学 | 椭圆曲线密码学—附py代码
Elliptic Curve Cryptography 椭圆曲线密码学(ECC)是一种基于有限域上椭圆曲线数学特性的公钥加密技术。其核心原理涉及椭圆曲线的代数性质、离散对数问题以及有限域上的运算。 椭圆曲线密码学是多种数字签名算法的基础,例如椭圆曲线数字签…...
【JavaSE】绘图与事件入门学习笔记
-Java绘图坐标体系 坐标体系-介绍 坐标原点位于左上角,以像素为单位。 在Java坐标系中,第一个是x坐标,表示当前位置为水平方向,距离坐标原点x个像素;第二个是y坐标,表示当前位置为垂直方向,距离坐标原点y个像素。 坐标体系-像素 …...

ArcGIS Pro制作水平横向图例+多级标注
今天介绍下载ArcGIS Pro中如何设置水平横向图例。 之前我们介绍了ArcGIS的横向图例制作:ArcGIS横向、多列图例、顺序重排、符号居中、批量更改图例符号等等(ArcGIS出图图例8大技巧),那这次我们看看ArcGIS Pro如何更加快捷的操作。…...
Rapidio门铃消息FIFO溢出机制
关于RapidIO门铃消息FIFO的溢出机制及其与中断抖动的关系,以下是深入解析: 门铃FIFO溢出的本质 在RapidIO系统中,门铃消息FIFO是硬件控制器内部的缓冲区,用于临时存储接收到的门铃消息(Doorbell Message)。…...

Springboot社区养老保险系统小程序
一、前言 随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱,社区养老保险系统小程序被用户普遍使用,为方…...

AI+无人机如何守护濒危物种?YOLOv8实现95%精准识别
【导读】 野生动物监测在理解和保护生态系统中发挥着至关重要的作用。然而,传统的野生动物观察方法往往耗时耗力、成本高昂且范围有限。无人机的出现为野生动物监测提供了有前景的替代方案,能够实现大范围覆盖并远程采集数据。尽管具备这些优势…...
JavaScript 数据类型详解
JavaScript 数据类型详解 JavaScript 数据类型分为 原始类型(Primitive) 和 对象类型(Object) 两大类,共 8 种(ES11): 一、原始类型(7种) 1. undefined 定…...