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

6.--JWT鉴权

为什么要 JWT 鉴权

没有鉴权的问题

假设 /api/user/info 返回用户敏感信息:

GET /api/user/info?uid=1

我们根据上篇Login实现的测试截图便可以看到,登录成功后服务器会以明文会返回大量用户信息

任何人都能查任意用户的信息
❌ 恶意用户可以 遍历 uid 拿到所有用户数据
❌ 无法知道请求是谁发的


有 JWT 鉴权后

POST /api/user/login → 返回 token

GET /api/user/info
Headers: Authorization: Bearer eyJhbGciOiJI...

✅ 只有登录后的用户才能访问
✅ 服务端通过 token 知道是谁在请求
✅ token 有时效,过期了需要重新登录


类比

场景 现实 接口
无Token 随便进景区 随便调接口
有Token 门票+身份证 token + 用户身份

业务需求

秒杀系统的核心流程:
用户登录 → 获取Token → 秒杀请求(带Token) → 验证Token → 扣库存
后续的秒杀、订单等接口都必须知道"是谁在请求",JWT 就是解决这个问题的。

一. 安装依赖并配置JWTConfig

1) 安装JWT依赖

go get -u github.com/golang-jwt/jwt/v5

2) 添加JWTConfig结构体

来到config/config.go

// JWTConfig 映射yaml文件中的jwt配置
type JWTConfig struct {Secret     string `mapstructure:"secret"` // JWT签名密钥Expire int    `mapstructure:"expire"` // JWT过期时间
}// Config 是整个项目的配置树
type Config struct {Server   ServerConfig   `mapstructure:"server"`   // 服务器相关配置Database DatabaseConfig `mapstructure:"database"` // 数据库相关配置JWT      JWTConfig      `mapstructure:"jwt"`      // (新增)JWT相关配置
}

3) 在config.yaml中更新jwt配置项

config/config.yaml

jwt:secret: "your-secret-key-change-in-production" # 替换成你自己的JWT密钥expire: 7200   # 2小时

这里如果不知道怎么生成密钥的话可以到免费JWT密钥生成器 | 安全的HS256、HS384、HS512密钥在线生成

默认的256即可

二. 添加JWT工具(生成与解析)

创建pkg/jwtx/jwt.go

package jwtximport ("time""github.com/golang-jwt/jwt/v5"
)// Claims 定义JWT的自定义声明结构体,包含用户ID和用户名
type Claims struct {Uid                  uint   `json:"uid"`      // 用户IDUsername             string `json:"username"` // 用户名jwt.RegisteredClaims        // 继承标准的JWT注册声明
}// GenerateToken 生成JWT token,接受用户ID、用户名、密钥和过期时间作为参数,返回生成的JWT字符串或错误
func GenerateToken(uid uint, username, secrect string, expire int) (string, error) {claims := Claims{Uid:      uid,Username: username,RegisteredClaims: jwt.RegisteredClaims{ // 继承标准的JWT注册声明ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Duration(expire) * time.Second),), // 设置过期时间IssuedAt: jwt.NewNumericDate(time.Now()), // 设置签发时间},}token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) // 创建一个新的JWT token,使用HS256签名方法,并将claims作为负载return token.SignedString([]byte(secrect))                 // 使用提供的密钥对token进行签名,并返回生成的JWT字符串
}// ParseToken 解析JWT token,接受JWT字符串和密钥作为参数,返回解析后的Claims结构体或错误
func ParseToken(tokenString, secrect string) (*Claims, error) {token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (interface{}, error) {return []byte(secrect), nil // 提供密钥用于验证token的签名})if err != nil {return nil, err}if claims, ok := token.Claims.(*Claims); ok && token.Valid { // 验证token的有效性,并将claims断言为自定义的Claims类型return claims, nil}return nil, jwt.ErrSignatureInvalid // 如果token无效或签名不正确,返回签名无效错误
}

三. 鉴权中间件

创建internal/middleware/auto.go

我们需要在这里实现Token的解析,并利用上方jwt.go写好的解析方法完成鉴权

package middlewareimport ("net/http""strings""github.com/Chuan81/secgo-mall/config""github.com/Chuan81/secgo-mall/pkg/jwtx""github.com/gin-gonic/gin"
)// JWTAuth 是一个Gin中间件函数,用于验证请求中的JWT token是否合法
func JWTAuth() gin.HandlerFunc {return func(c *gin.Context) {authHeader := c.GetHeader("Authorization") // 从请求头中获取Authorization字段的值// 如果Authorization字段为空,返回401 Unauthorized错误if authHeader == "" {c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"code": 401, "message": "missing token"})return}parts := strings.SplitN(authHeader, " ", 2) // 将Authorization字段的值按照空格分割成两部分,第一部分应该是"Bearer",第二部分是实际的JWT token// 如果分割后的部分数量不等于2,或者第一部分不是"Bearer",则返回401 Unauthorized错误if len(parts) != 2 || parts[0] != "Bearer" {c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"code": 401, "message": "invalid token format"})return}claims, err := jwtx.ParseToken(parts[1], config.GlobalConfig.JWT.Secret) // 调用jwtx包中的ParseToken函数,传入JWT token和配置中的JWT签名密钥进行解析// 如果解析过程中发生错误,返回401 Unauthorized错误if err != nil {c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"code": 401, "message": "invalid token: " + err.Error()})return}c.Set("uid", claims.Uid)           // 将解析后的用户ID存储在Gin的上下文中,供后续处理函数使用c.Set("username", claims.Username) // 将解析后的用户名存储在Gin的上下文中,供后续处理函数使用c.Next() // 继续处理请求,调用下一个中间件或处理函数}
}

四. Login方法的更新-生成并返回Token

1) 更新service

internal/service/user.go

我们这里要对service.Login()方法进行更新,使其能够生成JWT并返回

func Login(req *dto.LoginRequest) (*model.User, string, error) {// 根据用户名查询用户user, err := repository.GetUserByUsername(req.Username)if err != nil {return nil, "", errors.New("User not found")}// bcrypt验证密码if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(req.Password)); err != nil {return nil, "", errors.New("invalid password")}// 传参生成JWT tokentoken, err := jwtx.GenerateToken(user.Uid, user.Username, config.GlobalConfig.JWT.Secret, config.GlobalConfig.JWT.Expire)if err != nil {return nil, "", errors.New("failed to generate token")}return user, token, nil
}

2) 更新handlers

internal/api/handlers/user.go

这里接着更新对应的handlers.Login()方法

func Login(c *gin.Context) {var req dto.LoginRequest// 绑定请求参数到DTO结构体,并进行验证if err := c.ShouldBindJSON(&req); err != nil {response.Fail(c, 400, "invalid request parameters: "+err.Error())return}// 调用服务层的登录逻辑user, token, err := service.Login(&req)if err != nil {response.Fail(c, 401, "login failed: "+err.Error())return}// 登录成功,返回用户信息(不包含密码)response.Sucess(c, gin.H{"uid":      user.Uid,"username": user.Username,"token":    token,})
}

五. 鉴权路由

cmd/secgo-mall/main.go

func main() {// ... 现有代码 ...r := gin.Default()// 无需鉴权的公共路由r.POST("/api/user/register", handlers.Register)r.POST("/api/user/login", handlers.Login)// 需要鉴权的私有路由protected := r.Group("/api")protected.Use(middleware.JWTAuth()) // 使用JWT鉴权中间件{// 在这里定义需要鉴权的路由protected.GET("/user/info", func(c *gin.Context) {uid, _ := c.Get("uid")username, _ := c.Get("username")response.Sucess(c, gin.H{"uid":      uid,"username": username,})})}// ... 现有代码 ...

六. Postman 测试流程

1) 登录获取Token

POST http://localhost:8080/api/user/login
Body: { "username": "admin", "password": "admin123" }

Login_return_token

2) 带Token访问需鉴权接口

在请求头添加:

Authorization: Bearer eyJhbGciOiJIUzI1NiIsIn...
GET http://localhost:8080/api/user/info

get_user_info

http://www.jsqmd.com/news/656574/

相关文章:

  • 从零构建MinIO Java Starter:实战封装、权限控制与云原生集成
  • Display Driver Uninstaller:显卡驱动问题的终极手术刀
  • Zero123++:如何从单张图片生成一致的多视角3D内容?
  • 视频内容智能分析终极指南:用AI快速理解视频核心信息
  • 别再死记硬背OSI七层模型了!用TwinCAT TCP/IP通信实例,带你真正理解网络协议栈
  • 从Wi-Fi到5G:OFDM技术是如何成为现代无线通信‘扛把子’的?
  • 从LLM输出到可执行逻辑:用可视化AST图谱定位生成代码的3类隐性缺陷(含开源诊断工具链)
  • Material —— RBD(Houdini To UE)
  • 终极指南:如何使用Infinity构建高性能推荐系统与对话AI
  • 别再暴力匹配了!用DBoW2词袋模型为你的SLAM系统加速回环检测(附ORB-SLAM2实战代码)
  • 2026国产云端 PCB 设计工具推荐,支持多人协作,适合消费电子行业 - 品牌2026
  • AD5686R高精度DAC:从硬件选型到SPI驱动实战
  • NIS实战指南:从零搭建高效用户认证系统
  • 如何快速上手Tinymist:Typst语言服务的完整指南
  • PyTorch环境配置Jupyter Notebook后,命令启动不自动打开浏览器的排查与修复
  • element-plus中Cascader级联选择器组件的使用
  • 终极指南:如何掌握obs-websocket协议的RPC通信机制与消息格式
  • NVIDIA Profile Inspector终极指南:5个步骤彻底解决游戏性能问题
  • 2025届最火的十大AI辅助论文方案实际效果
  • 从零到一:sql_exporter实战指南
  • Symfony Cache Contracts 高级特性:元数据管理和过期控制机制
  • APK-Installer:告别臃肿模拟器,3种高效方式在Windows上安装安卓应用
  • 终极指南:如何高效使用Lin UI表单组件构建微信小程序
  • 终极指南:如何将Vulture集成到CI/CD流程中实现自动化代码清理
  • 旧版坚果手机救星:用Scrcpy+乐播投屏在Win10上复活TNT桌面(SOS 8.0以下适用)
  • 【51单片机数码管+蜂鸣器的使用】2023-6-14
  • Winhance中文版:三分钟搞定Windows系统优化与个性化定制
  • 如何使用Sverchok实现CNC加工全流程:从参数化设计到G代码生成的完整指南
  • 1--项目初始化与第一个HTTP引擎
  • Lattice Planner实战避坑指南:从Frenet坐标推导到参考线平滑,我的实车调试血泪史