文章目录
- 📌 技术名片
- 一、为什么 AI 特别容易把权限系统写坏?
- 二、架构规范
- 1. 先把认证和授权分开
- 2. JWT 签发必须只有一个标准实现
- 3. Token 校验也必须集中
- 4. 认证成功,不代表拥有权限
- 5. RBAC 的核心:不要直接给用户写权限判断
- 6. [miniagent](https://github.com/liupras/miniagent) 如何取得用户权限?
- 7. 超级管理员也不能到处写 `if is_admin`
- 8. Router 应该“声明权限”,而不是“实现权限”
- 9. [miniagent](https://github.com/liupras/miniagent) 的 Permission 设计
- 10. 权限查询还需要缓存
- 11. 认证、授权、业务三者应该形成清晰边界
- 三、统一鉴权有什么好处?
- 四、给 AI 写清楚架构规则
- 总结
- 开源代码
在 AI 编程里,有一类代码特别容易“越写越多、越写越乱”:
if user.role != "admin":
raise HTTPException(status_code=403)
换一个接口,AI 又写:
if "user:delete" not in user.permissions:
raise ForbiddenError()
再换一个接口:
if current_user.id != owner_id and not current_user.is_admin:
raise HTTPException(status_code=403)
每一段单独看都说得过去。但当项目里出现几十种这样的 if,权限系统实际上已经失控了。
真正成熟的做法不是“让 AI 更认真地写 if”,而是建立一套统一的:
身份认证 + 权限模型 + 权限执行机制
其中最常见的一种权限模型,就是 RBAC(Role-Based Access Control,基于角色的访问控制)。
📌 技术名片
Authentication / 身份认证
解决的是:
“你是谁?”
例如验证登录账号、JWT 令牌是否有效。
Authorization / 授权
解决的是:
“你能做什么?”
例如用户能否删除 Agent、修改知识库、管理用户。
RBAC / Role-Based Access Control / 基于角色的访问控制
不直接给每个用户写一堆权限,而是通过:
用户 → 角色 → 权限
建立统一授权关系。
例如:
张三
↓
系统管理员
↓
user:list
user:create
user:update
user:delete
而不是在几十个接口里分别判断:
if username == "zhangsan":
...
或者:
if role == "admin":
...
💡 通俗理解:鉴权就像公司门禁
想象一家公司,员工进入大楼时,第一件事是刷门禁卡。
系统需要先判断:
这张卡是真的吗?
有没有过期?
对应的是谁?
这个员工是否还在职?
这就是:
Authentication(身份认证)
身份确认以后,员工进入不同区域时还要继续判断:
普通员工能进办公区吗?
能。
普通员工能进机房吗?
不能。
运维人员能进机房吗?
能。
财务人员能打开财务系统吗?
能。
这就是:
Authorization(授权)
如果设计合理,公司不会在每一扇门旁边贴一张纸:
如果张三来了就开门
如果李四来了也开门
如果王五来了不开
如果老板来了全部打开
因为员工一多,这套规则马上失控。
更加合理的是:
员工
↓
角色
↓
权限
↓
资源
例如:
张三 → 运维人员 → server:manage
李四 → 财务人员 → finance:view
王五 → 普通员工 → office:access
这就是 RBAC。
一、为什么 AI 特别容易把权限系统写坏?
因为 AI 很擅长解决眼前的问题。
你告诉它:
给删除用户接口增加权限判断。
它最容易生成:
if not current_user.is_admin:
raise HTTPException(status_code=403)
功能实现了。
但下一次你让它:
给删除 Agent 接口增加权限判断。
它可能又生成:
if "agent:delete" not in user.permissions:
raise HTTPException(status_code=403)
再让它保护知识库:
if user.role not in ["admin", "manager"]:
raise HTTPException(status_code=403)
最终项目里可能同时存在:
is_admin
role == "admin"
role in [...]
permissions
permission_codes
user_type
is_super
六七套权限判断方式。
这就是典型的:
功能正确,架构失控。
二、架构规范
1. 先把认证和授权分开
统一鉴权体系首先要建立一个非常重要的边界:
Authentication
认证:你是谁?
↓
Authorization
授权:你能做什么?
不要把二者混成一个巨大函数,一个比较清晰的架构应该是:
Client Request
↓
Bearer Token
↓
JWT Verification
↓
Resolve User
↓
Check User Status
↓
Authenticated User ID
↓
Permission Resolution
↓
Permission Check
↓
Router
↓
Service
其中:
Bearer Token(持有者令牌)
通常放在 HTTP 请求头:
Authorization: Bearer xxxxx
而:
JWT(JSON Web Token,JSON Web 令牌)
负责证明:
这个令牌是谁签发的?
是否被修改过?
是否已经过期?
对应哪个用户?
RBAC 则负责:
这个用户拥有哪些角色?
这些角色拥有哪些权限?
当前接口需要什么权限?
两者职责完全不同。
2. JWT 签发必须只有一个标准实现
AI 项目里另一个常见问题,是 Token(令牌)签发逻辑被重复实现。
例如:
payload = {
"username": username,
"exp": datetime.utcnow() + timedelta(hours=1)
}
token = jwt.encode(payload, SECRET)
另一个地方又写:
payload = {
"sub": user.id,
"expire": ...
}
第三个地方:
jwt.encode(
{"user": username},
key,
algorithm="HS256"
)
这样很快就会出现:
字段不一样
过期时间不一样
算法不一样
签名密钥读取方式不一样
验证逻辑也不一样
因此应该给 AI 明确规定:
Token 的创建、解析和验证只能通过统一 JWT 组件完成。
在 miniagent 中,这一职责集中在:
app/core/security/jwt_auth.py
JWTAuth.create_token() 会统一生成 Payload(载荷):
payload = {
"sub": username,
"exp": expire,
"iat": datetime.now(timezone.utc),
"type": token_type
}
其中:
sub:Subject,令牌主体;exp:Expiration Time,过期时间;iat:Issued At,签发时间;type:令牌类型。
再统一调用:
jwt.encode(
payload,
self.secret_key,
algorithm=self.algorithm
)
完成签发。
这意味着 miniagent 不需要让 Login Router、Admin Router、User Router 各自重新研究 JWT 怎么生成。
3. Token 校验也必须集中
签发统一还不够,验证同样不能散落。
错误的方式是:
try:
payload = jwt.decode(...)
except:
...
然后几十个接口各写一份。
因为 Token 校验至少涉及:
签名是否合法?
是否过期?
使用什么算法?
主体字段是什么?
异常如何处理?
在 miniagent 中,JWTAuth.verify_token() 统一调用:
payload = jwt.decode(
token,
self.secret_key,
algorithms=[self.algorithm],
options={
"verify_exp": True,
"verify_signature": True
}
)
验证通过以后,再统一读取:
username = payload.get("sub")
如果 Token 过期或非法,则统一返回失败结果。
于是整个项目只需要认一个事实:
JWTAuth
├─ create_token()
└─ verify_token()
而不是:
Router A → 自己 decode
Router B → 自己 decode
Service C → 自己 decode
Middleware D → 又写一遍 decode
4. 认证成功,不代表拥有权限
这是初学者非常容易混淆的一点。JWT 验证成功,只能说明:
这个请求对应一个合法身份。
并不能说明:
这个用户什么都能做。
比如普通用户登录成功:
JWT ✅
但尝试删除另一个用户:
user:delete ❌
因此认证之后,还需要进入授权阶段。
在 miniagent 中,这部分由:
AuthPermission
集中负责。
它的 resolve_user_id() 流程是:
Token
↓
JWT verify
↓
username
↓
查询用户
↓
检查 is_active
↓
user_id
如果 Token 无效、用户不存在或账号已经被禁用,统一拒绝请求。
这个设计很重要,因为认证不能只相信 Token。
比如:
用户昨天登录
↓
取得合法 Token
↓
管理员今天禁用账号
↓
旧 Token 仍未过期
如果系统只验证 JWT 签名,这个用户理论上还能继续访问。
而 miniagent 会进一步读取数据库中的用户状态,因此禁用账号可以真正失效。
5. RBAC 的核心:不要直接给用户写权限判断
RBAC 最简单的关系是:
User
↓
Role
↓
Permission
例如:
用户:Alice
↓
角色:admin
↓
权限:
user:list
user:create
user:update
user:delete
另一个用户:
用户:Bob
↓
角色:viewer
↓
权限:
user:list
于是 Router 不再关心:
Alice 是不是管理员?
Bob 是什么角色?
Carol 属于几个角色?
Router 只关心:
这个接口需要哪个 Permission?
比如:
system:user:delete
然后让统一权限系统回答:
当前用户有没有这个权限?
6. miniagent 如何取得用户权限?
在 miniagent 的 AsyncMenuDatabase 中,用户权限不是硬编码在 Router 里,而是通过关系查询获得:
User
↓
Role
↓
Menu / Permission
对应代码中的关系查询:
select(Menu.name)
.select_from(User)
.join(User.roles)
.join(Role.menus)
.where(
User.id == user_id,
Menu.is_active.is_(True)
)
然后转换成:
set(result.scalars().all())
也就是一个权限集合。这已经形成了典型的 RBAC 思路:
User
↓
Role
↓
Resource Code
业务接口不需要知道角色表、用户角色关联表、角色菜单关联表到底怎么查询。
7. 超级管理员也不能到处写 if is_admin
超级管理员是另一个极易把权限系统写乱的地方。最危险的方式就是:
if user.username == "admin":
return True
或者:
if user.role == "superadmin":
...
然后散落在几十个业务文件里。
miniagent 采用的是统一:
SUPER_PERMISSION
如果用户属于超级角色:
return {SUPER_PERMISSION}
普通用户则返回真实的权限集合。最终所有权限判断集中成:
if SUPER_PERMISSION in perms or required in perms:
return
否则拒绝访问。
这样超级管理员规则只有一个权威实现,而不是整个项目到处出现:
if is_super:
8. Router 应该“声明权限”,而不是“实现权限”
这是整套设计最重要的一句话。
错误的 Router:
@router.delete("/users/{user_id}")
async def delete_user(
user_id: int,
request: Request
):
token = request.headers.get("Authorization")
username = jwt_auth.verify_token(token)
user = await user_db.get_user(username)
permissions = await menu_db.get_user_resource_codes(
user.id
)
if (
"system:user:delete" not in permissions
and "*" not in permissions
):
raise HTTPException(status_code=403)
return await user_service.delete(user_id)
这里 Router 同时干了:
解析 Header
验证 JWT
查用户
查权限
判断超级管理员
执行权限校验
执行业务
Router 已经不再是 Router。
更加合理的是:
@router.delete("/users/{user_id}")
async def delete_user(
user_id: int,
current_user_id: int = Depends(
Permission("system:user:delete")
)
):
return await user_service.delete(user_id)
这时业务层表达的只有:
删除用户需要
system:user:delete。
至于:
Token 怎么取?
JWT 怎么验证?
用户怎么查?
权限怎么查?
缓存怎么处理?
超级管理员怎么算?
403 怎么抛?
全部由统一鉴权系统负责。
9. miniagent 的 Permission 设计
miniagent 中的 AuthPermission.Permission 就是干这件事的。
核心流程非常清楚:
async def __call__(
self,
request: Request,
credentials: HTTPAuthorizationCredentials = Depends(
_bearer_scheme
),
) -> int:
auth = request.app.state.container.auth
user_id = await auth.resolve_user_id(
credentials.credentials
)
await auth.check(
user_id,
self._code
)
return user_id
也就是:
Bearer Token
↓
resolve_user_id()
↓
JWT 验证
↓
用户身份
↓
check()
↓
权限集合
↓
required permission
↓
允许 / 拒绝
这套逻辑由统一依赖执行,而不是业务 Router 自己实现。
10. 权限查询还需要缓存
统一鉴权之后,又会遇到一个现实问题。
如果每一个 API 请求都执行:
JWT 验证
↓
查 User
↓
查 Role
↓
查 Permission
高并发情况下会增加数据库压力,因此权限集合很适合缓存。
在 miniagent 中:
CACHE_TTL_SECONDS = 3600.0
也就是默认缓存一个小时。
查询逻辑是:
代码中:
cached = self._cache.mget_ttl(
[self._cache_key(user_id)]
)[0]
if cached is not None:
return self._decode(cached)
return await self._load_permissions(user_id)
权限发生变化时,再通过:
invalidate(user_id)
主动删除缓存。
于是统一鉴权不仅让代码更干净,也给性能优化留下了统一入口。
11. 认证、授权、业务三者应该形成清晰边界
最终可以把整个架构理解为:

miniagent 的 JWTAuth 负责统一 Token 签发和验证。
miniagent 的 AuthPermission 负责从 Token 解析用户、检查用户状态、加载并缓存权限,以及进行最终权限判断。
miniagent 的权限数据则通过 User → Role → Menu 的关系读取;超级角色统一转换为 SUPER_PERMISSION,而不是在业务代码中反复判断管理员身份。
简单来看:
这里最大的价值不是代码少了几行。
而是边界非常明确:
JWTAuth
负责“令牌”
AuthPermission
负责“认证 + 授权”
Permission
负责“声明接口权限”
Router
负责“请求编排”
Service
负责“业务逻辑”
AI 只要遵守这些边界,就不容易把权限逻辑写散。
三、统一鉴权有什么好处?
1. 安全规则只有一个实现
假设以后 JWT 算法改变。
如果项目里有 30 份:
jwt.decode(...)
你就要检查 30 个地方。
如果全部集中到:
JWTAuth.verify_token()
只需要改一处。
2. 权限逻辑真正一致
所有接口统一经过:
resolve_user_id()
↓
get_permissions()
↓
check()
于是:
禁用用户怎么处理
超级管理员怎么处理
权限不存在怎么处理
403 怎么返回
缓存怎么处理
全部一致。
3. Router 变得容易理解
看到:
Permission("system:user:delete")
开发者马上知道:
这个接口需要删除用户权限。
不用读 20 行 if 才知道究竟谁能访问。
4. 权限可以集中审计
当所有授权都经过统一:
AuthPermission.check()
以后想加入:
权限拒绝日志
安全审计
访问统计
异常行为分析
只需要在一个入口增加。
5. 更适合 AI 编程
AI 最怕的是:
项目里有五种做法,
但没人告诉它应该用哪一种。
统一鉴权相当于明确告诉 AI:
不要自己设计权限系统。
需要 Token:
找 JWTAuth。
需要当前用户:
找 CurrentUser。
需要权限:
找 Permission。
需要判断权限:
找 AuthPermission。
AI 的自由度被控制在正确的位置。
四、给 AI 写清楚架构规则
可以把下面内容直接加入项目级 AI Rules:
## 身份认证与授权
身份认证和权限控制必须统一实现。
### JWT
- 禁止在 Router、Service、Repository 中自行创建或验证 JWT;
- 所有 JWT 签发和验证必须复用项目现有 JWTAuth;
- 禁止自行设计另一套 Payload、算法或过期策略。
### 权限
- 权限模型采用 RBAC;
- 禁止在业务代码中硬编码角色判断;
- 禁止散落编写权限 if 判断;
- Router 通过现有 Permission / AuthPermission 声明权限;
- Service 不重复实现接口级权限判断;
- 超级管理员绕过规则必须集中实现。
### 职责
JWTAuth:
负责 Token。
AuthPermission:
负责认证、权限加载、缓存和权限检查。
Router:
只声明需要什么权限。
Service:
只处理业务逻辑。
提示词落地:不要让 AI “自己加个鉴权”
错误提示词:
给这个接口加一下管理员权限。
这句话的问题在于:
AI 不知道“管理员权限”在你的项目里究竟应该怎么实现。
于是它很可能写:
if user.role != "admin":
更加合理的提示词是:
请为该接口增加权限控制。
必须遵守当前项目统一鉴权架构:
1. 禁止在 Router 中手动解析 Authorization Header;
2. 禁止直接调用 jwt.decode;
3. 禁止在 Router 或 Service 中判断 role == "admin";
4. 禁止新增零散权限 if;
5. 复用已有 AuthPermission / Permission;
6. Router 只声明 required permission;
7. JWT 继续使用现有 JWTAuth;
8. 超级管理员继续使用现有 SUPER_PERMISSION;
9. 不改变现有 RBAC 数据模型;
10. 不重复实现已有权限缓存逻辑。
AI 得到这样的约束后,就不再需要“设计权限系统”。
它只需要:
接入已有权限系统。
开发新功能时,先问 AI 一个问题
以后让 AI 增加接口时,可以先要求:
在修改代码之前,请先检查:
1. 当前项目是否已有统一 JWT 实现;
2. 当前项目是否已有认证依赖;
3. 当前项目是否已有权限模型;
4. 当前项目是否已有权限声明方式;
5. 当前接口应该复用哪个已有机制。
如果已有机制,禁止重新实现。
这句话非常重要:
如果已有机制,禁止重新实现。
因为 AI 编程最大的隐患之一,就是:
旧代码已经解决了一次,
AI 又非常认真地解决第二次。
总结
统一鉴权与 RBAC 的核心并不是:
多写几个安全判断。
恰恰相反,它要求我们:
不要让安全判断散落在业务代码里。
一个健康的权限架构应该非常清楚:
JWT
解决身份凭证
Authentication
解决“你是谁”
RBAC
解决“你拥有什么权限”
Authorization
解决“你能不能做这件事”
Router
声明需要什么权限
Service
执行真正的业务
而在 AI 编程时代,还应该再加上一条硬规则:
AI 可以调用权限系统,但不允许重新发明权限系统。
不要让项目最终变成:
if user.role == "admin":
if user.is_super:
if permission in permissions:
if username == "root":
散落一地。我们真正希望看到的是:
Permission("system:user:delete")
然后所有复杂的认证与授权机制,都在统一架构背后完成。
这才是 RBAC 对 AI 编程最大的价值:
把“谁能做什么”变成统一规则,而不是让 AI 在每个业务文件里重新猜一次。
开源代码
🪐祝您好运🪐


被折叠的 条评论
为什么被折叠?



