Go+Gin框架JWT令牌验证中间件触发500错误问题排查
Go+Gin+JWT中间件无令牌时触发500错误问题排查与修复
问题描述
跟随教程开发Go+Gin+JWT令牌验证中间件,请求携带有效JWT Cookie时中间件工作正常;但无令牌时,虽会返回401未授权,却同时引发500服务器错误,查阅JWT官方文档后未解决。
中间件代码
package middleware import ( "fmt" "net/http" "os" "time" "github.com/fahad-md-kamal/go-jwt/initializers" "github.com/fahad-md-kamal/go-jwt/models" "github.com/gin-gonic/gin" "github.com/golang-jwt/jwt/v4" ) func RequrieAuth(c *gin.Context) { // 从请求中获取Cookie tokenString, err := c.Cookie("Authorization") if err != nil { c.AbortWithStatus(http.StatusUnauthorized) } // 解码/验证令牌 token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"]) } return []byte(os.Getenv("SECRET")), nil }) if token == nil { // fmt.Println(token.Valid) c.AbortWithStatus(http.StatusUnauthorized) } if claims, ok := token.Claims.(jwt.MapClaims); ok{ if float64(time.Now().Unix()) > claims["exp"].(float64){ c.AbortWithStatus(http.StatusUnauthorized) } // 根据令牌中的sub字段查找用户 var user models.User initializers.DB.First(&user,claims["sub"]) if user.ID == 0 { c.AbortWithStatus(http.StatusUnauthorized) } // 将用户信息附加到请求上下文 c.Set("user", user) // 继续执行后续处理 c.Next() } else { c.AbortWithStatus(http.StatusUnauthorized) } }
错误信息
2022/09/30 18:50:47 [Recovery] 2022/09/30 - 18:50:47 panic recovered: GET /validate HTTP/1.1 Host: localhost:8000 Accept: */* Accept-Encoding: gzip, deflate, br Cache-Control: no-cache Connection: keep-alive Content-Length: 102 Content-Type: application/json Postman-Token: 9950b831-aa25-4ba3-a1eb-17c0fd8db5b4 User-Agent: PostmanRuntime/7.29.2 runtime error: invalid memory address or nil pointer dereference /usr/local/opt/go/libexec/src/runtime/panic.go:260 (0x104c475) panicmem: panic(memoryError) /usr/local/opt/go/libexec/src/runtime/signal_unix.go:835 (0x104c445) sigpanic: panicmem() /Users/genex/Desktop/golang/jwt/middleware/requireAuth.go:37 (0x168016a) RequrieAuth: if claims, ok := token.Claims.(jwt.MapClaims); ok{ /Users/genex/golang/pkg/mod/github.com/gin-gonic/gin@v1.8.1/context.go:173 (0x166e361) (*Context).Next: c.handlers[c.index](c) /Users/genex/golang/pkg/mod/github.com/gin-gonic/gin@v1.8.1/recovery.go:101 (0x166e34c) CustomRecoveryWithWriter.func1: c.Next() /Users/genex/golang/pkg/mod/github.com/gin-gonic/gin@v1.8.1/context.go:173 (0x166d466) (*Context).Next: c.handlers[c.index](c) /Users/genex/golang/pkg/mod/github.com/gin-gonic/gin@v1.8.1/logger.go:240 (0x166d449) LoggerWithConfig.func1: c.Next() /Users/genex/golang/pkg/mod/github.com/gin-gonic/gin@v1.8.1/context.go:173 (0x166c530) (*Context).Next: c.handlers[c.index](c) /Users/genex/golang/pkg/mod/github.com/gin-gonic/gin@v1.8.1/gin.go:616 (0x166c198) (*Engine).handleHTTPRequest: c.Next() /Users/genex/golang/pkg/mod/github.com/gin-gonic/gin@v1.8.1/gin.go:572 (0x166bcdc) (*Engine).ServeHTTP: engine.handleHTTPRequest(c) /usr/local/opt/go/libexec/src/net/http/server.go:2947 (0x12a7dab) serverHandler.ServeHTTP: handler.ServeHTTP(rw, req) /usr/local/opt/go/libexec/src/net/http/server.go:1991 (0x12a2fc6) (*conn).serve: serverHandler{c.server}.ServeHTTP(w, w.req) /usr/local/opt/go/libexec/src/runtime/asm_amd64.s:1594 (0x1067740) goexit: BYTE $0x90 // NOP [GIN-debug] [WARNING] Headers were already written. Wanted to override status code 401 with 500 [GIN] 2022/09/30 - 18:50:47 | 500 | 6.815587ms | ::1 | GET "/validate"
问题根源与修复方案
核心问题
- 未终止函数执行:当获取Cookie失败(
err != nil)时,调用c.AbortWithStatus(http.StatusUnauthorized)后没有执行return,导致后续代码继续运行。此时tokenString为空,jwt.Parse返回的token为nil,后续访问token.Claims触发nil指针 panic。 - 未处理
jwt.Parse的错误:解析令牌失败时直接跳过错误检查,可能导致token无效或为nil。 - 未验证令牌有效性:仅检查
token是否为nil,未通过token.Valid确认令牌本身合法。
修正后的中间件代码
package middleware import ( "fmt" "net/http" "os" "time" "github.com/fahad-md-kamal/go-jwt/initializers" "github.com/fahad-md-kamal/go-jwt/models" "github.com/gin-gonic/gin" "github.com/golang-jwt/jwt/v4" ) func RequireAuth(c *gin.Context) { // 从请求中获取Cookie tokenString, err := c.Cookie("Authorization") if err != nil { c.AbortWithStatus(http.StatusUnauthorized) return // 终止函数,避免后续代码执行 } // 解码/验证令牌 token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"]) } return []byte(os.Getenv("SECRET")), nil }) // 处理解析错误或无效令牌 if err != nil || !token.Valid { c.AbortWithStatus(http.StatusUnauthorized) return } // 断言并检查Claims类型 claims, ok := token.Claims.(jwt.MapClaims) if !ok { c.AbortWithStatus(http.StatusUnauthorized) return } // 检查令牌过期时间 if float64(time.Now().Unix()) > claims["exp"].(float64) { c.AbortWithStatus(http.StatusUnauthorized) return } // 根据令牌中的sub字段查找用户 var user models.User initializers.DB.First(&user, claims["sub"]) if user.ID == 0 { c.AbortWithStatus(http.StatusUnauthorized) return } // 将用户信息附加到请求上下文 c.Set("user", user) // 继续执行后续处理 c.Next() }
关键修改点说明
- 所有
c.AbortWithStatus后添加return,确保触发拦截后立即终止函数,避免后续代码执行。 - 新增
err != nil || !token.Valid检查,覆盖解析错误和令牌无效的场景。 - 将Claims断言与分支判断拆分,逻辑更清晰,避免嵌套过深。
- 修正函数名拼写错误(原
RequrieAuth改为RequireAuth,规范命名)。
内容的提问来源于stack exchange,提问作者Fahad Md Kamal
相关产品推荐
相关产品推荐

