073、Agent访问业务系统:认证与授权

发布时间:2026/9/17 1:51:28
073、Agent访问业务系统:认证与授权 073、Agent访问业务系统认证与授权昨天线上告警炸了某个Agent任务在凌晨三点批量拉取订单数据半数请求返回401日志里全是token expired。我起初以为是凭证过期时间配短了翻代码才发现问题根本不是过期——Agent在每次调用业务系统时都会重新申请一次token而业务系统的授权服务器默认做了并发token互踢。更麻烦的是这个Agent用的是某个管理员的用户token管理员改密码第二天整个Agent集体失联。这个场景很典型。Agent要访问业务系统不是简单在HTTP头里塞个Authorization就完事。普通人访问系统登录一次、浏览器帮你管session顶多弹个验证码。Agent不一样它没有眼睛去点“同意授权”没有手去输账号密码更可怕的是它可能会同时发起几十个并发请求每个请求都携带不同的上下文。认证和授权这两件事在Agent场景里被彻底撕开你得用不同的姿势去处理。先说认证。Agent访问业务系统身份到底是“Agent自己”还是“被Agent代理的那个人”很多初稿方案直接把用户token塞给Agent让Agent拿着这个token去调用业务API。别这样写后患无穷。用户token的有效期通常很短暂比如JWT可能只有15分钟Agent任务可能跑几个小时token早过期了。你可以在代码里加刷新逻辑但刷新令牌refresh_token是有状态的Agent分布在多个节点上你没法保证每次刷新都落在同一台机器而且业务系统的授权服务器不一定允许token被并发使用。我当时调试的那个系统Agent通过OAuth2授权码模式拿到了用户token然后挂在内存里复用。可业务系统的authorization server在用户下次登录时签发了新的token旧token直接进了黑名单Agent手里的token当场作废。这不是刷新逻辑能解决的这是身份语义的错误。Agent不是用户它不应该继承用户的会话生命周期。Agent应该有自己的凭证或者使用“代表用户但独立管理”的凭证。正确的姿势是区分两种模式。如果Agent只是访问自己权限范围内的资源比如定时拉取公开报表、写监控数据那就用client credentials模式Agent以独立身份申请token这个token的生命周期由应用本身控制和某个用户的密码无关。如果Agent必须替用户操作数据比如“帮我查上个月的报销单”那就用OAuth2的token exchange或者impersonation让Agent先获得一个短期代表令牌同时把真正的用户上下文放在请求头或者JWT的claim里业务系统用这个上下文做授权而不是直接信任Agent自己的身份。这里踩过坑最坑的是你图省事把用户的access token直接存进数据库然后几个Agent任务共用。结果就是一个任务刷新了token另一个任务还在用旧token数据库里存的是最新还是旧的最新并发写下去就乱套了。我的建议是token不要落库Agent启动时从secret管理服务拉一次内存在存快过期的时候用refresh_token换新的换完丢掉旧的。如果你的技术栈允许直接用短期凭证比如AWS的STS或者Kubernetes的ServiceAccountToken过期时间短到分钟级就算泄露也没啥损失。再说授权。Agent调用业务系统的API权限怎么判你不能因为Agent是机器就给它开超级管理员。Agent的可控性比人差它可能被prompt注入可能被上游数据带偏权限边界一定要窄。RBAC是底线ABAC是更优解。比如一个“订单管理Agent”它可能只需要读取订单状态和发起退款两个操作那你就在API网关层把这两个权限点配出来其他的统统deny。别给整个模块的读写权限Agent不是人人知道什么该做什么不该做Agent只知道自己被允许做什么。有一个很容易被忽略的地方授权判断应该放在业务系统侧而不是Agent侧。有的团队图快在Agent代码里判断“如果是管理员就允许调这个接口”这种判断形同虚设。Agent代码一旦被攻破判断逻辑直接被绕过。业务系统必须每个请求都做鉴权而且鉴权不能只看token里的角色还要看上下文。比如用户A的订单Agent能不能替用户B查不能。所以你要在请求中显式传递一个user_idclaim业务系统校验这个claim是否和资源归属一致。代码里写清楚# 这里踩过坑别只检查access_token有效要检查scope和subpayloaddecode_jwt(access_token)iforders:readnotinpayload[scope]:abort(403)ifpayload.get(sub)!order.owner_id:abort(403)这样即使Agent拿到了用户A的token也没法读用户B的订单。再往下说Agent的会话上下文管理也是个麻烦。人用浏览器每个标签页一个会话Agent可能是一个任务一个会话也可能一个任务里面嵌套多个子任务每个子任务需要不同的权限。这时候不要搞集合命令式的授权而是用细粒度的“工具权限”。把业务系统API封装成Agent可调用的工具每个工具有自己的权限声明。比如“查询库存”工具只允许GET且只允许访问/inventory/*“创建工单”工具要求scope包含ticket:write。Agent调度器在调用工具前先检查工具元数据里声明的权限是否匹配当前会话持有的凭证。这种设计能让授权关系一目了然审计的时候也方便。实际调试中我遇到过更诡异的问题——Agent内部缓存了用户token但业务系统用session而session存在Redis里Redis的key是用户浏览器指纹。Agent请求带的是自己的user-agent业务系统匹配不上每次请求都创建新session内存爆掉。这告诉我们Agent访问业务系统最好别依赖那些隐式上下文。业务系统要显式识别Agent比如在网关层加一个X-Agent-ID头用于区分流量来源同时确保session或token不会因为客户端特征变化而失效。安全方面除了认证授权你还得考虑传输和审计。Agent内部的调用链很长A服务调B服务B再调C每一跳都带着原始token很容易泄露。这里可以用短期票据或者内网mTLS降低被截获的风险。另外所有Agent发起的敏感操作必须写审计日志而且日志里不能只记“哪个Agent”要记录“是哪个用户触发的哪个Agent的哪次操作”。没有审计的授权就是耍流氓出了问题你都追溯不到。回到开头那个故障最后我改成client credentials模式单独给Agent建了service account权限只开了订单查询和状态更新两个scope。token用内存缓存到期前2分钟自动刷新没有并发互斥问题。管理员的用户token不再参与Agent流程管理员改密码也不会影响Agent。那个凌晨的告警再也没出现过。如果你正在设计Agent访问业务系统的方案我给你几个实在的建议。第一别把Agent当成人不要复用用户登录态单独给Agent发身份凭证。第二权限遵循最小化每个Agent能调哪些API写死在配置里代码里不要有“if 是Agent就放行”这种鬼东西。第三token管理必须考虑并发刷新用单一锁或者原子操作否则你以为修好了加个节点就炸。第四授权决定放在业务系统API入口不要放在Agent内部Agent只负责传上下文不负责做判决。第五日志要能回答三个问题谁调了代表谁调的为什么他有权调如果你说不清楚趁早改。Agent不像人它会严格按照你给的权限执行也会在权限不该生效的时候硬生生生效。把认证授权这一层做扎实你的Agent才算是真正“进入”了业务系统而不是在门口瞎撞。