数据权限设计文档 (Data Authorization)
本文档说明 AuthKeystone 的权限模型如何在代码层面落地,以及各类数据的隔离边界。 适合开发者、架构师、运维理解“谁能看到什么、谁不能看到什么“。
一、权限模型总览
系统有 两层权限,它们独立运作:
┌─────────────────────────────────────────────────────────┐
│ 第一层:功能权限(RBAC — 你能做什么操作) │
│ │
│ 用户 → 角色 → 菜单(F=按钮权限) │
│ 例:admin 角色 → system:user:list(能看用户列表) │
│ 代码落地点:middleware::check_permission() │
├─────────────────────────────────────────────────────────┤
│ 第二层:数据权限(Data Scope — 你能看到哪些人的数据) │
│ │
│ 角色.data_scope 字段:self / department / all │
│ 例:数据范围=self → 只能看到自己的用户记录 │
│ 代码落地点:admin::enforce_data_scope() + list_users │
└─────────────────────────────────────────────────────────┘
两者的关系:先过功能权限(能不能做),再过数据权限(对谁做)。
例:你有 system:user:list(功能权限),但 data_scope=self,那用户列表里只有你自己。
二、第一层:功能权限(RBAC)
数据模型
users ──< user_roles >── roles ──< role_menus >── menus
│
└── data_scope (数据权限,见第三节)
- menus 表:目录(M)、菜单(C)、按钮(F)三级树。F 节点带
permission字段(如system:user:list)。 - roles 表:角色,绑定一组菜单(F 权限)。
- user_roles 表:用户-角色关联(多对多)。
权限标识规范
格式:模块:资源:操作,例如:
system:user:list— 系统模块/用户/查询system:user:create— 系统模块/用户/创建user:apikey:list— 普通用户域/密钥/查询(与 admin 的 system:apikey:list 分离,避免 UNIQUE 冲突)
代码落地
签发时:JWT 的 permissions 字段携带用户所有 F 类权限。
#![allow(unused)]
fn main() {
// auth.rs — login/issue_tokens_for_user_id
let user_permissions = self.get_user_permissions(user.id).await?;
// get_user_permissions → SELECT DISTINCT m.permission FROM role_menus...
// 结果存入 claims.permissions
}
验证时:中间件从请求提取 token → 验签 → 注入 Claims 到请求扩展。
#![allow(unused)]
fn main() {
// middleware.rs — auth_middleware
let verified = auth_service.verify_token_full(token).await?;
req.extensions_mut().insert(verified.claims); // 后续 handler 通过 Extension<Claims> 取
}
检查时:handler 调用 check_permission:
#![allow(unused)]
fn main() {
// middleware.rs
pub fn check_permission(claims: &Claims, permission: &str) -> Result<(), AppError> {
if claims.permissions.contains(&SUPER_PERMISSION.to_string()) { return Ok(()); } // * 通配
if claims.permissions.contains(&permission.to_string()) { return Ok(()); }
Err(AppError::Permission(format!("权限不足:需要 {}", permission)))
}
}
OR 逻辑(同一操作多权限域):
#![allow(unused)]
fn main() {
// middleware.rs — check_any_permission
// 用于 apikeys:admin 用 system:apikey:list,普通用户用 user:apikey:list
pub fn check_any_permission(claims: &Claims, permissions: &[&str]) -> Result<(), AppError>
}
实时刷新:verify_token_full 时从 DB 重新查权限覆盖 JWT 中的值(角色变更后立即生效,不依赖 token 重新签发):
#![allow(unused)]
fn main() {
// auth.rs — verify_token_full
let fresh_permissions = self.get_user_permissions(claims.user_id).await?;
claims.permissions = fresh_permissions.clone(); // 覆盖
}
三、第二层:数据权限(Data Scope)
三种数据范围
| data_scope | 含义 | list_users 行为 | enforce_data_scope 行为 |
|---|---|---|---|
self | 仅本人 | WHERE id = ?(当前用户) | 操作目标必须是自己 |
department | 本部门 | WHERE dept_id = (操作者的 dept_id) | 操作目标必须同部门 |
all | 全部 | 无 WHERE 限制 | 放行 |
取最宽原则:用户有多个角色时,取最宽的 data_scope(all > department > self)。
#![allow(unused)]
fn main() {
// admin.rs — get_user_data_scope
async fn get_user_data_scope(state: &AppState, user_id: i64) -> String {
// 查该用户所有角色的 data_scope
// all > department > self(取最宽)
}
}
数据范围仅作用于“用户管理“
当前数据权限只影响用户管理模块(list_users / update_user / delete_user):
all:看到/操作所有用户self:只看到/操作自己department:看到/操作同部门用户
其它模块(角色/菜单/字典/部门/审计)不受数据范围限制——它们是系统级配置,有功能权限即可访问全部。
设计理由:角色/菜单/字典是全局配置数据,没有“我的角色 vs 你的角色“概念。只有用户数据有归属(属于谁/哪个部门),需要数据范围。
代码落地
list_users(查询过滤):
#![allow(unused)]
fn main() {
// admin.rs — list_users
let data_scope = get_user_data_scope(&state, claims.user_id).await;
let rows = if data_scope == DATA_SCOPE_ALL {
// 全部用户
sqlx::query("SELECT ... FROM users ORDER BY created_at DESC LIMIT ? OFFSET ?")
} else {
// 仅本人(department 暂未在 list_users 实现,降级为 self)
sqlx::query("SELECT ... FROM users WHERE id = ? LIMIT ? OFFSET ?").bind(claims.user_id)
};
}
enforce_data_scope(单条操作拦截):
#![allow(unused)]
fn main() {
// admin.rs — update_user / delete_user 调用
async fn enforce_data_scope(state: &AppState, claims: &Claims, target_user_id: i64) -> Result<()> {
let scope = get_user_data_scope(state, claims.user_id).await;
match scope.as_str() {
"all" => Ok(()),
"department" => {
// 比较操作者与目标的 dept_id
let my_dept = sqlx::query_scalar("SELECT dept_id FROM users WHERE id = ?")...;
let target_dept = sqlx::query_scalar("SELECT dept_id FROM users WHERE id = ?")...;
if my_dept == target_dept { Ok(()) } else { Err(权限不足) }
}
_ => { // self
if claims.user_id == target_user_id { Ok(()) } else { Err(权限不足) }
}
}
}
}
四、不同来源用户的权限差异
| 来源 | 默认角色 | 默认 data_scope | 可见数据 | 内置 admin 保护 |
|---|---|---|---|---|
| 内置 admin | admin 角色(种子) | all | 全部 | ✓ 禁用/删除/去角色均被拒 |
| admin 手动创建 | 创建时指定 | 创建时指定 | 取决于角色 | 仅 admin 账户受保护 |
| 自助注册 | user 角色(种子) | self | 仅自己 | 普通用户 |
| GitHub OAuth 首次 | user 角色(种子) | self | 仅自己 | 普通用户 |
关键点:
- 注册/GitHub 用户自动获得 user 角色,data_scope=self,只能看自己的记录。
- 管理员可在用户管理里给这些用户分配更高级角色(data_scope=all 的角色)来提升权限。
- admin 账户(username=admin)有三重保护:禁止禁用、禁止删除、禁止移除管理员角色。
注册用户如何与 admin 交互(闭环)
用户注册/GitHub登录
→ 自动获得 user 角色(data_scope=self)
→ 看到自己的用户记录(用户列表只显示自己)
→ admin 在用户管理里看到该用户(admin 的 data_scope=all)
→ admin 给该用户分配新角色(如"运营",data_scope=department)
→ 该用户重新登录/刷新后生效(权限实时刷新)
→ 该用户现在能看到本部门所有用户
五、哪些数据隔离,哪些不隔离
| 数据 | 是否隔离 | 原因 |
|---|---|---|
| 用户数据 | ✓ 按 data_scope 隔离 | 用户有归属(自己/部门),需要范围控制 |
| 角色 | ✗ 不隔离 | 系统级配置,有 system:role:list 权限即可看全部 |
| 菜单 | ✗ 不隔离 | 系统级配置,全局唯一 |
| 字典 | ✗ 不隔离 | 枚举值,全局共享 |
| 部门 | ✗ 不隔离 | 组织结构,全局唯一 |
| 审计日志 | ✗ 不隔离 | 有 system:audit:list 即可看全部(安全合规需要) |
| API Key | 部分 | admin 看全部 Key;普通用户只看自己的(通过不同权限域区分) |
为什么不引入租户隔离?
当前是全局单租户模型(所有用户在同一 users 表,共享同一套配置)。原因:
- 本系统是内部中台(为微服务提供认证授权),不是面向 C 端的多租户 SaaS。
- 用户量可控,不需要租户间的数据强隔离。
- 如果未来需要多租户,可以在此基础上加
tenant_id维度(users 表已有 tenant_id 字段)。
六、已知限制与后续计划
-
department 数据范围在 list_users 未完全实现:当前 list_users 只有 all/self 两个分支,department 降级为 self。后续需补 department 分支(
WHERE dept_id = 操作者的 dept_id)。 -
数据范围只作用于用户管理:角色/菜单等系统配置不受 data_scope 影响。如果业务需要“角色也按部门隔离“,需扩展 enforce_data_scope 到这些模块。
-
API Key 没有数据范围:API Key 的权限通过
permissions数组控制,不涉及 data_scope。资源隔离通过account_key实现(多租户维度)。 -
菜单/角色无租户隔离:所有用户共享同一套菜单树和角色定义。如需“不同租户看到不同菜单“,需加 tenant_id 到 menus/roles 表。
附:权限检查代码索引
| 功能 | 代码位置 | 说明 |
|---|---|---|
| 功能权限检查 | middleware::check_permission | 单权限检查 |
| 多权限检查(OR) | middleware::check_any_permission | apikeys 等多权限域 |
| 数据范围获取 | admin::get_user_data_scope | 查用户角色的最宽 data_scope |
| 数据范围拦截 | admin::enforce_data_scope | update/delete 操作目标校验 |
| 查询过滤 | admin::list_users | 根据 data_scope 加 WHERE |
| 权限实时刷新 | auth::verify_token_full | 验签时从 DB 重查权限覆盖 JWT |
| admin 保护 | admin::update_user/delete_user | 禁止禁用/删除 admin |
| 角色移除保护 | handlers::assign_roles | 禁止移除 admin 的管理员角色 |