认证与授权
本页提供 JWT 完整验证流程、RBAC 权限检查中间件、ABAC 属性策略与常见认证漏洞的可运行代码示例,帮助在 API 与 Agent 工具链中正确落地「证明身份」与「授予能力」的边界。
认证解决「你是谁」:凭证形式(密码、MFA、OAuth token、mTLS)与颁发方需可追溯;授权解决「你能访问什么」:角色、策略、资源级权限应在服务端强制执行,而非仅靠模型自觉。
在 SKILL 中可要求 Agent 先列出受保护资源的鉴权假设(如 Bearer 范围、租户 ID),再生成调用示例;禁止在示例或日志中硬编码长期密钥。
人机协同场景下,区分「终端用户身份」与「服务主体」:Agent 若以应用身份代操,需在文档中写明委托范围与审计字段。
- 硬性项:敏感操作二次确认、最小权限、拒绝默认放行。
- 与 API 设计对齐:401/403 语义一致,错误信息不泄露账户枚举信息。
- 会话与 token:过期、轮换、吊销路径写进运维清单。
请求路径:认证 → 授权(skill-flow-block)
[ 客户端 / Agent 携带凭证(Header、Cookie、mTLS、签名)]
│
▼
[ 认证层:校验凭证、解析主体(sub、client_id、租户声明)]
│
┌────────┴────────┐
▼ ▼
[ 失败 → 401 ] [ 成功 → 授权上下文:角色 / 权限 / 属性 ]
│ │
│ ▼
│ [ 策略判定:资源 + 动作 + 环境(ABAC)或角色绑定(RBAC)]
│ │
└────────┬────────┘
▼
[ 拒绝 → 403 ] 或 [ 通过 → 业务处理 + 审计字段 ]
先认证后授权:未识别主体时返回 401;已识别但策略不允许时返回 403。Agent 生成代码时应把校验放在网关或服务内统一中间件,而不是在每个 handler 里复制粘贴。
认证与授权:JWT 验证 vs 权限检查
- 401 Unauthorized:缺凭证、过期、签名无效——客户端应换 token 或重新登录,勿与「权限不足」混用。
- 403 Forbidden:身份已知但当前动作或资源不允许——可配合细分子错误码(团队约定)但避免泄露资源是否存在。
- 反模式:用 200 +
{ "allowed": false }隐藏鉴权失败;在错误体中返回「该邮箱未注册」等可枚举信息;在提示词中要求模型「自行判断是否有权」代替服务端策略。
JWT 完整验证流程——必须按顺序执行所有检查,跳过任何一步都是漏洞:
// Node.js: JWT 正确验证流程(verify signature → check exp → check iss → check aud)
import jwt from 'jsonwebtoken';
import jwksClient from 'jwks-rsa';
const client = jwksClient({
jwksUri: 'https://auth.example.com/.well-known/jwks.json',
cache: true, cacheMaxEntries: 5, cacheMaxAge: 600000
});
async function verifyJWT(token: string): Promise<JWTPayload> {
// 步骤1: 解码 header 获取 kid,不可信任 payload 内容
const decoded = jwt.decode(token, { complete: true });
if (!decoded) throw new Error('Invalid token format');
// 步骤2: 验证签名——用 JWKS 拉取公钥,严禁用 alg:none 或对称密钥
const key = await client.getSigningKey(decoded.header.kid);
const publicKey = key.getPublicKey();
return new Promise((resolve, reject) => {
jwt.verify(token, publicKey, {
algorithms: ['RS256'], // 步骤2: 算法白名单,拒绝 HS256/none
issuer: 'https://auth.example.com', // 步骤3: 验证 iss,防跨服务 token 混用
audience: 'api.example.com', // 步骤4: 验证 aud,防 token 重用到其他服务
clockTolerance: 30, // 步骤5: exp 检查,容忍30秒时钟偏移
}, (err, payload) => {
if (err) reject(new AuthError('JWT verification failed', 401));
else resolve(payload as JWTPayload);
});
});
}
// 常见漏洞示例(❌ 不要这样做)
// ❌ 不验证签名:jwt.decode(token) — 任何人都能伪造 payload
// ❌ 不检查 exp:jwt.verify(token, key, { ignoreExpiration: true })
// ❌ 混淆身份来源:不校验 iss,A 服务的 token 被 B 服务接受
RBAC:角色-权限映射与中间件实现
基于角色的访问控制把权限打包成角色,主体通过成员关系继承。适合组织边界清晰、操作集合可枚举的系统;缺点是角色爆炸与跨租户「同角色不同可见范围」时往往需要角色 × 租户矩阵或层级角色。
// RBAC 实现:角色-权限映射表 + Express 权限检查中间件
type Permission = 'read:articles' | 'write:articles' | 'delete:articles' | 'admin:users';
type Role = 'viewer' | 'editor' | 'admin';
// 角色-权限映射表:单一事实来源,变更需审批
const ROLE_PERMISSIONS: Record<Role, Permission[]> = {
viewer: ['read:articles'],
editor: ['read:articles', 'write:articles'],
admin: ['read:articles', 'write:articles', 'delete:articles', 'admin:users'],
};
// 权限检查中间件:统一挂在路由层,而不是散落在 handler 里
function requirePermission(permission: Permission) {
return (req: Request, res: Response, next: NextFunction) => {
const userRole = req.user?.role as Role;
if (!userRole) return res.status(401).json({ error: 'Unauthenticated' });
const allowed = ROLE_PERMISSIONS[userRole]?.includes(permission) ?? false;
if (!allowed) {
// 403:身份已知但权限不足;不泄露资源是否存在
return res.status(403).json({ error: 'Insufficient permissions' });
}
next();
};
}
// 使用示例:默认拒绝,显式授权
app.delete('/articles/:id', authenticate, requirePermission('delete:articles'), handler);
- 角色命名与审计:角色变更应可追踪(谁授予、何时、何范围)。
- 默认拒绝:无角色或无绑定即拒绝,避免「新接口忘了挂策略」。
- 与 API scope 对齐:OAuth scope 与内部 RBAC 映射表写进文档,避免 scope 过宽。
ABAC:基于属性的访问控制实现
基于属性的访问控制用主体、资源、动作、环境的属性组合成策略(常配合策略引擎或 DSL)。适合资源级规则(如「仅所有者或同部门」)、动态标签与合规约束;成本是策略可读性、测试用例与性能(每条请求求值)。
// ABAC 简单实现:基于主体/资源/动作/环境属性的策略求值
interface AccessContext {
subject: { userId: string; role: string; department: string; tenantId: string };
resource: { ownerId: string; tenantId: string; classification: 'public' | 'internal' | 'confidential' };
action: 'read' | 'write' | 'delete';
environment: { ipRegion: string; mfaVerified: boolean };
}
// 策略函数:版本化、可测试,每条规则有唯一名称便于审计日志
const policies = [
{
name: 'owner-can-always-read-own',
evaluate: (ctx: AccessContext) =>
ctx.action === 'read' && ctx.subject.userId === ctx.resource.ownerId
&& ctx.subject.tenantId === ctx.resource.tenantId,
},
{
name: 'confidential-requires-mfa',
evaluate: (ctx: AccessContext) =>
ctx.resource.classification === 'confidential'
? ctx.environment.mfaVerified // 环境属性:MFA 已验证
: true, // 其他密级不受此策略限制
},
{
name: 'tenant-isolation',
evaluate: (ctx: AccessContext) =>
ctx.subject.tenantId === ctx.resource.tenantId, // 租户隔离:来自 token 声明
},
];
function checkAccess(ctx: AccessContext): { allowed: boolean; reason: string } {
for (const policy of policies) {
if (!policy.evaluate(ctx)) {
return { allowed: false, reason: policy.name }; // 默认拒绝,记录原因
}
}
return { allowed: true, reason: 'all-policies-passed' };
}
- 属性来源可信:来自 token 声明的属性须与 IdP 或内部目录一致,防客户端伪造。
- 策略版本化:变更可回滚;为关键策略准备单元/集成测试与「允许/拒绝」对照表。
- 与 RBAC 组合:常用模式是 RBAC 定粗粒度 + ABAC 补丁资源级规则。
RBAC 更顺手时
- 权限主要由岗位/职能决定
- 接口与操作集合相对稳定
- 审计要求「谁在什么角色下」即可
ABAC 更顺手时
- 规则依赖资源属性(所有者、密级、区域)
- 同一角色在不同上下文需要不同结果
- 合规策略需可配置、少改代码
API 与 Agent 工具链
工具定义应声明所需凭证类型与最小 scope;执行路径上由运行时注入短期 token,而不是让模型拼接密钥。多步委托时记录「代理主体 + 终端用户」双身份或等价的审计维度。
// Agent 工具定义:声明所需凭证类型与最小 scope
const tool = {
name: 'read_user_profile',
description: '读取用户 profile,需要 read:profile scope',
// 安全元数据:运行时注入短期 token,不由模型拼接
auth: {
type: 'bearer',
requiredScopes: ['read:profile'], // 最小权限原则
tokenSource: 'runtime-injected', // 禁止从 prompt 中读取
},
execute: async ({ userId }, { token }) => {
// 运行时提供短期 token,记录双身份审计维度
const response = await fetch(`/api/users/${userId}/profile`, {
headers: { Authorization: `Bearer ${token}` },
});
// 日志记录:决策结果 + 代理主体,不记录完整 token
auditLog({ action: 'read_profile', subject: token.sub, targetUser: userId });
return response.json();
}
};
- SKILL 要求:生成调用前列出假设的认证方式与授权检查点。
- 日志与追踪:鉴权决策结果(允许/拒绝原因码)可采样记录,勿记录完整 token。
SKILL 片段
---
name: authn-authz-pattern
description: 设计或审查 API 与 Agent 的认证与授权边界
---
# 认证步骤(JWT)
1. 验证签名:从 JWKS 端点获取公钥,算法白名单仅含 RS256/ES256
2. 检查 exp:不得设 ignoreExpiration;时钟容忍最多 30s
3. 检查 iss:必须精确匹配受信任的颁发方 URL
4. 检查 aud:必须包含本服务的 audience 标识
5. 解析 sub/claims:不信任未经签名验证的 payload 字段
# 授权步骤
6. RBAC:从单一角色-权限映射表判定,统一中间件挂载
7. ABAC:组合主体/资源/动作/环境属性,策略函数独立可测
8. 默认拒绝:无显式 allow 规则即返回 403
9. 记录决策原因码,便于审计与调试
# 安全约束
10. 401 vs 403 语义严格区分,不泄露账户枚举信息
11. Agent 工具不从 prompt 读取密钥,由运行时注入短期 token
12. 多租户:tenant_id 从 token claims 获取,不信任客户端传参
13. 日志:记录 sub + action,不记录完整 token 或密码
14. 长期密钥禁止出现在示例、注释或版本库历史中
15. 敏感操作(删除、支付)需二次鉴权或 MFA 确认
策略选型草稿(auth-page JS)
勾选约束后,在本地生成一段可贴进设计文档的「RBAC / ABAC / 混合」建议草稿;不涉及网络请求。
勾选变化即更新草稿;最终策略仍以威胁模型与合规要求为准。