|
|
|
|
@ -1,156 +1,341 @@
|
|
|
|
|
# 鉴权与授权模块设计 (Authentication & Authorization)
|
|
|
|
|
# Auth 权限系统设计
|
|
|
|
|
|
|
|
|
|
`auth` 模块为 VBase 应用提供了灵活且强大的权限控制系统。它摒弃了隐式的“所有权猜测”,转而采用**显式授权**(ACL)和**手动鉴权**相结合的策略,以确保系统的安全性和可维护性。
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## 1. 核心鉴权机制概览
|
|
|
|
|
## 一、权限码层级
|
|
|
|
|
|
|
|
|
|
我们提供三种层级的鉴权方式,分别适用于不同的业务场景:
|
|
|
|
|
### 1.1 层级定义
|
|
|
|
|
|
|
|
|
|
| 方式 | 描述 | 适用场景 | 示例 |
|
|
|
|
|
| :--- | :--- | :--- | :--- |
|
|
|
|
|
| **全局中间件** (`Perm`) | 检查用户是否拥有某种**全局能力**(基于角色)。 | 管理后台、列表查询、无需特定资源ID的操作。 | `user:read` (查看所有用户) |
|
|
|
|
|
| **简单手动鉴权** (Manual) | 在业务代码中直接检查资源字段(如 `OwnerID`)。 | **高频/私有资源**(如订单、日志),逻辑简单且追求高性能。 | 修改自己的订单 |
|
|
|
|
|
| **复杂资源鉴权** (`PermOnResource`) | 基于数据库 ACL 表检查用户对**特定实例**的权限。 | **高价值/共享资源**(如项目、组织),需要精细控制和跨用户授权。 | 修改项目成员 |
|
|
|
|
|
```
|
|
|
|
|
层级从 1 开始计数:
|
|
|
|
|
- 奇数层(第1、3、5层):资源类型
|
|
|
|
|
- 偶数层(第2、4、6层):实例
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 1.2 示例
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
app → 第1层 (奇数) - 资源类型
|
|
|
|
|
app:vbase → 第2层 (偶数) - 实例
|
|
|
|
|
app:vbase:role → 第3层 (奇数) - 资源类型
|
|
|
|
|
app:vbase:role:admin → 第4层 (偶数) - 实例
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## 2. 基础:上下文与中间件
|
|
|
|
|
## 二、权限等级
|
|
|
|
|
|
|
|
|
|
### 2.1 认证中间件 (AuthMiddleware)
|
|
|
|
|
### 2.1 奇数层(资源类型)
|
|
|
|
|
|
|
|
|
|
认证中间件仅负责验证 JWT Token 的有效性,并将核心的用户 ID 注入到请求上下文中。为了保持高性能,它**不会**预加载用户详情、角色或组织信息。
|
|
|
|
|
| level | 二进制 | 含义 |
|
|
|
|
|
| ----- | ------ | -------------------- |
|
|
|
|
|
| 0 | 000 | 无权限 |
|
|
|
|
|
| 1 | 001 | 可创建该类型的子资源 |
|
|
|
|
|
|
|
|
|
|
### 2.2 上下文访问
|
|
|
|
|
### 2.2 偶数层(实例)
|
|
|
|
|
|
|
|
|
|
请使用 `auth` 包提供的辅助函数来访问上下文中的认证信息:
|
|
|
|
|
| level | 二进制 | 含义 |
|
|
|
|
|
| ----- | ------ | ---------------------- |
|
|
|
|
|
| 0 | 000 | 无权限 |
|
|
|
|
|
| 2 | 010 | 读取 |
|
|
|
|
|
| 4 | 100 | 写入(修改,不能删除) |
|
|
|
|
|
| 6 | 110 | 读写(读取+修改) |
|
|
|
|
|
| 7 | 111 | 管理员(完全控制:读写+删除+授权) |
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
func MyHandler(x *vigo.X) {
|
|
|
|
|
// 1. 获取当前用户 ID (核心,所有认证请求都可用)
|
|
|
|
|
userID := auth.GetUserID(x)
|
|
|
|
|
|
|
|
|
|
if userID == "" {
|
|
|
|
|
// 未登录
|
|
|
|
|
return vigo.ErrUnauthorized
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 2. 获取当前组织 ID
|
|
|
|
|
// 注意:仅当路由使用了 LoadOrg 中间件,或手动调用了 LoadOrg 后才可用
|
|
|
|
|
orgID := auth.GetOrgID(x)
|
|
|
|
|
|
|
|
|
|
// 3. 获取在当前组织中的角色列表
|
|
|
|
|
// 注意:仅当路由使用了 LoadOrg 中间件后可用
|
|
|
|
|
roles := auth.GetOrgRoles(x)
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
### 2.3 组织上下文加载 (LoadOrg)
|
|
|
|
|
## 三、检查规则
|
|
|
|
|
|
|
|
|
|
对于需要组织上下文的接口(如 `/api/orgs/{org_id}/...`),使用 `LoadOrg` 中间件按需加载组织信息。
|
|
|
|
|
### 3.1 层级与权限对应
|
|
|
|
|
|
|
|
|
|
- **行为**: 自动从 Path (`{org_id}`), Query (`?org_id=`), Header (`X-Org-ID`) 中解析组织 ID。
|
|
|
|
|
- **验证**: 检查当前用户是否为该组织的有效成员。
|
|
|
|
|
- **注入**: 将 OrgID 和用户在该组织的角色注入上下文。
|
|
|
|
|
| 权限 | level | 检查层级 | 说明 |
|
|
|
|
|
|------|-------|----------|------|
|
|
|
|
|
| 创建 | 1 | 奇数层 | 检查资源类型层 |
|
|
|
|
|
| 读取 | 2 | 偶数层 | 检查实例层 |
|
|
|
|
|
| 写入 | 4 | 偶数层 | 检查实例层 |
|
|
|
|
|
| 读写 | 6 | 偶数层 | 检查实例层 |
|
|
|
|
|
| 管理 | 7 | 偶数层 | 检查实例层 |
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
// 路由注册示例
|
|
|
|
|
// 对于需要组织上下文的接口,显式添加 LoadOrg 中间件
|
|
|
|
|
Router.Get("/{org_id}", "获取组织详情", auth.VBaseAuth.LoadOrg, auth.VBaseAuth.Perm("org:read"), get)
|
|
|
|
|
### 3.2 具体规则
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
创建资源 (level 1)
|
|
|
|
|
→ 检查当前 permissionID 对应的奇数层
|
|
|
|
|
→ 例: "app:{appID}:role" 检查 "app:{appID}:role" 层
|
|
|
|
|
|
|
|
|
|
读取/更新/删除资源 (level 2,4,6,7)
|
|
|
|
|
→ 检查当前 permissionID 对应的偶数层
|
|
|
|
|
→ 如无权限,递归向上检查父实例层
|
|
|
|
|
→ 注意:只有 Level 7 (管理员) 权限才会向下继承,Level 2,4,6 不会继承
|
|
|
|
|
→ 例: "app:{appID}:role:{roleID}" 先检查实例层,再检查 "app:{appID}"
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## 3. 场景一:全局/类别权限 (Perm 中间件)
|
|
|
|
|
## 四、权限流程示例
|
|
|
|
|
|
|
|
|
|
用于控制“谁能进入这个门”。不关心具体操作哪个数据,只关心你有没有这个资格。
|
|
|
|
|
### 场景一:用户 A 创建应用
|
|
|
|
|
|
|
|
|
|
- **原理**: 检查用户的角色(Role)是否包含指定权限。
|
|
|
|
|
- **配置**: 通常在路由定义时使用。
|
|
|
|
|
```
|
|
|
|
|
1. 用户A创建应用 "VBase"
|
|
|
|
|
2. 自动创建权限:
|
|
|
|
|
- PermissionID: "app:vbase"
|
|
|
|
|
- Level: 7 (创建者完全控制)
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
// 只有管理员 (拥有 user:delete 权限) 可以删除用户
|
|
|
|
|
Router.Delete("/{user_id}", auth.VBaseAuth.Perm("user:delete"), DeleteUser)
|
|
|
|
|
### 场景二:用户 A 邀请用户 B 加入应用
|
|
|
|
|
|
|
|
|
|
// 只有拥有 user:read 权限的人可以查看列表
|
|
|
|
|
Router.Get("/", auth.VBaseAuth.Perm("user:read"), ListUsers)
|
|
|
|
|
```
|
|
|
|
|
1. 用户A授予用户B: app:vbase level 2 (读)
|
|
|
|
|
2. 用户B权限表:
|
|
|
|
|
- app:vbase level 2
|
|
|
|
|
3. 用户B可执行:
|
|
|
|
|
- ✓ 读取 vbase 信息
|
|
|
|
|
- ✗ 修改/删除
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
### 场景三:用户 B 创建角色
|
|
|
|
|
|
|
|
|
|
## 4. 场景二:简单手动鉴权 (Manual Check)
|
|
|
|
|
```
|
|
|
|
|
前置: 用户B有 app:vbase level 2 (读),需要额外授权
|
|
|
|
|
|
|
|
|
|
对于大多数业务场景(如电商订单、个人博客文章),资源的所有权非常明确且单一。此时,直接在业务代码中判断 `UserID` 字段是最简单、最高效的方式。
|
|
|
|
|
1. 用户A授予用户B: app:vbase:role level 1 (创建角色)
|
|
|
|
|
2. 用户B创建角色 "Editor"
|
|
|
|
|
3. 自动创建权限:
|
|
|
|
|
- PermissionID: "app:vbase:role:editor"
|
|
|
|
|
- Level: 7
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
**推荐范式:**
|
|
|
|
|
## 五、Auth 接口设计
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
// 更新文章 (PATCH /articles/{id})
|
|
|
|
|
func UpdateArticle(x *vigo.X) {
|
|
|
|
|
// 1. 获取 ID
|
|
|
|
|
articleID := x.PathParams.Get("id")
|
|
|
|
|
currentUserID := auth.GetUserID(x)
|
|
|
|
|
|
|
|
|
|
// 2. 查库获取资源
|
|
|
|
|
article := db.GetArticle(articleID)
|
|
|
|
|
|
|
|
|
|
// 3. 【核心鉴权逻辑】
|
|
|
|
|
// 允许条件:(我是作者) OR (我是管理员/有特权)
|
|
|
|
|
if article.OwnerID != currentUserID {
|
|
|
|
|
// 如果不是作者,再检查是否有全局权限进行兜底
|
|
|
|
|
if !auth.VBaseAuth.CheckPerm(x.Context(), currentUserID, "", "article:update", "") {
|
|
|
|
|
return vigo.ErrForbidden
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 4. 执行更新
|
|
|
|
|
db.Save(article)
|
|
|
|
|
package auth
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
|
|
|
|
"github.com/veypi/vigo"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// ========== 权限等级 ==========
|
|
|
|
|
|
|
|
|
|
const (
|
|
|
|
|
LevelNone = 0
|
|
|
|
|
LevelCreate = 1 // 001 创建 (检查奇数层)
|
|
|
|
|
LevelRead = 2 // 010 读取 (检查偶数层)
|
|
|
|
|
LevelWrite = 4 // 100 写入 (检查偶数层)
|
|
|
|
|
LevelReadWrite = 6 // 110 读写 (检查偶数层)
|
|
|
|
|
LevelAdmin = 7 // 111 管理员 (完全控制)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// PermFunc 权限检查函数类型
|
|
|
|
|
type PermFunc func(x *vigo.X) error
|
|
|
|
|
|
|
|
|
|
// Auth 权限管理接口
|
|
|
|
|
type Auth interface {
|
|
|
|
|
// ========== 上下文 ==========
|
|
|
|
|
|
|
|
|
|
// UserID 获取当前用户ID
|
|
|
|
|
UserID(x *vigo.X) string
|
|
|
|
|
|
|
|
|
|
// ========== 登录检查 ==========
|
|
|
|
|
|
|
|
|
|
// Login 检查用户是否登录
|
|
|
|
|
Login() PermFunc
|
|
|
|
|
|
|
|
|
|
// ========== 权限检查 ==========
|
|
|
|
|
|
|
|
|
|
// Perm 检查权限
|
|
|
|
|
// code: 权限码,支持动态解析
|
|
|
|
|
// - 固定写法: "app:vbase"
|
|
|
|
|
// - 动态解析: "app:{appID}" 从 path 获取
|
|
|
|
|
// "app:{appID@query}" 从 query 获取
|
|
|
|
|
// "app:{appID@header}" 从 header 获取
|
|
|
|
|
// "app:{appID@ctx}" 从 ctx 获取
|
|
|
|
|
// level: 需要的权限等级
|
|
|
|
|
Perm(code string, level int) PermFunc
|
|
|
|
|
|
|
|
|
|
// ========== 快捷方法 ==========
|
|
|
|
|
|
|
|
|
|
// PermCreate 检查创建权限 (level 1,检查奇数层)
|
|
|
|
|
PermCreate(code string) PermFunc
|
|
|
|
|
|
|
|
|
|
// PermRead 检查读取权限 (level 2,检查偶数层)
|
|
|
|
|
PermRead(code string) PermFunc
|
|
|
|
|
|
|
|
|
|
// PermWrite 检查更新权限 (level 4,检查偶数层)
|
|
|
|
|
PermWrite(code string) PermFunc
|
|
|
|
|
|
|
|
|
|
// PermAdmin 检查管理员权限 (level 7,检查偶数层)
|
|
|
|
|
PermAdmin(code string) PermFunc
|
|
|
|
|
|
|
|
|
|
// ========== 权限授予(业务调用) ==========
|
|
|
|
|
|
|
|
|
|
// Grant 授予权限
|
|
|
|
|
// 在创建资源、被授权等业务逻辑中调用
|
|
|
|
|
// permissionID: 权限码,如 "app:vbase"
|
|
|
|
|
// level: 权限等级
|
|
|
|
|
Grant(ctx context.Context, userID, permissionID string, level int) error
|
|
|
|
|
|
|
|
|
|
// Revoke 撤销权限
|
|
|
|
|
Revoke(ctx context.Context, userID, permissionID string) error
|
|
|
|
|
|
|
|
|
|
// ========== 权限查询 ==========
|
|
|
|
|
|
|
|
|
|
// Check 检查权限 不支持动态解析
|
|
|
|
|
// permissionID: 完整的权限码,如 "app:vbase"
|
|
|
|
|
Check(ctx context.Context, userID, permissionID string, level int) bool
|
|
|
|
|
|
|
|
|
|
// ========== 资源列表查询 ==========
|
|
|
|
|
|
|
|
|
|
// ListResources 查询用户在特定资源类型下的详细权限信息
|
|
|
|
|
// 用于解决 "查询我有权限的 app 列表" 等场景
|
|
|
|
|
// userID: 用户ID
|
|
|
|
|
// resourceType: 资源类型 (奇数层),如 "app" 或 "app:{appID}:role"
|
|
|
|
|
// 返回: map[实例ID]权限等级 (如 {"vbase": 2, "other": 7})
|
|
|
|
|
ListResources(ctx context.Context, userID, resourceType string) (map[string]int, error)
|
|
|
|
|
|
|
|
|
|
// ListUsers 查询特定资源的所有协作者及其权限
|
|
|
|
|
// 用于解决 "查看这个应用有哪些成员" 等场景
|
|
|
|
|
// permissionID: 资源实例权限码,如 "app:vbase"
|
|
|
|
|
// 返回: map[用户ID]权限等级 (如 {"user1": 2, "user2": 7})
|
|
|
|
|
ListUsers(ctx context.Context, permissionID string) (map[string]int, error)
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
**优点**:
|
|
|
|
|
- **零开销**: 不需要额外的 ACL 表查询。
|
|
|
|
|
- **逻辑清晰**: 鉴权逻辑与业务逻辑紧密结合,不易出错。
|
|
|
|
|
// ========== 数据结构 ==========
|
|
|
|
|
|
|
|
|
|
// Permission 用户权限
|
|
|
|
|
type Permission struct {
|
|
|
|
|
ID string `json:"id"`
|
|
|
|
|
UserID string `json:"user_id"`
|
|
|
|
|
RoleID string `json:"role_id"`
|
|
|
|
|
PermissionID string `json:"permission_id"`
|
|
|
|
|
Level int `json:"level"`
|
|
|
|
|
CreatedAt int64 `json:"created_at"`
|
|
|
|
|
UpdatedAt int64 `json:"updated_at"`
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## 5. 场景三:复杂资源鉴权 (PermOnResource 中间件)
|
|
|
|
|
## 六、使用示例
|
|
|
|
|
|
|
|
|
|
对于像“项目协作”、“共享文档”这样需要多人协作、权限动态分配的资源,简单的字段判断就不够用了。这时我们需要引入 ACL (Access Control List)。
|
|
|
|
|
### 6.1 固定写法
|
|
|
|
|
|
|
|
|
|
### 5.1 使用方法
|
|
|
|
|
```go
|
|
|
|
|
var Router = vigo.NewRouter()
|
|
|
|
|
|
|
|
|
|
**路由配置**:
|
|
|
|
|
告诉中间件去检查 `user_permissions` 表,看当前用户对这个 `project_id` 是否有 `project:update` 权限。
|
|
|
|
|
func init() {
|
|
|
|
|
// 创建应用 - 需要系统级 app 权限
|
|
|
|
|
Router.Post("/apps", cfg.Auth.PermCreate("app"), CreateApp)
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
// 这里的 "project_id" 是 URL 路径参数的 key
|
|
|
|
|
Router.Patch("/{project_id}", auth.VBaseAuth.PermOnResource("project:update", "project_id"), UpdateProject)
|
|
|
|
|
// 超级管理员接口
|
|
|
|
|
Router.Get("/admin/users", cfg.Auth.PermAdmin("*"), AdminListUsers)
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 5.2 授权 (Grant)
|
|
|
|
|
### 6.2 动态解析
|
|
|
|
|
|
|
|
|
|
在创建资源时,必须**显式**授予创建者权限。
|
|
|
|
|
```go
|
|
|
|
|
func init() {
|
|
|
|
|
// 从路径参数获取 appID (默认)
|
|
|
|
|
// GET /apps/{appID}
|
|
|
|
|
Router.Get("/apps/{appID}", cfg.Auth.PermRead("app:{appID}"), GetApp)
|
|
|
|
|
|
|
|
|
|
// 从 query 参数获取
|
|
|
|
|
// GET /apps?appID=xxx
|
|
|
|
|
Router.Get("/apps", cfg.Auth.PermRead("app:{appID@query}"), GetApp)
|
|
|
|
|
|
|
|
|
|
// 多层嵌套
|
|
|
|
|
// GET /apps/{appID}/roles/{roleID}
|
|
|
|
|
Router.Get("/apps/{appID}/roles/{roleID}",
|
|
|
|
|
cfg.Auth.PermRead("app:{appID}:role:{roleID}"),
|
|
|
|
|
GetRole,
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 6.3 完整示例
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
func CreateProject(x *vigo.X) {
|
|
|
|
|
// ... 创建项目逻辑 ...
|
|
|
|
|
project := db.CreateProject(...)
|
|
|
|
|
var Router = vigo.NewRouter().Use(cfg.Auth.Login())
|
|
|
|
|
|
|
|
|
|
func init() {
|
|
|
|
|
// 创建应用 - 系统级权限
|
|
|
|
|
Router.Post("/apps", cfg.Auth.PermCreate("app"), CreateApp)
|
|
|
|
|
|
|
|
|
|
// 【关键】赋予创建者对该资源的全部权限
|
|
|
|
|
// 权限ID "project:*" 表示对该项目的所有操作权限
|
|
|
|
|
auth.VBaseAuth.GrantResourcePerm(x.Context(), currentUserID, orgID, "project:*", project.ID)
|
|
|
|
|
// 列出我的应用 - 只需登录
|
|
|
|
|
Router.Get("/apps", ListMyApps)
|
|
|
|
|
|
|
|
|
|
// 应用操作 - 从路径获取
|
|
|
|
|
Router.Get("/apps/{appID}", cfg.Auth.PermRead("app:{appID}"), GetApp)
|
|
|
|
|
Router.Put("/apps/{appID}", cfg.Auth.PermWrite("app:{appID}"), UpdateApp)
|
|
|
|
|
Router.Delete("/apps/{appID}", cfg.Auth.PermAdmin("app:{appID}"), DeleteApp)
|
|
|
|
|
|
|
|
|
|
// 角色操作 - 嵌套资源
|
|
|
|
|
Router.Post("/apps/{appID}/roles", cfg.Auth.PermCreate("app:{appID}:role"), CreateRole)
|
|
|
|
|
Router.Get("/apps/{appID}/roles/{roleID}", cfg.Auth.PermRead("app:{appID}:role:{roleID}"), GetRole)
|
|
|
|
|
Router.Put("/apps/{appID}/roles/{roleID}", cfg.Auth.PermWrite("app:{appID}:role:{roleID}"), UpdateRole)
|
|
|
|
|
Router.Delete("/apps/{appID}/roles/{roleID}", cfg.Auth.PermAdmin("app:{appID}:role:{roleID}"), DeleteRole)
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 6.4 动态解析规则
|
|
|
|
|
|
|
|
|
|
| 语法 | 来源 | 示例 |
|
|
|
|
|
|------|------|------|
|
|
|
|
|
| `{key}` | path 参数 | `{appID}` |
|
|
|
|
|
| `{key@query}` | query 参数 | `{appID@query}` |
|
|
|
|
|
| `{key@header}` | header | `{appID@header}` |
|
|
|
|
|
| `{key@ctx}` | context | `{appID@ctx}` |
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## 6. 设计决策记录 (ADR)
|
|
|
|
|
## 七、接口说明
|
|
|
|
|
|
|
|
|
|
### 6.1 废弃隐式所有权推断 (PermWithOwner)
|
|
|
|
|
我们移除了曾尝试自动推断资源所有权的 `PermWithOwner` 中间件。因为在中间件层无法准确知道资源的 `OwnerID` 字段名,也无法处理复杂的“多所有者”场景,这导致了安全漏洞和难以调试的错误。现在我们推荐使用 **Manual Check** 模式。
|
|
|
|
|
### 7.1 业务调用 vs 管理端
|
|
|
|
|
|
|
|
|
|
### 6.2 上下文精简
|
|
|
|
|
为了减少内存占用和数据库压力,Request Context (`vigo.X`) 中现在**仅**包含最核心的 `UserID`。其他的非核心信息(如组织信息、详细的用户资料)都改为**按需加载**(On-Demand)或通过特定中间件(如 `LoadOrg`)加载。
|
|
|
|
|
此接口是**业务层**调用,用于:
|
|
|
|
|
- 创建资源时自动授予权限
|
|
|
|
|
- 业务逻辑中检查权限
|
|
|
|
|
|
|
|
|
|
**管理端**(如权限管理后台)可以通过直接操作数据库实现批量管理。
|
|
|
|
|
|
|
|
|
|
### 7.2 Grant 调用时机
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
// 创建应用时
|
|
|
|
|
func CreateApp(x *vigo.X, req *CreateAppReq) (*AppResp, error) {
|
|
|
|
|
app := models.App{Name: req.Name}
|
|
|
|
|
db.Create(&app)
|
|
|
|
|
|
|
|
|
|
// 授予创建者完全控制权限
|
|
|
|
|
cfg.Auth.Grant(x.Context(), userID, "app:"+app.ID, auth.LevelAdmin)
|
|
|
|
|
|
|
|
|
|
return &AppResp{App: app}, nil
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 6.3 组织信息解耦
|
|
|
|
|
组织信息的获取逻辑已从核心 `AuthMiddleware` 中剥离,移至 `LoadOrg` 中间件。这意味着不涉及组织业务的接口(如个人资料、系统设置)不再承担加载组织信息的额外开销。
|
|
|
|
|
### 7.3 列表与搜索接口设计
|
|
|
|
|
|
|
|
|
|
对于资源列表(List)或搜索接口,推荐以下设计模式:
|
|
|
|
|
|
|
|
|
|
1. **全量管理接口**(如后台管理系统):
|
|
|
|
|
- 使用 `PermAdmin("*")` 或 `PermAdmin("app:*")`。
|
|
|
|
|
- 这类接口返回所有数据,必须严格控制权限。
|
|
|
|
|
|
|
|
|
|
2. **用户侧列表/搜索**(如“我的应用”):
|
|
|
|
|
- **方式一(仅所有者)**:
|
|
|
|
|
- 使用 `Login()` 确保登录。
|
|
|
|
|
- 业务层:`db.Where("owner_id = ?", userID).Find(&apps)`
|
|
|
|
|
- **方式二(协作模式 - 使用 ListResources)**:
|
|
|
|
|
- 调用 Auth 接口获取有权限的 ID 列表。
|
|
|
|
|
- `perms, _ := auth.ListResources(ctx, userID, "app")`
|
|
|
|
|
- `ids := keys(perms)`
|
|
|
|
|
- 业务层:`db.Where("id IN ?", ids).Find(&apps)`
|
|
|
|
|
- **方式三(混合模式)**:
|
|
|
|
|
- 同时查询 owner_id 和 授权列表。
|
|
|
|
|
- `perms, _ := auth.ListResources(...)`
|
|
|
|
|
- `ids := keys(perms)`
|
|
|
|
|
- `db.Where("owner_id = ? OR id IN ?", userID, ids).Find(&apps)`
|
|
|
|
|
|