Go中组合鉴权的核心是分离认证与授权:认证中间件(JWT/API Key/Basic Auth)注入用户至context,鉴权器基于context判断权限,严格区分401(未认证)与403(无权限)。

Go 中组合鉴权的核心是拆解认证与授权两层逻辑
直接在 http.HandlerFunc 里堆 if tokenValid() && hasRole() && ipInWhitelist() 是不可维护的。Go 没有内置的“多因子鉴权框架”,但标准库的中间件链(http.Handler 套接)天然适合分层:认证(who are you)走前置中间件,授权(what can you do)走后置校验或服务层判断。关键不是“怎么拼多个条件”,而是“谁负责哪一层、失败时如何短路”。
用中间件链实现认证方式可插拔
每种认证方式(JWT、API Key、Basic Auth)应封装为独立中间件,返回 http.Handler,内部只做身份识别并注入 context.Context。它们不互相依赖,也不决定是否放行——放行由统一的鉴权器决定。
- JWT 中间件解析
Authorization: Bearer xxx,验证签名和过期,成功则调用context.WithValue(ctx, userKey, user) - API Key 中间件从
X-API-Key头或 query 参数读取 key,查数据库匹配,同样注入user或匿名标识 - Basic Auth 中间件解码
Authorization: Basic xxx,校验用户名密码,注意必须走 HTTPS,且密码不应明文比对(应 bcrypt 校验) - 所有中间件都允许“无认证信息”通过(比如公开接口),此时
user为nil或anonymous,把决策权留给后续环节
鉴权器需明确区分“认证成功”与“权限足够”
认证中间件只解决“你是谁”,鉴权器(如 CanAccessResource)才回答“你能不能访问这个资源”。它接收 context.Context(含用户)、HTTP 方法、路径、甚至请求体字段(如操作目标 ID),再查策略。
- 简单场景可用 map 或 switch:对
/admin/*要求role == "admin";对/api/v1/users/:id要求user.ID == idParam - 复杂策略建议用
casbin库:定义model.conf描述 RBAC 或 ABAC 规则,加载 policy.csv 后调用e.Enforce(user.Role, path, method) - 务必检查
user是否为nil:未认证用户不能直接拒绝,而应根据接口设计决定是返回 401(需要登录)还是 403(已登录但无权) - 避免在鉴权器里重复解析 JWT 或查 DB——这些已在认证中间件做完,上下文里已有结果
常见错误:混淆认证失败与授权失败的 HTTP 状态码
这是上线后最常被前端吐槽的点。401 和 403 语义不同,混用会导致客户端无法正确重试登录或提示错误。
-
401 Unauthorized:请求缺少有效认证凭证,或凭证无效(如 JWT 过期、签名错误、API Key 不存在)。此时客户端应刷新 token 或重新登录 -
403 Forbidden:认证成功(user非 nil),但权限不足(如普通用户访问 admin 接口)。客户端不应重试认证,而应提示“无权限” - 中间件链中,认证中间件发现无效凭证应直接写
http.Error(w, "...", http.StatusUnauthorized)并 return;鉴权器发现权限不足才返回http.StatusForbidden - 如果某接口允许“游客访问”,但游客无法执行某些子操作(如点赞),这部分应在业务逻辑里判断并返回 403,而非在网关层拦截
真正麻烦的是跨域场景下,401 响应可能被浏览器静默丢弃——这时得确保预检请求(OPTIONS)不触发鉴权,且认证头白名单包含 Authorization 和 X-API-Key。











