授权逻辑应封装为HTTP中间件,统一解析凭证(如JWT)、校验有效性、注入context,避免写在handler内导致重复;需显式校验exp/iat/nbf并处理时钟偏差,用策略模式支持多认证方式,且必须及时return防止后续执行。

Go HTTP Middleware 怎么写授权逻辑
授权过滤器本质是 HTTP 中间件,不是独立服务组件。Golang 没有内置“授权过滤器”类型,所有逻辑都得靠 http.Handler 或 http.HandlerFunc 包装实现。关键在于:请求进来时检查凭证(比如 JWT、API key),合法才放行,否则直接写回 401 或 403。
常见错误是把授权逻辑写在 handler 内部,导致重复、难测试、无法复用。正确做法是抽成独立函数,接收 http.Handler 返回新 http.Handler:
func AuthMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
token := r.Header.Get("Authorization")
if !isValidToken(token) {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
next.ServeHTTP(w, r)
})
}
JWT 解析怎么避免 panic 和时钟偏差
用 github.com/golang-jwt/jwt/v5 时,token.Valid() 不等于“签名校验通过 + 未过期”,它只检查 exp 字段是否过期,且默认不校验 iat、nbf,也不处理系统时钟偏差。
- 必须显式传入
jwt.WithValidTime才启用时间校验 - 用
jwt.WithClock(…)注入带偏移的时钟,例如jwt.NewClockWithOffset(time.Minute)应对客户端时间不准 -
token.Claims是any类型,强制类型断言前务必先检查token.Valid(),否则可能 panic - 别用
ParseWithClaims的裸指针参数——容易漏掉err != nil判断,建议用Parse+Verify分步
如何让中间件支持多种认证方式(API Key / JWT / Basic)
硬编码 switch-case 容易失控。推荐用策略模式:定义接口 Authenticator,每种方式实现 Authenticate(*http.Request) (string, error) 方法,返回用户 ID 或错误。
立即学习“go语言免费学习笔记(深入)”;
中间件内部按顺序尝试各实现,任一成功即停止:
func MultiAuthMiddleware(auths ...Authenticator) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var userID string
for _, a := range auths {
id, err := a.Authenticate(r)
if err == nil {
userID = id
break
}
}
if userID == "" {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
// 注入用户上下文
ctx := context.WithValue(r.Context(), "user_id", userID)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
}
注意:context.WithValue 只适合传递简单、不可变的值(如 ID),别塞结构体或指针;下游 handler 必须用 r.Context().Value("user_id") 显式取,不能依赖全局变量。
为什么 Gin / Echo 的中间件不能直接复用于 net/http
Gin 的 gin.HandlerFunc 和 Echo 的 echo.MiddlewareFunc 都封装了框架特有上下文(*gin.Context / echo.Context),和标准库 http.Handler 类型不兼容。强行转换会 panic,或导致 Request.Body 被提前读取(因为框架内部已调用 ParseForm 或 Bind)。
如果项目混合使用标准库路由和框架路由,授权逻辑必须分开实现,或者统一降级为 net/http —— 毕竟 Go 生态里真正可移植、可测、可组合的中间件,只存在于标准库抽象上。
实际开发中,最容易被忽略的是:授权失败后没及时 return,后续 handler 仍会执行;还有就是没重置 Request.Body(比如用了 io.ReadAll 但没用 bytes.NewReader 替换回去),导致下游解析 body 失败。这些细节比算法本身更常引发线上问题。


















