统一鉴权:标准化 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 如何取得用户权限?

miniagentAsyncMenuDatabase 中,用户权限不是硬编码在 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认证授权架构
miniagentJWTAuth 负责统一 Token 签发和验证。

miniagentAuthPermission 负责从 Token 解析用户、检查用户状态、加载并缓存权限,以及进行最终权限判断。

miniagent 的权限数据则通过 User → Role → Menu 的关系读取;超级角色统一转换为 SUPER_PERMISSION,而不是在业务代码中反复判断管理员身份。

简单来看:

Request

Bearer Token

JWTAuth
Token 签发
签名验证
过期验证

AuthPermission
用户解析
用户状态
权限加载
权限缓存
权限检查

Permission

Router

Service

Repository

这里最大的价值不是代码少了几行。

而是边界非常明确:

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 在每个业务文件里重新猜一次。


开源代码


🪐祝您好运🪐

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值