关于 api-security-best-practices
面向 API 开发与审查的安全实践指南,覆盖身份验证、资源授权、输入校验、滥用控制和日志。强调按实际应用的身份与数据模型验证边界,并明确示例和检查不等同于已完成的审计。
主要功能
- 01验证令牌签名及声明
- 02按用户和租户授权资源
- 03校验输入并限制请求体
- 04设计速率限制与滥用控制
- 05安全处理密码、密钥和日志
适用场景
- 01审查受保护的 API 端点
- 02排查跨用户或跨租户访问
- 03加固令牌刷新流程
- 04验证输入和限流失败场景
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ sickn33/ agentic-awesome-skills --skill api-security-best-practices安装后在 Claude Code 中输入/api-security-best-practices确认已加载。
详细介绍
API 安全最佳实践
从调用方身份到授权、经过验证的输入、存储和可观测响应,全面审视请求边界。保留应用实际的 身份提供方和数据模型,而不是引入第二套认证系统。
何时使用
在添加受保护端点、审查对象访问、替换宽松的请求解析或调查 API 滥用路径时使用。对于具体的 缺陷,从出问题的路由及其调用方入手;不要部署无关的安全基础设施。
输入与前置条件
记录路由、调用方/租户模型、身份提供方、令牌契约、运行时和已锁定的依赖版本、数据库 schema、代理拓扑以及已授权的测试范围。在测试环境中使用合成身份。现有任务授权继续沿用; 生产环境扫描、账户写入和消息发送需要各自独立的授权。下面的 Node 示例是 Express、 jsonwebtoken 和 Zod 的集成草图;应用/数据库适配器是有意重命名的,并非作为完整可运行的 服务呈现。集成前请根据已安装版本确认 API。
1. 验证确切的令牌契约
优先使用既有的提供方/会话中间件。当服务自行持有 HMAC JWT 契约时,要求使用强大的服务端 自有密钥、固定算法、确切的 issuer 和 audience,以及必需的运行时声明。在签名验证之前,不要 从已解码的令牌推断权限。绝不接受调用方自行选择的验证算法。
const jwt = require('jsonwebtoken');
// Illustrative first-party access-token contract; not a third-party OAuth adapter.
const ACCESS_POLICY = {
algorithms: ['HS256'], issuer: 'example-auth', audience: 'example-api'
};
function verifyAccessToken(token, signingKey) {
const claims = jwt.verify(token, signingKey, ACCESS_POLICY);
if (!claims || typeof claims !== 'object' ||
typeof claims.sub !== 'string' || !claims.sub ||
typeof claims.tenantId !== 'string' || !claims.tenantId ||
!Number.isSafeInteger(claims.exp) || !Number.isSafeInteger(claims.iat) ||
claims.exp <= claims.iat) {
throw new Error('Invalid access claims');
}
return { subject: claims.sub, tenantId: claims.tenantId };
}
function readBearer(header) {
if (typeof header !== 'string' || header.length > 8192) return null;
const match = /^Bearer ([A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+)$/i.exec(header);
return match ? match[1] : null;
}
签发访问令牌时使用相同的 issuer/audience/algorithm 以及一个应用批准的较短有效期。将验证 失败作为通用的 401 处理,不回显令牌或异常。仅凭过期并不能撤销令牌;根据实际威胁模型定义 撤销机制或短生命周期会话。使用非对称提供方密钥的服务需要提供方的 discovery/JWKS 验证和 密钥轮换策略,而不是这个 HMAC 示例。绝不要将访问令牌复用为刷新令牌。
刷新会话
使用提供方支持的会话流程或服务端不透明刷新设计:仅存储摘要、过期时间、用户/会话族和撤销 状态。在一个原子事务中消费旧的活跃令牌并创建替换令牌。并发复用不得签发两个后继令牌; 按已定义的复用处理方式撤销受影响的会话族。签发新的访问令牌时检查当前用户状态和权限。将 刷新绑定到预期的客户端/会话,并保护基于 cookie 的请求免受 CSRF 攻击。不要记录令牌、不要 将其以明文存储在数据库中,也不要通过 URL 返回刷新令牌。在宣布流程完成之前,测试同时刷新、 过期、重放、撤销和事务失败。此处未捆绑数据库事务适配器。
2. 授权资源与操作
认证识别调用方;授权决定针对某个对象和租户的确切操作。角色名称不会自动授予跨租户访问。 在数据库变更中应用所有者/租户谓词,以避免检查后写入的竞态,并将可写属性加入允许列表。 根据产品的资源披露策略一致地使用 404/403。
// Prisma-style sketch; id and tenant types must match your actual schema.
async function deleteOwnedPost(prisma, postId, principal) {
const result = await prisma.post.deleteMany({
where: { id: postId, userId: principal.subject, tenantId: principal.tenantId }
});
return result.count === 1;
}
管理员路径需要显式独立的策略和审计事件;不要为每个所有者检查添加隐式的管理员绕过。测试 有效用户访问另一用户的对象、另一租户中的相同 ID、已删除的成员关系以及批量端点。
3. 只解析一次,然后使用经过校验的值
拒绝部分数字解析(12abc 不是 ID 12)、不安全的整数、意外属性和超大请求。使用参数化数据库查询。ORM 并不提供业务授权,也不能让不安全的原始 SQL 变得安全。
function parsePositiveId(raw) {
if (typeof raw !== 'string' || !/^[1-9][0-9]{0,15}$/.test(raw)) return null;
const value = Number(raw);
return Number.isSafeInteger(value) && value > 0 ? value : null;
}
const { z } = require('zod');
const profileUpdate = z.object({
displayName: z.string().trim().min(1).max(100)
}).strict();
function validateBody(schema) {
return (req, res, next) => {
const parsed = schema.safeParse(req.body);
if (!parsed.success) {
return res.status(400).json({ error: 'Invalid request' });
}
req.validatedBody = parsed.data; // Defaults/transforms must reach the handler.
next();
};
}
// Handler uses req.validatedBody, never the original body or an arbitrary spread.
在解析之前设置请求体大小限制。Zod 形状校验只是其中一层:在事务中检查当前归属、允许的状态转换和唯一性。对于 HTML,只允许使用经过维护的净化器所支持的必需标签/属性,然后用目标环境的安全输出 API 进行渲染。对于纯文本评论,优先使用纯文本;对字符串进行净化并不能使其在每种 JavaScript、URL 或 HTML 上下文中都安全。也要将上游 API 的响应视为不可信输入进行校验。
对于出站 URL,要定义允许的协议/主机、重定向行为、解析后的 IP 范围、凭据策略、超时和响应大小。仅靠正则表达式或 URL 解析器无法防止 SSRF 或 DNS 重绑定。文件上传同样需要类型/内容检查、大小限制、隔离存储以及读取时的授权。
4. 在不宣称 DDoS 防护的前提下控制滥用
使用现有的网关和经过维护的限流存储。在推导已认证用户键之前先进行身份认证,绝不要信任用户提供的层级。对于匿名流量,使用库所支持的、能识别 IPv6 的 IP 键处理方式,并将 Express 的 trust proxy 配置为实际可信的跳数;不要盲目为所有调用方启用它。
对于分布式配额,使用带过期时间的原子计数器实现,并定义存储故障时的行为。避免手写 INCR 后跟 EXPIRE:两者之间发生崩溃可能会留下永久键。区分每用户配额、每 IP 滥用控制、并发限制和上游服务预算。记录实际的窗口/重置语义,并发送准确的 Retry-After,而不是硬编码的整个窗口值。除非配置了共享存储,否则内存限流器是每进程独立的。
测试并发请求、IPv4/IPv6、伪造的转发头、未知层级、身份缺失、Redis 故障和过期。应用层限流无法吸收网络饱和。Helmet 配置的是 HTTP 响应头;它不是 DDoS 防护、访问控制,也不能替代上游容量控制。针对实际部署和子域策略推出 CSP/HSTS;不要盲目复制预加载设置。
5. 密码、密钥和日志
尽可能使用既有的身份提供商。对于存储的密码,使用经过维护的密码哈希方案并校准参数(新设计优先使用 Argon2id)。检查已泄露/常见密码并支持口令短语。不要强加任意的的大写/符号组合规则,也不要静默截断长密码。旧版 bcrypt 有输入字节上限,迁移时必须明确考虑;密码的字符长度和 UTF-8 字节数是不同的。
将密钥保存在经批准的密钥机制中,在启动时检查必需配置而不打印其值,并轮换已暴露的凭据。绝不要在常规日志中包含原始令牌、密码、请求体或完整的数据库异常。在适当的保留/访问策略下,记录有界的事件名称、请求关联以及安全的状态/错误类别。经过净化的错误不应返回可批量赋值的用户对象。CORS 控制的是浏览器跨源访问;它不是 API 认证或 CSRF 防护。
完整示例:资料更新边界
给定 PATCH /users/:id,使用字符串 ID 和可编辑的显示名称:
- 在 fixtures 中记录已授权的调用方/租户以及当前端点行为。
- 检查缺失/过期/错误受众的 token 是否在访问存储之前返回 401。
- 发送
12abc、一个不安全的整数、空名称以及额外的role字段;预期返回 400 且不发生数据库变更。发送一个带填充的有效名称;确认只有解析后去除填充的 值才会到达按所有者与租户限定范围的更新。 - 尝试其他用户的有效 ID 以及跨租户 ID;预期得到文档所述的拒绝 且不发生变更。有效的所有者请求仅更新允许的属性。
- 触发配额/存储失败,并确认日志中不包含请求 token 或名称。
返回路由策略、复现的失败用例、确切的测试命令/输出以及 剩余缺口。这些是应在目标应用中执行的预期检查,并非声称本仓库已测试过 已部署的 API。
限制
本指南不是完整的身份服务、认证安全审计或渗透 测试。代码片段省略了应用适配器和集成错误中间件。解析器的单元测试 并不验证数据库事务、代理行为或提供商会话。 请明确报告任何未测试的路由、租户路径和失败模式。不要根据通过的结构性检查 或风险元数据标签推断出安全状况良好。
参考资料
- OWASP API Security
- JWT best current practices, RFC 8725
- OWASP Session Management
- OWASP Password Storage
- Express production security
- Zod basic parsing
- 相关技能:
auth-implementation-patterns、api-patterns、systematic-debugging。