Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

数据权限设计文档 (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 保护
内置 adminadmin 角色(种子)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 字段)。

六、已知限制与后续计划

  1. department 数据范围在 list_users 未完全实现:当前 list_users 只有 all/self 两个分支,department 降级为 self。后续需补 department 分支(WHERE dept_id = 操作者的 dept_id)。

  2. 数据范围只作用于用户管理:角色/菜单等系统配置不受 data_scope 影响。如果业务需要“角色也按部门隔离“,需扩展 enforce_data_scope 到这些模块。

  3. API Key 没有数据范围:API Key 的权限通过 permissions 数组控制,不涉及 data_scope。资源隔离通过 account_key 实现(多租户维度)。

  4. 菜单/角色无租户隔离:所有用户共享同一套菜单树和角色定义。如需“不同租户看到不同菜单“,需加 tenant_id 到 menus/roles 表。


附:权限检查代码索引

功能代码位置说明
功能权限检查middleware::check_permission单权限检查
多权限检查(OR)middleware::check_any_permissionapikeys 等多权限域
数据范围获取admin::get_user_data_scope查用户角色的最宽 data_scope
数据范围拦截admin::enforce_data_scopeupdate/delete 操作目标校验
查询过滤admin::list_users根据 data_scope 加 WHERE
权限实时刷新auth::verify_token_full验签时从 DB 重查权限覆盖 JWT
admin 保护admin::update_user/delete_user禁止禁用/删除 admin
角色移除保护handlers::assign_roles禁止移除 admin 的管理员角色