Golang Web项目如何做权限控制_权限校验设计思路总结

10次阅读

应放在http中间件处理功能级权限,数据级权限须在业务逻辑二次校验;RBAC优于ABAC,权限码用“资源:动作”格式,JWT仅存user_id和role_ids并配合redis缓存带版本号的权限列表。

Golang Web项目如何做权限控制_权限校验设计思路总结

权限校验该放在 HTTP 中间件还是业务逻辑里?

绝大多数 golang Web 项目应把权限校验放在 HTTP middleware 层,而不是每个 handler 里重复写 if !user.HasRole("admin")。中间件统一拦截、提前拒绝,既减少冗余代码,也避免遗漏导致越权访问。

但注意:RBAC 中的「数据级权限」(例如用户只能删自己创建的文章)不能只靠中间件解决,必须在业务逻辑中二次校验。中间件适合做「功能级权限」(能否访问 /api/users),业务层负责「实例级权限」(能否删 id=123 这条记录)。

  • 中间件适合校验 rolepermission code(如 "user:delete")、JWT scope
  • 中间件不适合校验数据库关联字段(如 article.user_id == current_user.ID
  • 使用 gorilla/muxchi 时,中间件可按路由组注册,比如 adminRouter.Use(authMiddleware("admin"))

如何设计可扩展的权限模型:RBAC vs ABAC?

中小项目优先用 RBAC(基于角色的访问控制),结构清晰、易维护;ABAC(基于属性的访问控制)灵活但复杂度高,除非有明确需求(如动态策略引擎、多租户细粒度策略),否则不建议一上来就上 OPA 或自研策略 DSL。

RABC 实现关键是把「权限」抽象为字符串码("order:read""product:write"),而非硬编码角色名。角色只是权限集合的别名:

立即学习go语言免费学习笔记(深入)”;

type Role struct {     Name        string     Permissions []string `json:"permissions"` } 

// 数据库中存: // role: "editor" → permissions: ["post:read", "post:write"] // role: "viewer" → permissions: ["post:read"]

  • 避免用 role == "admin" 做判断,改用 user.HasPermission("user:delete")
  • 权限码建议用 资源:动作 格式,便于未来加命名空间(如 "tenant1:order:read"
  • 缓存用户权限时,不要只缓存角色名,缓存展开后的完整权限码列表(避免每次查角色再 JOIN 权限表)

JWT Token 里该不该放权限信息?

可以放,但必须设短过期时间(如 15–30 分钟),且服务端要支持权限变更后主动使 token 失效(通过 redis 黑名单或版本号机制)。绝不能把权限长期固化在 JWT payload 里,否则管理员删了某人权限,对方 token 还能用到过期为止。

推荐做法是:JWT 只放 user_idrole_ids(非权限码),每次请求在中间件中查缓存获取最新权限列表:

func authMiddleware(requiredPerm string) http.Handler {     return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {         userID := getIDFromJWT(r)         perms, _ := cache.Get("perms:" + strconv.Itoa(userID)) // 从 Redis 查         if !contains(perms.([]string), requiredPerm) {             http.Error(w, "forbidden", http.StatusForbidden)             return         }         next.ServeHTTP(w, r)     }) }
  • JWT 中放 role_ids 比放完整权限列表更安全:权限列表可能很长,JWT 膨胀影响传输和签名性能
  • Redis 缓存 key 建议带版本号,如 "perms:123:v2",用户权限变更时只更新版本号,旧 token 自动失效
  • 开发环境可加 debug=true 参数,返回实际校验的权限列表,方便排查为什么某个接口被拒

常见越权漏洞怎么快速发现?

最常被忽略的是 ID 类型混淆和未校验上下文。比如 GET /api/orders/{id} 接口只校验了用户是否登录,但没确认该 id 是否属于当前用户——攻击者换一个别人订单 ID 就能直接读取。

  • 所有带路径参数({id})、查询参数(?user_id=123)、请求体字段({"target_user_id": 456})的地方,都必须做归属校验
  • int 做 ID 时,注意 0 或负数是否被当作“未设置”,绕过校验(应统一用 uint 或校验 > 0)
  • 数据库查询时,不要先 select * FROM orders WHERE id = ? 再判断 owner,而应直接 WHERE id = ? AND user_id = ?
  • 测试阶段可用自动化脚本批量替换请求中的 ID 为其他用户 ID,看是否返回 200 或 403

权限不是加个中间件就完事。真正的难点永远在「这个请求里的那个 ID,到底归谁管」——这句话得在每个 handler 入口手动问一遍。

text=ZqhQzanResources