分析yao openapi oauth角色acl中的实现原理
结合 yao/openapi 目录下的源码,深入浅出地剖析 Yao 框架中的 OAuth、角色(Role)与 ACL(访问控制列表) 的实现机制。
可以将这套系统想象成一个高级办公大楼的门禁系统:
- OAuth: 是发卡处(验证你是谁,给你发门禁卡)。
- Role (角色): 是你的职位(经理、清洁工、访客)。
- ACL: 是那本厚厚的规则书(经理可以进金库,访客只能在大厅)。
- Permissions (权限/Scope): 是具体的动作(“开门”、“查看文件”)。
1. 核心原理:门禁是如何工作的?
在代码层面,整个流程发生在 openapi/oauth 目录中。核心逻辑链条如下:
请求拦截 (Guard):
- 代码位置:
openapi/oauth/guard.go - 原理: 这是一个“保安”。每个进入 Yao 系统接口的 HTTP 请求,都要先过这一关。它会检查请求头里的
Authorization: Bearer <Token>。 - 源码逻辑:
Guard()函数解析 Token,提取出当前用户的 ID 和 Role (角色)。
- 代码位置:
权限仲裁 (Enforce):
- 代码位置:
openapi/oauth/acl/enforce.go - 原理: 保安拿着你的角色,去查阅“规则书”。
- 核心函数:
Enforce(role, resource, method)。它判断:拥有这个role的人,能否对这个resource(API路径)执行method(GET/POST等)操作。
- 代码位置:
规则定义 (ACL & Scopes):
- 代码位置:
openapi/oauth/acl/acl.go和scope.go - 原理: 这里定义了具体的规则。Yao 采用的是 基于 Scope(作用域)的控制。角色是一组 Scope 的集合。
- 代码位置:
2. 配置:规则书长什么样?
Yao 的权限配置通常不是写死的代码,而是通过 DSL (JSON/YAML) 配置加载的。虽然源码中展示了处理逻辑,但实际使用中,你会在应用配置中看到类似结构。
根据 openapi/oauth/acl 目录下的逻辑,配置主要分为三部分:
2.1 定义资源与 Scope (Scopes Configuration)
系统首先定义了哪些动作需要什么权限标签(Scope)。
- 资源: 例如
/api/v1/chat - 动作:
POST - 所需 Scope:
chat.create
2.2 定义角色 (Role Configuration)
然后,定义“角色”拥有哪些“Scope”。
- Admin (管理员): 拥有
*(所有权限) 或chat.*,user.*。 - User (普通用户): 仅拥有
chat.create,user.profile.read。
2.3 代码中的默认加载
在 openapi/oauth/acl/acl.go 中,通常会有默认的加载逻辑。Yao 可能会从文件系统加载 acl.yao 或类似的配置文件,将其解析为内存中的 Go 结构体。
3. 使用方式:如何授权与控制
这是初学者最关心的部分:“我该怎么把一个用户变成管理员,并让他能访问 API?”
3.1 给用户授权角色 (Assign Role)
在 Yao 中,用户的角色通常存储在数据库的用户表中,或者在登录时动态计算。
- 源码线索: 查看
openapi/oauth/providers/user/目录。 - 实现方式:
- 数据库层面: 你的
user表中通常有一个role字段(例如 JSON 类型或字符串)。 - 登录颁发令牌: 当用户调用登录接口(如
openapi/user/login.go)时,系统验证密码成功。 - 生成 Token: 系统调用
openapi/oauth/token.go中的New()方法。- 关键点: 在生成 JWT Token 时,会将数据库中查到的
role字段的值,写入 Token 的payload中。 - 结果: 用户拿到的 Token 里就刻上了 "我是 Admin" 的印记。
- 关键点: 在生成 JWT Token 时,会将数据库中查到的
- 数据库层面: 你的
操作示例 (伪代码):
javascript
// 在数据库中更新用户
Process('models.user.save', {
id: 1,
name: '张三',
role: 'admin' // <--- 这里赋予角色
});3.2 角色与具体权限关联 (Mapping)
这部分通常是在 Yao 的服务启动时通过 DSL 配置完成的,但源码中体现了其匹配逻辑。
原理 (Scope Match): 在 openapi/oauth/acl/scope.go 中,有一个匹配算法。
- 如果 API 需要
users.edit权限。 - 如果用户的角色是
editor,配置中editor拥有["users.*"]。 - ACL 会进行通配符匹配:
users.*包含users.edit-> 允许通过。
3.3 在 API 中使用 (Use Guard)
如果你自己在开发自定义的 API 接口(Go 或 DSL),你需要告诉系统“这个接口受保护”。
在 DSL (apis/user.http.json) 中:
json
{
"paths": [
{
"path": "/info",
"method": "GET",
"guard": "bearer-jwt", // <--- 这里调用了 oauth.Guard
"process": "models.user.find",
"in": ["$session.user_id"]
}
]
}注意:DSL 中的 guard: "bearer-jwt" 底层就是映射到了 openapi/oauth/guard.go 的逻辑。
4. 总结:初学者流程图
如果你要实现“让小明拥有管理员权限”,你的脑海中应该有这幅图:
- 配置阶段: 确保 Yao 的 ACL 配置中定义了
admin角色,并且admin角色拥有*(所有) Scope。 - 数据阶段: 去数据库修改小明的记录,把他的
role字段改为admin。 - 登录阶段: 小明重新登录。Yao 的登录逻辑读取数据库 -> 发现他是
admin-> 把role: admin封印进 JWT Token -> 发给小明。 - 请求阶段: 小明带着 Token 访问
/api/delete-db。 - 验证阶段:
Guard看到 Token,解密出role: admin。Enforce检查:/api/delete-db需要database.delete权限。ACL判断:admin拥有所有权限,当然包含database.delete。- 放行。
5. 给开发者的建议
如果你想修改或扩展这部分功能:
- 调试: 可以在
openapi/oauth/guard.go中加日志,打印出c.Request().Header.Get("Authorization")看看收到的 Token 对不对。 - 扩展: 如果你需要更复杂的权限(比如根据部门、根据具体 ID),你需要修改
openapi/oauth/acl/enforce.go,在标准的 Role 检查之后,加入自定义的逻辑(比如 ABAC 属性访问控制)。 - MCP: 注意到目录中有
openapi/mcp和openapi/oauth/mcp.go,这意味着 Yao 的 Agent/AI 接口也复用了这套鉴权逻辑,给 AI 赋予特定的“角色”和“Scope”是目前的高级用法。