当前位置: 首页 > article >正文

【Gin-Web】Bluebell社区项目梳理2:JWT-Token认证

本文目录

  • 一、JWT
    • Header
    • Payload
    • Signature
  • 二、代码解析
    • 刷新Token
    • 鉴权中间件

一、JWT

JWT是Json Web Token的缩写。JWT本身是没有定义任何技术实现,只是定义了一种基于Token的会话管理规则,涵盖Token需要包含的标准内容和Token生成过程,特别适用于分布式站点的单点登录SSO场景。

在这里插入图片描述

在这里插入图片描述
JWT长这样,三个部分分别是头部、负载、签名。

头部和负载以Json的形式存在,就是JWT中的Json,三部分的内容分别经过了Base64编码,然后拼接成一个JWT token。

Header

头部存储了所使用的加密算法还有Token类型。比如
{ "alg":"HS2546","TYP":"jwt" }.

Payload

将Token当成是一个载体,表示Token里面装了什么,也是一个json对象。JWT规定了7个官方字段给开发者使用。

在这里插入图片描述
除了官方字段,开发者也可以指定字段和内容,比如下面的。
在这里插入图片描述
JWT默认是不加密的,任何人都可以读到,所以不要把秘密信息放在这个不分,这个JSON对象也要使用Base64URL算法转成字符串。

Signature

Signature是对前两个部分的签名,防止篡改。

首先需要指定一个秘钥,这个密钥只有服务器才知道,不能泄露给用户。然后使用Header里面指定的签名算法(默认SHA 256),按照下面的公式产生签名。

HMACSHA256(base64UrlEncode(header)+"."+base64UrlEncode(payload),secret)

JWT拥有基于Token的会话管理方式所拥有的一切优势,不依赖Cookie,使得其可以防止CSRF攻击,也能在禁用Cookie的浏览器环境中正常运行。
1、jwt基于json,非常方便解析;
2、可以在令牌中自定义丰富的内容,易扩展;
3、通过非对称加密算法及数字签名技术。

JWT的最大优势是服务端不再需要存储Session,使得服务端认证鉴权业务可以方便扩展,避免存储Session所需要引入的Redis等组件,降低了系统架构复杂度。

但这也是JWT最大的劣势,由于有效期存储在Token 中,JWT Token一旦签发,就会在有效期内一直可用(所以一般设置的时间会比较短),无法在服务端废止,当用户进行登出操作,只能依赖客户端删除掉本地存储的Token,如果需要禁用用户,单纯使用JWT就无法做到了。

二、代码解析

在这里插入图片描述

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

定义一个自定义的 JWT 声明结构体 MyClaims,用于扩展标准的 JWT 声明以满足特定需求。jwt.StandardClaims 是 JWT 包自带的结构体,包含了官方定义的标准字段(如 Issuer、ExpiresAt 等)。由于标准声明中没有包含用户 ID 和用户名等自定义信息,因此通过内嵌 jwt.StandardClaims 并添加额外字段(如 UserID 和 Username),可以将这些自定义信息嵌入到 JWT 中。这样,生成的 JWT 令牌不仅包含标准的 JWT 信息,还能携带用户相关的自定义数据,方便在后续的认证和授权过程中使用。

var mySecret = []byte("Golinie")

func keyFunc(_ *jwt.Token) (i interface{}, err error) {
	return mySecret, nil
}

参数 _ *jwt.Token 是一个占位符,表示这个函数接收一个 JWT 令牌对象,但在这个实现中并没有使用它(因此用 _ 忽略)。

函数返回两个值:
i interface{}:返回密钥,这里返回的是全局变量 mySecret。
i interface{}:表示函数返回的第一个值是一个空接口类型。空接口可以存储任何类型的值,因此可以返回任何类型的数据。

err error:返回可能发生的错误。在这个实现中,没有错误发生,因此返回 nil。

// GenToken 生成access token 和 refresh token
func GenToken(userID uint64, username string) (aToken, rToken string, err error) {
	// 创建一个我们自己的声明
	c := MyClaims{
		userID,     // 自定义字段
		"username", // 自定义字段
		jwt.StandardClaims{ // JWT规定的7个官方字段
			ExpiresAt: time.Now().Add(
				time.Duration(viper.GetInt("auth.jwt_expire")) * time.Hour).Unix(), // 过期时间
			Issuer: "bluebell", // 签发人
		},
	}
	// 加密并获得完整的编码后的字符串token
	aToken, err = jwt.NewWithClaims(jwt.SigningMethodHS256, c).SignedString(mySecret)

	// refresh token 不需要存任何自定义数据
	rToken, err = jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.StandardClaims{
		ExpiresAt: time.Now().Add(time.Second * 30).Unix(), // 过期时间
		Issuer:    "bluebell",                              // 签发人
	}).SignedString(mySecret)
	// 使用指定的secret签名并获得完整的编码后的字符串token
	return
}

代码首先定义了一个自定义的 JWT 声明结构体 MyClaims,它包含用户 ID 和用户名等自定义字段,以及 JWT 标准声明字段(如过期时间和签发人)。这些自定义字段允许在生成的 Access Token 中携带用户相关信息,方便后续的验证和授权。

通过 jwt.NewWithClaims 方法,结合自定义声明 MyClaimsHS256 签名方法,生成 Access TokenAccess Token 的过期时间通过配置文件动态获取(viper.GetInt(“auth.jwt_expire”)),并设置为从当前时间起的若干小时后过期。最终,使用全局密钥 mySecretAccess Token 进行签名并编码为字符串。

Refresh Token 的生成逻辑类似,但不需要携带自定义数据,仅使用 JWT 标准声明字段。用于在 Access Token 过期时刷新新的 Access Token。同样使用 mySecret 进行签名。

函数返回生成的 Access TokenRefresh Token,以及可能发生的错误。这两个令牌将被发送给客户端,客户端在后续请求中使用 Access Token 访问资源,当 Access Token 过期时,使用 Refresh Token 获取新的 Access Token


Access Token 是用户访问受保护资源的凭证,通常有效期较短(如几分钟到几小时)。这种设计减少了令牌被泄露后可能造成的损害,因为即使令牌被攻击者获取,它很快就会失效。用户在每次请求受保护资源时都需要携带 Access Token,因此它需要频繁地被验证和使用。

Refresh Token 的有效期通常较长(如几天到几周),用于在 Access Token 过期时刷新一个新的 Access Token。这样可以避免用户频繁重新登录,提升用户体验。Refresh Token 通常存储在客户端的更安全位置(如 HTTPOnly Cookie 或本地存储),并且不会直接用于访问受保护资源,因此即使被泄露,攻击者也无法直接利用它访问系统。

Access Token 和 Refresh Token 可以独立管理。例如,服务器可以在检测到异常行为时吊销 Refresh Token,而不会影响当前正在使用的 Access Token。

而 Refresh Token 虽然有效期长,但通常不会直接用于访问资源,且可以被服务器端更严格地管理(如限制使用次数、IP 绑定等)。

接下来看看解析Token和刷新Token的代码。

在这里插入图片描述

先看看解析Token:定义了一个名为 ParseToken 的函数,用于解析和验证 JWT 令牌。它接收一个字符串形式的 JWT 令牌 tokenString,并尝试将其解析为自定义的 MyClaims 声明结构体。函数通过调用 jwt.ParseWithClaims 方法,结合自定义的密钥函数 keyFunc 来解析令牌,并将解析后的声明存储在 claims 中。如果解析过程中出现错误,函数会返回错误;如果解析成功但令牌无效,函数会返回一个自定义错误 “invalid token”。

该函数的作用是确保传入的 JWT 令牌是有效的,并提取其中的声明信息(如用户 ID 和用户名)。通过这种方式,可以在后续的业务逻辑中安全地使用这些声明信息,例如验证用户身份或授权访问特定资源。


刷新Token

一图胜千言,看图说话。

在这里插入图片描述
所以说后端需要对外提供一个刷新Token的接口,前端需要实现一个当Access Toekn过期时自动请求刷新Token的接口获取新Access Token的拦截器。

在这里插入图片描述
首先尝试解析 Refresh Token。如果解析失败(例如 Refresh Token 无效或已过期),函数直接返回错误。

尝试解析 Access Token,并提取其中的声明信息(MyClaims)。如果解析失败,会捕获错误并尝试将其转换为 jwt.ValidationError,以便进一步处理。

如果 Access Token 的错误类型是 jwt.ValidationErrorExpired(即 Access Token 已过期),并且 Refresh Token 仍然有效(前面已经验证过),则调用 GenToken 函数生成新的 Access Token 和 Refresh Token。(返回一个token也可以,看情况自己定义)。

鉴权中间件

当涉及权限的某些接口,就需要Token验证了,比如说只有登录了之后才能进行评论。

也就是通过了鉴权,才能访问下面的接口。

为一组路由应用 JWT 认证中间件,定义多个受保护的 HTTP 接口。通过调用 middlewares.JWTAuthMiddleware(),所有定义在该中间件下的路由都将要求客户端提供有效的 JWT 令牌才能访问,从而确保这些接口的安全性。这些路由涵盖了创建帖子、投票、评论等操作,以及一些数据查询接口,例如获取帖子详情、评论列表等。
在这里插入图片描述

来看看基于JWT的认证中间件的实现。

在这里插入图片描述

这里我们可以使用post工具来模拟。
在这里插入图片描述

上面的代码首先从 HTTP 请求头的 Authorization 字段中获取令牌,检查令牌是否存在且格式是否正确(假设以“Bearer”开头)。如果令牌缺失或格式错误,中间件会返回错误响应并终止请求处理。如果令牌格式正确,它会调用 jwt.ParseToken 函数解析 JWT 令牌,并验证其有效性。如果解析失败,中间件会返回错误并终止请求;如果解析成功,它会将解析出的用户 ID 存储到 Gin 的上下文 c 中,供后续的处理函数使用,然后继续执行后续的请求处理流程。

这里对用户的ID进行了封装,也就是获取当前用户的ID,方便后续的操作。

getCurrentUserID 函数通过从上下文中获取用户 ID,并进行类型断言,确保了获取到的用户 ID 是有效的。如果用户未登录或上下文中没有用户 ID,函数会返回一个明确的错误 ErrorUserNotLogin,这有助于在业务逻辑中快速识别和处理未登录的情况。

在这里插入图片描述


http://www.kler.cn/a/558046.html

相关文章:

  • 值和引用类型在变量赋值时的区别是什么?(C#)
  • SSI用量子计算来玩AI
  • 计算机考研之数据结构:P 问题和 NP 问题
  • ok113i——交叉编译音视频动态库
  • 【AI时代】可视化训练模型工具LLaMA-Factory安装与使用
  • 【Python爬虫(50)】从0到1:打造分布式爬虫项目全攻略
  • 2025最新在GitHub上搭建个人图床,保姆级图文教程,实现图片高效管理
  • mysql之InnoDB 统计信息收集
  • 【Web前端开发精品课 HTML CSS JavaScript基础教程】第二十五章课后题答案
  • MySQL数据库——表的约束
  • 基于数据可视化+SpringBoot+安卓端的数字化施工项目计划与管理平台设计和实现
  • Infuse Pro for Mac v8.1 全能视频播放器 支持M、Intel芯片
  • Lua 面向对象
  • Vue3 前端路由配置 + .NET8 后端静态文件服务优化策略
  • 力扣——杨辉三角
  • 基于数据可视化+SpringBoot+安卓端的数字化OA公司管理平台设计和实现
  • 具有整合各亚专科医学领域知识能力的AI智能体开发纲要(2025版)
  • 模拟实现Java中的计时器
  • 边缘计算网关:圆织机设备数据洞察的 “智慧之眼”
  • 《A++ 敏捷开发》- 20 从 AI 到最佳设计