Agent越强大,权限管理就越重要。一个能调用外部API、读写数据库、执行代码的Agent,如果没有严格的访问控制,本质上就是一个拿着管理员账号到处乱跑的自动化脚本。传统的权限系统大多面向人或服务设计,而Agent有自己的特点:身份动态、行为由模型驱动、调用链路长且不确定。这就要求我们重新思考Agent场景下的访问控制怎么落地。业界目前比较成熟的做法是RBAC负责定义角色和权限边界,OPA负责策略评估和动态决策,两者组合起来正好覆盖了Agent权限管理的主要环节。

为什么Agent需要专门的访问控制方案
先看一个真实场景:一个客服Agent需要查询订单、退款、给用户发通知。如果直接把数据库账号给它,模型一旦被提示词注入攻击诱导,就可能执行删库操作。Agent的权限风险主要来自三个方面:一是模型输出不可控,工具调用的参数由LLM生成,可能被构造恶意输入;二是身份模糊,Agent往往以同一个服务身份运行,无法区分它代表哪个用户行事;三是权限蔓延,为了让Agent能干活,开发者倾向于给宽权限,出了问题才收紧。
传统RBAC系统针对的是稳定主体,比如员工、应用服务,角色和权限映射相对静态。Agent的行为却是动态的,同一个Agent在处理不同用户请求时,应该继承不同用户的权限边界。这叫代理身份问题:Agent替A用户操作时只能看A的数据,替B用户操作时权限随之切换。如果权限不跟随用户切换,就会出现水平越权,Agent拿A的上下文去访问B的订单。
所以Agent访问控制的核心诉求可以总结为:最小权限、身份传递、策略可审计、权限可动态调整。RBAC解决角色定义问题,OPA解决策略集中评估问题,这个组合正好对应这些诉求。
用RBAC为Agent设计角色模型
RBAC的核心思想是用户不直接持有权限,而是通过角色间接获得。为Agent设计角色时,建议不要按Agent的功能划分,而是按它能操作的资源域和风险等级划分。比如一个电商Agent可以拆出三个角色:订单查询员(只读订单表)、退款操作员(可写退款记录、单笔上限500元)、通知发送员(可调用消息接口)。
落地时角色定义可以直接放在数据库或配置中心,下面是一个简单的角色权限映射示例:
{
"roles": {
"agent_order_viewer": {
"resources": ["orders"],
"actions": ["read"],
"conditions": {
"scope": "assigned_user_only"
}
},
"agent_refund_operator": {
"resources": ["refunds"],
"actions": ["create", "read"],
"conditions": {
"max_amount": 500,
"require_approval_above": 500
}
},
"agent_notifier": {
"resources": ["notifications"],
"actions": ["send"],
"conditions": {
"rate_limit_per_minute": 10
}
}
}
}
注意conditions字段,这是纯RBAC模型力不从心的地方。当权限需要依赖上下文判断,比如金额大小、时间窗口、用户归属,静态的角色映射表达不了。这时候有两种选择:一是把条件硬编码进业务代码,维护成本高且无法审计;二是引入策略引擎,把条件抽象为声明式规则。OPA就是目前最主流的选择。
OPA如何做策略评估
OPA(Open Policy Agent)是一个通用策略引擎,策略用Rego语言编写,与业务代码解耦。业务系统在执行敏感操作前,把请求上下文发给OPA,OPA根据策略返回allow或deny。它的价值在于策略集中管理、可单元测试、可审计,改权限不需要重新发版业务代码。
针对上面的Agent退款场景,一条Rego策略可以这样写:
package agent.authz
# 默认拒绝,安全兜底
default allow := false
# 允许查询订单:只能查自己名下的
allow {
input.action == "read"
input.resource == "orders"
input.agent_role == "agent_order_viewer"
input.order.user_id == input.on_behalf_of
}
# 允许创建退款:金额上限控制
allow {
input.action == "create"
input.resource == "refunds"
input.agent_role == "agent_refund_operator"
input.refund.amount <= 500
}
# 超过限额的操作单独给出拒绝原因,方便前端提示人工介入
deny_msg[m] {
input.action == "create"
input.resource == "refunds"
input.refund.amount > 500
m := "退款金额超过Agent权限上限,需要人工审批"
}
这段策略里有两个关键设计。第一,on_behalf_of字段携带了Agent所代表的真实用户ID,这就是前面提到的身份传递。业务系统在构造请求时必须把原始用户身份透传进来,OPA用它做归属校验,防止水平越权。第二,default allow := false确保任何未被策略覆盖的请求都会被拒绝,这是安全系统的默认拒绝原则,比白名单遗漏导致放行要安全得多。
OPA的部署方式也很灵活。可以直接以_sidecar_模式跑在Agent服务旁边,请求走本地HTTP调用,延迟在1毫秒以内;也可以集中部署为策略服务,配合Bundle机制分发策略。对于Agent网关这种统一入口的架构,把OPA嵌入网关层是最常见的做法,所有工具调用在网关统一鉴权,Agent本身拿不到任何原始凭据。
RBAC与OPA的集成架构与选型建议
实际工程中两者的分工是这样的:RBAC负责回答这个Agent有哪些角色,OPA负责回答这次具体请求能不能放行。集成点通常在工具执行层。Agent框架(比如LangChain、自研编排引擎)在真正执行工具前,先拼装一个包含角色、资源、动作、上下文的JSON,发给OPA的/v1/data/agent/authz/allow端点,拿到true才继续执行。伪代码如下:
import requests
def call_tool(agent_role, action, resource, context):
payload = {
"agent_role": agent_role,
"action": action,
"resource": resource,
**context
}
resp = requests.post(
"http://opa:8181/v1/data/agent/authz/allow",
json={"input": payload}
)
if resp.json().get("result") is not True:
raise PermissionError("策略拒绝该操作")
# 鉴权通过后才真正执行工具
return execute_tool(action, resource, context)
选型上有几点经验值得参考。小规模系统如果权限逻辑简单,纯RBAC加数据库配置就够了,引入OPA反而增加运维负担。当出现以下信号时再上OPA:权限规则开始依赖请求上下文、多个Agent共享权限策略需要统一管理、合规审计要求能回放每一次授权决策。OPA自带决策日志能力,开启decision_logs后每次评估的输入输出都会记录下来,配合审计系统可以完整回答当时为什么放行了这笔操作。
另外提醒一个常见坑:不要把LLM的输出直接当作权限输入的一部分而不加校验。比如模型生成的工具调用参数里包含目标订单ID,这个ID必须经过OPA的归属校验,而不是信任模型给的值。提示词注入攻击的典型路径就是诱导模型生成越权参数,策略引擎是最后一道防线,必须独立于模型输出做判断。
总结
Agent访问控制的本质不是发明新机制,而是把成熟的RBAC与策略引擎组合好:RBAC定义角色边界,控制粒度粗但稳定;OPA用Rego策略处理动态条件,实现最小权限和身份传递;两者在工具执行层集成,配合默认拒绝和决策日志,构成可审计的完整链路。如果你的Agent已经接入了真实生产环境,建议先从梳理角色清单和给每个工具调用加OPA校验开始,权限收紧永远不嫌早。