认证与授权

本页提供 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 / 混合」建议草稿;不涉及网络请求。

常见约束

              

勾选变化即更新草稿;最终策略仍以威胁模型与合规要求为准。

返回技能库 更多技能入口