硬编码角色判断会快速失控,因新增角色需修改所有接口、权限升级导致逻辑分散、测试成本高,且role字段来源不一易引发panic;应通过Authorizer接口抽象鉴权逻辑,支持RBAC/ABAC策略热替换,用map实现O(1)角色校验,并严格校验JWT的aud与scope白名单。

为什么不能在 handler 里写 if role == "admin" || role == "editor"
这种硬编码会快速失控:加个新角色就得改所有接口,权限升级(比如 editor 要能删自己文章但不能删别人)时逻辑散落在各处,测试成本飙升。更危险的是,role 字段可能来自 JWT payload、DB 查询结果或 context,类型不一致时直接 panic——比如 c.Get("role") 返回 nil 或 int,.(string) 断言就崩。
用中间件 + 接口抽象把鉴权逻辑拎出来
核心是把“谁能干啥”定义成接口,而不是一堆 if 块:
- 定义
Authorizer接口:CanAccess(ctx context.Context, resource string, action string) bool - 实现不同策略:
RBACAuthorizer查角色-权限映射表,ABACAuthorizer动态计算属性(如user.TenantID == resource.TenantID) - 中间件只调
authorizer.CanAccess(),不碰具体规则
这样换 ABAC 只需换实现,不用动路由或 handler;加租户隔离也只需改 ABACAuthorizer 里的判断条件。
多角色校验别手写 sliceContains,用 map 做 O(1) 判断
传入角色列表时,别每次遍历切片查权限——尤其当角色数 > 5 或并发高时明显拖慢。正确做法是预处理成 map:
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
立即学习“go语言免费学习笔记(深入)”;
// 初始化时构建
roleLevels := map[string]int{
"viewer": 1,
"editor": 2,
"admin": 3,
}
// 中间件里直接比大小
if roleLevels[currentRole] < requiredLevel {
http.Error(w, "forbidden", http.StatusForbidden)
return
}
注意:currentRole 必须是从 ctx.Value(userKey) 安全取出的,且已做非空和类型检查;requiredLevel 应由路由参数或注解注入,而非硬编码在中间件里。
微服务间鉴权必须带 scope 白名单,别信 Header 里的 X-Permissions
HTTP Header 可被篡改,X-Permissions: ["user:read"] 这种字段绝不能作为校验依据。真实做法是:
- 上游服务签发 JWT 时,
scope字段填最小必要权限(如["order:read:own"]) - 下游服务解析 JWT 后,用白名单精确匹配:
strings.Contains(allowedScopes, "order:read:own"),拒绝前缀匹配(防order:read:own:internal漏放) - 权限列表从配置中心加载,避免重启服务才能更新
最易被忽略的点:JWT 的 aud(audience)必须严格等于当前服务名,否则跨服务伪造 token 就成了单点风险。

















