HTTP中间件必须返回http.Handler或框架对应类型(如Gin的gin.HandlerFunc),本质是包装器;需用http.HandlerFunc显式转换闭包,校验token时注意Header规范化和Bearer前缀,避免耗时操作与错误响应泄露敏感信息。

中间件函数签名必须返回 http.Handler 或接受 http.HandlerFunc
Go 的 HTTP 中间件本质是“包装器”,不是独立生命周期组件。常见错误是直接写一个普通函数做鉴权,却不返回可被 http.ServeMux 或框架(如 Gin、Echo)注册的处理器类型。
正确做法是让中间件接收原始 handler,返回一个新的 handler:
func AuthMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
token := r.Header.Get("Authorization")
if token == "" || !isValidToken(token) {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
next.ServeHTTP(w, r)
})
}
- 必须用
http.HandlerFunc显式转换闭包,否则无法调用ServeHTTP - 不要在中间件里直接调用
next.ServeHTTP以外的写响应逻辑(比如重复http.Error),否则可能触发 “header already written” 错误 - 如果用 Gin,应返回
gin.HandlerFunc;Echo 则是echo.MiddlewareFunc—— 类型不兼容,不能混用
从请求中提取并校验 token 时要注意 Header 大小写和前缀
HTTP Header 名称是 case-insensitive,但 Go 的 net/http 默认将所有 Header key 转为首字母大写的格式(Authorization),而客户端可能发来 authorization 或 AUTHORIZATION。实际测试中发现某些代理或测试工具(如 curl -H 'authorization: Bearer xxx')会绕过标准解析逻辑。
- 用
r.Header.Get("Authorization")是安全的,它内部做了规范化处理 - 务必检查 Bearer 前缀:
strings.HasPrefix(token, "Bearer "),再取后半段,避免把Basic xxx或空字符串当 token 解析 - JWT 解析建议用
github.com/golang-jwt/jwt/v5,注意ParseWithClaims第三个参数必须是非 nil 函数,否则会 panic - 别在中间件里做耗时操作(如查 DB 验证 token),应先验签(
ParseUnverified),再异步查黑名单或刷新有效期
Gin 和 Echo 的注册方式差异导致中间件顺序容易出错
中间件执行顺序直接影响鉴权是否生效。Gin 使用 Use() 全局注册,Echo 用 Use() 或路由级 Group.Use(),但两者对“下一个中间件”的控制逻辑不同。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
立即学习“go语言免费学习笔记(深入)”;
- Gin 中,
router.Use(AuthMiddleware)后注册的中间件会在 Auth 之后执行;但若在某个GET("/api/user", ...)里漏掉Use(),该路由就绕过鉴权 - Echo 中,
e.Use(AuthMiddleware)对所有路由生效,但若你写了g := e.Group("/admin"); g.Use(AuthMiddleware),那/admin下的子路由才受控,/api不受影响 - 最易踩的坑:在 Gin 中把
AuthMiddleware放在router.Static()之后 —— 静态文件路由会提前匹配并终止链路,导致鉴权中间件根本没机会运行
自定义错误响应体需统一格式且避免暴露敏感信息
身份验证失败时,直接 http.Error(w, "Invalid token", http.StatusUnauthorized) 返回纯文本,前端难解析,也不符合 API 规范。
- 应该封装成 JSON 响应:
w.Header().Set("Content-Type", "application/json; charset=utf-8"),再json.NewEncoder(w).Encode(map[string]string{"error": "unauthorized"}) - 不要在错误消息里返回 token 解析失败的具体原因(如 “expired at 2024-01-01”),防止时间侧信道攻击
- 如果使用 JWT,
err.Error()可能包含 base64 解码失败等底层细节,必须捕获并映射为通用错误码,例如jwt.ErrSignatureInvalid→"invalid_signature" - 日志里记详细错误(含 trace ID),但响应体只给客户端最小必要信息
中间件看似简单,但 token 提取时机、错误覆盖范围、框架注册点这三个地方,只要一个没对齐,就会出现“有时鉴权、有时不鉴权”的诡异行为。

















