Authorization & Roles(权限与角色)
这份说明用于新手快速判断:“为什么 403、为什么要写 PermissionSpec、角色怎么生效”。
1)框架权限层级(先背下来)
route permission(路由声明) -> PermissionSpec(模块声明) -> RoleGrant(主体授权) -> ProjectedPolicy(运行时投影)
路由里出现的每个字符串都必须是 resource:action,例如:
- tenant:manage
- book:write
- role_grant:grant
2)PermissionSpec 的真实字段与约束
from core.permissions import PermissionSpec
PermissionSpec(
resource="book",
action="write",
scope="tenant", # tenant / own / resource / platform
description="Create/update books",
risk_level="high", # low/normal/high/critical
)
scope合法值:tenant、own、resource、platformcreate_router的permission_scope仅支持tenant或platformcreate_router不允许public=True同时声明权限
create_router 与权限的关系(最容易错)
- 有
permissions时,如果不写permission_scope,默认就是tenant - 非
public且permissions为空时,不会进行细粒度权限校验 - 公开路由只能是:
create_router(..., public=True)- 且不能带
permissions或permission_scope
3)路由权限怎么对齐(实操)
在路由文件:
from core.base import create_router
book_read_router = create_router(
"/books",
tags=["books"],
permissions=["book:read"],
permission_scope="tenant",
)
在模块文件:
PERMISSIONS = [
PermissionSpec(resource="book", action="read", scope="tenant"),
PermissionSpec(resource="book", action="write", scope="tenant", risk_level="high"),
]
常见不一致导致 PERMISSION_DENIED:
- 路由声明了 book:write 但 PermissionSpec 没有写入
- scope 写成 platform 而路由实际是 tenant 数据
- 路由 permission_scope 与 PermissionSpec 的资源域不匹配
4)AuthorizationDecision 与 route_authorization_decision
任何有写行为的接口建议加入:
from typing import Annotated
from fastapi import Depends
from core.permissions import AuthorizationDecision, route_authorization_decision
decision: Annotated[AuthorizationDecision, Depends(route_authorization_decision)]
decision 里可用于日志/审计输出,确认该次请求实际命中的:
- tenant_id
- user_id
- resource
- action
- policy_version
5)角色配置(RoleTemplate + RoleGrant + 投影)
角色和权限在数据库层由 3 张表承载(实际字段见源码模型):
- role_templates:角色定义(scope + name + version + permissions)
- role_grants:给某个主体发放的角色
- projected_policies:投影结果(运行时生效视图)
推荐流程:先发放模板,再触发投影
- 确认角色模板和权限声明已存在(通常在部署/种子步骤维护)
- 执行角色指派
- 执行权限投影同步:
core permissions reconcile --installed-app ... --database-url "$DATABASE__URL" --repair --json
角色最常见路径(无需直接操作 RoleGrant)
POST /api/v1/platform/tenants/{tenant_id}/invitationspayload 有role_template_id- 用户完成邀请后系统会在
TENANT_MEMBER激活时尝试自动授予角色
直接 SQL 的角色排障(高级场景)
如果你是 DBA/运维,且必须离线修复角色,可以按 DB 结构做最小排查:
-- 查已有角色模板
SELECT id, scope, name, version FROM role_templates ORDER BY scope, name;
-- 查某租户角色映射
SELECT tenant_id, subject_type, subject_id, role_template_id
FROM role_grants
WHERE tenant_id = 'tenant_demo';
角色变更后为什么会“看不到生效”
- 没执行
core permissions reconcile - reconcile 未加
--repair(只读模式不会写入投影) - 缓存未清(有缓存层时重跑流程并确认
policy_version)
6)日常排障
PERMISSION_DENIED:先检查路由字符串、模板、scope、route_authorization_decisionTENANT_CONTEXT_CONFLICT:tenant_id与 header 不一致- 新增/修改角色后必须 run
reconcile - 读不到角色权限时,优先看
PermissionSpec是否和模块实际路由一致