/** * package middleware * 作用:JWT认证中间件 */ package middleware import ( "log" "net/http" "xk-websocket-v2/internal/utils" "github.com/gin-gonic/gin" ) /** * AbortUnauthorized * 功能:返回统一格式的401响应并终止请求。 * 说明:保持 HTTP 401 状态码(前端请求层按状态码识别认证失败并触发登出), * 响应体经 utils.ResponseWithStatus 组装——与业务接口完全同构, * 含 interface_info(result_time/ecs),不再手工构造 ApiResponse。 * 导出原因:/ws 握手鉴权(main.go)等非中间件场景也需要同样的统一 401 响应,复用避免格式漂移 */ func AbortUnauthorized(c *gin.Context, message string) { utils.ResponseWithStatus(c, http.StatusUnauthorized, utils.CodeUnauthorized, message, nil, utils.TypeError) c.Abort() } /** * JWTAuthMiddleware * * 功能:JWT认证中间件 * * 作用: * 1. 从HTTP请求头或查询参数中提取JWT Token * 2. 验证Token的有效性和过期时间 * 3. 从Token中解析出用户ID * 4. 将用户ID注入到Gin Context中,供后续处理器使用 * 5. 如果Token无效或缺失,返回401未授权错误 * * 使用场景: * - 需要用户登录才能访问的API接口 * - 需要在处理器中获取当前用户信息的接口 * * @returns gin.HandlerFunc 中间件处理函数 */ func JWTAuthMiddleware() gin.HandlerFunc { return func(c *gin.Context) { // 步骤1: 从HTTP请求头中获取Token(标准方式) token := c.GetHeader("Authorization") if token == "" { // 步骤2: 如果请求头中没有,尝试从查询参数获取(兼容旧代码) token = c.Query("token") } // 步骤3: 如果仍然没有Token,返回401未授权错误 if token == "" { AbortUnauthorized(c, "缺少认证Token") return } // 步骤4: 移除 "Bearer " 前缀(如果存在) // 标准JWT Token格式:Bearer if len(token) > 7 && token[:7] == "Bearer " { token = token[7:] } // 步骤5: 验证Token的有效性和过期时间 userID, err := utils.ValidateToken(token) if err != nil { // Token无效或已过期,返回401错误 AbortUnauthorized(c, "Token无效或已过期: "+err.Error()) return } // 调试日志:打印解析出的用户ID,帮助排查JWT问题 log.Printf("🔑 [JWT] 请求: %s %s | 解析出的用户ID: %s", c.Request.Method, c.Request.URL.Path, userID) // 步骤6: 将解析出的用户ID注入到Context中 // 后续处理器可以通过 c.Get("user_id") 获取当前用户ID c.Set("user_id", userID) c.Next() // 继续执行下一个中间件或处理器 } } /** * OptionalJWTAuthMiddleware * * 功能:可选的JWT认证中间件(不强制要求认证) * * 作用: * 1. 如果请求中提供了Token,则验证Token并注入用户ID * 2. 如果没有提供Token,则继续执行,不返回错误 * 3. 适用于既支持登录用户访问,也支持匿名用户访问的接口 * * 使用场景: * - 公开接口,但登录用户可以获取更多信息 * - 兼容旧代码,不强制要求认证 * * @returns gin.HandlerFunc 中间件处理函数 */ func OptionalJWTAuthMiddleware() gin.HandlerFunc { return func(c *gin.Context) { // 步骤1: 尝试从请求头或查询参数获取Token token := c.GetHeader("Authorization") if token == "" { token = c.Query("token") } // 步骤2: 如果提供了Token,则验证并注入用户ID if token != "" { // 移除 "Bearer " 前缀 if len(token) > 7 && token[:7] == "Bearer " { token = token[7:] } // 验证Token,如果有效则注入用户ID // 如果Token无效,不返回错误,继续执行(允许匿名访问) if userID, err := utils.ValidateToken(token); err == nil { c.Set("user_id", userID) } } // 步骤3: 继续执行,无论是否有Token c.Next() } }