API 安全清单实战:基于 API-Security-Checklist 的设计、测试与发布全流程安全加固指南
API 安全清单实战:基于 API-Security-Checklist 的设计、测试与发布全流程安全加固指南
本文以仓库中的西班牙语版清单 README-es.md 为骨架(英文原版见 README.md,中文对照版见 README-zh.md),逐条展开"为什么"与"怎么做",覆盖身份认证、访问控制、授权、输入校验、业务处理、输出安全、CI/CD 与监控告警,并深入 Rate Limiting、GraphQL、密钥管理与零信任四大进阶专题。读完本文,你将得到一份可直接对照执行、可落地到具体配置与代码的 API 安全加固方案。
这份清单(API Security Checklist)由 Shieldfy 团队维护,旨在回答一个非常实际的问题:在设计、测试和发布 API 的完整生命周期中,哪些安全对抗措施最重要?清单以"待办复选框"(- <a href="https://link.gitcode.com/i/214b050d506ac8dcd5bd9391be4616dd" target="_blank"> ])的形式呈现,每一个条目都对应一类真实存在的攻击面或业界公认的安全基线。仓库内以 README-*.md 命名规则维护着 30 多种语言的翻译版本(语言代码遵循 ISO 639-1,详见 [CONTRIBUTING.md),本文所述内容在西班牙语、英语、简体中文与繁体中文版本中完全一致。
一、身份认证(Autenticación):先把"门锁"装对
认证是 API 的第一道防线,清单给出了四条底线要求:
- 不要使用
Basic Auth,改用标准认证协议。Basic Auth把用户名密码以Base64形式直接放进Authorization请求头,既无加密也极易被解码与重放,应改用 OAuth 2.0、OpenID Connect、OIDC 或携带签名机制的标准方案。 - 不要重新发明轮子。 认证流程、
token生成、密码存储这三件事都极其容易写错,务必使用经过审计的标准库(如bcrypt/scrypt/argon2做密码哈希、标准 JWT 库或随机 API Key 生成器),而不是自己拼凑加密算法。 - 登录接口启用
Max Retry(最大重试次数)与 jail(封禁)机制。 在给定时间窗口内超过失败次数后拒绝登录、递增等待时间或临时封禁 IP,是抵御密码暴力破解最直接的手段。 - 对所有敏感信息加密。 无论是数据库中的密码、密钥还是传输中的凭据,都应使用强加密算法存储与传输。
此外,从 README-zh.md 的对应小节可以补充两条在中文语境下尤其常见的落地细节:不要把 API Key、云组件 Key 等硬编码进前端页面或 APP;使用开源框架时禁止保留默认密钥(例如 Shiro 的默认 Key)。这些条目共同构成"入口安全"的第一层。
二、访问控制与传输安全(Acceso):管住流量、锁死信道
这一节解决的是"API 暴露在公网上"时最基础的防护问题:
- 限流(Throttling)以防 DDoS 与暴力破解。 对单位时间内的请求数进行限制,是成本最低、见效最快的防御手段,具体可结合网关策略实现(详见下文"API Gateway"小节)。
- 服务端使用 HTTPS,避免 MITM(中间人攻击)。 在客户端与服务器之间建立加密信道,防止攻击者窃听或篡改流量。
- 使用
HSTS头配合 SSL,防 SSL Strip 攻击。HSTS(HTTP Strict Transport Security)告诉浏览器"今后只允许通过 HTTPS 访问本站",从而阻止攻击者把 HTTPS 降级成 HTTP 的经典攻击手法。 - 关闭目录列表(Directory Listing)。 防止 Web 服务器把目录下的文件结构直接暴露给访问者。
- 私有 API 只允许白名单 IP/主机访问。 对于不对外公开的接口,应在网络层或网关层做来源过滤。
三、授权与 OAuth(Autorización):最容易被绕过的环节
授权决定"登录后能做什么"。针对 OAuth 授权流程,清单给出了四条极易出问题的检查点:
- 始终在服务端校验
redirect_uri,只允许白名单 URL。 若回调地址可被攻击者控制,攻击者就能诱导授权服务器把授权码(code)或令牌发往自己的服务器,形成凭据窃取。 - 始终交换"授权码(code)"而非"令牌(token)",不要允许
response_type=token。 授权码模式(Authorization Code Flow)允许令牌在受保护的token端点通过后通道换取,而隐式模式(Implicit Flow,response_type=token)直接把令牌暴露在浏览器地址栏与历史记录中,且无法对客户端做认证。 - 使用带随机哈希的
state参数,防止 OAuth 授权过程中的 CSRF。state作为一次性的防伪随机值在发起授权时生成、回调时校验,确保回调请求确实由自己发起的授权流程触发。 - 定义默认 scope,并针对每个应用逐一校验其 scope 参数。 授权范围应遵循最小权限原则:默认只授予最小必要权限,同时验证应用实际申请的 scope 不超出其应得的范围,防止权限越界。
四、输入校验(Entradas):把"入口的垃圾"挡在门外
输入是攻击者接触 API 的第一现场,这一节清单条目最多,也最贴近日常开发:
- 按操作使用恰当的 HTTP 方法:
GET(读取)、POST(创建)、PUT/PATCH(替换/更新)、DELETE(删除);若请求方法不适用于目标资源,返回405 Method Not Allowed。这既是 REST 语义的要求,也能避免"用 GET 删数据"这类因语义混乱导致的安全事故。 - 校验请求
Accept头中的content-type(内容协商),只允许你支持的格式(如application/xml、application/json),不匹配时返回406 Not Acceptable。同理,还要验证请求正文实际声明的content-type是否在你接受的范围内(如application/x-www-form-urlencoded、multipart/form-data、application/json)。声明与实体不一致(例如宣称 JSON 却收到表单数据)常常是绕过过滤器的前奏。 - 校验用户输入,防范
XSS、SQL 注入、远程代码执行等常见漏洞。 对所有外部输入(路径参数、查询参数、请求体、上传文件名)做白名单校验、参数化查询与输出编码。 - 不要在 URL 中携带敏感数据(
凭据、密码、安全令牌、API Key),一律放入标准的Authorization请求头。URL 会出现在代理日志、浏览器历史、Referer 头中,且不受 HSTS 等传输层保护之外的额外约束。 - 只使用服务端加密。 客户端加密既无法信任也无法审计,密钥管理必须在服务端完成。
- 使用 API Gateway 服务,以便启用缓存(caching)、限速策略(Rate Limit,如
Quota、Spike Arrest、Concurrent Rate Limit)并动态部署 API 资源。网关将认证、限流、缓存、协议转换集中到一个可治理的边界上,避免每个服务各自为政。
参考实现:Express 风格的输入校验骨架
以 Node.js/Express 为例,一个最小可行的做法是统一校验方法、内容类型并返回语义化状态码(下列代码仅为说明清单落点的示意,并非仓库源码):
// 校验 HTTP 方法
app.all('/api/orders', (req, res, next) => {
if (!['GET', 'POST'].includes(req.method)) {
return res.status(405).set('Allow', 'GET, POST').end();
}
next();
});
// 校验请求正文的 Content-Type
app.post('/api/orders', (req, res, next) => {
if (req.headers['content-type'] !== 'application/json') {
return res.status(406).end();
}
next();
});
五、业务处理(Procesamiento):处理环节里的隐藏风险
"登录成功"不等于"安全",处理阶段的清单聚焦于身份与资源模型的正确性:
- 检查所有端点是否都在认证保护之后,避免"认证被绕过"(broken authentication)。 最常见的问题不是没有认证,而是某个内部接口、管理接口或遗留端点漏掉了认证。
- 避免使用带用户 ID 的资源路径,用
/me/orders替代/user/654321/orders。前者由服务端从会话中解析当前用户,后者则诱导开发者相信客户端传来的 ID,极易产生水平越权(访问他人资源)。 - 不要使用自增 ID,改用
UUID。 自增 ID 可枚举、可预测,攻击者遍历 ID 即可批量抓取资源;UUID 不可枚举,天然降低了资源探测风险。 - 解析 XML 时确保关闭实体解析(entity parsing),防范
XXE(XML 外部实体攻击)。 未关闭外部实体时,攻击者可通过<!DOCTYPE ...>声明读取服务器文件、发起 SSRF。 - 解析 XML、YAML 或其他支持锚点(anchors)与引用(refs)的语言时,关闭实体扩展,防范
Billion Laughs/XML bomb。 这类攻击通过实体指数级嵌套展开,把几十字节的 XML 膨胀成数 GB 内存消耗,最终拖垮服务(即指数实体扩展攻击)。 - 文件上传使用 CDN。 上传内容不应写入应用服务器的本地磁盘,交给 CDN/对象存储承载,避免磁盘耗尽与存储型 XSS 的载体问题。
- 面对大量数据时,用 Workers 和队列在后台异步处理,尽快返回响应,避免 HTTP 阻塞。 长耗时同步请求既拖垮用户体验,也占满连接池成为 DoS 入口。
- 关闭 Debug 模式。 生产环境开启 Debug 会泄露堆栈、配置与内部路径。
- 尽可能使用不可执行栈(non-executable stacks)。 例如为上传目录与静态资源设置禁止执行脚本的策略,阻止 WebShell 落地执行。
六、输出安全(Salidas):别在响应里"自曝家底"
输出安全是经常被忽略的一环——响应不仅返回业务数据,还会泄漏服务器指纹与内部信息:
- 发送
X-Content-Type-Options: nosniff头,禁止浏览器对响应内容做 MIME 嗅探,防止类型混淆型攻击。 - 发送
X-Frame-Options: deny头,禁止页面被嵌入<iframe>,从源头防点击劫持(Clickjacking)。 - 发送
Content-Security-Policy: default-src 'none'头,默认拒绝一切来源加载,再按需放行,把 XSS 的破坏面压到最小。 - 删除指纹头:
X-Powered-By、Server、X-AspNet-Version等头会暴露技术栈与版本,便于攻击者定向搜索对应漏洞,应统一移除或替换为泛化值。 - 强制响应
content-type:返回application/json时响应头就必须是application/json,避免"JSON 响应被当作 HTML 解析"引发的反射型 XSS。 - 不要把过于具体的错误消息返回给客户端,以免泄露实现细节(框架名、SQL 语句、文件路径);对客户端使用通用错误消息,详细日志只保留在服务端。
- 不要返回敏感数据,如
凭据、密码、安全令牌。 - 按操作结果返回恰当的状态码,如
200 OK、400 Bad Request、401 Unauthorized、405 Method Not Allowed等,既符合 HTTP 语义,也有助于客户端正确区分"未认证"与"无权限"。
从 README-zh.md 还可补充中文实战中常被点名的三条:返回统一的错误页面,禁止把调用堆栈展示在错误页中;仅返回前端需要的业务数据,不返回过多敏感类型数据;数据脱敏必须在服务端返回前完成,禁止在前端做脱敏(因为前端脱敏可被轻易绕过)。
参考实现:统一安全响应头中间件
app.use((req, res, next) => {
res.set({
'X-Content-Type-Options': 'nosniff',
'X-Frame-Options': 'deny',
'Content-Security-Policy': "default-src 'none'",
'Cache-Control': 'no-store'
});
res.removeHeader('X-Powered-By');
res.removeHeader('Server');
next();
});
七、CI/CD:把安全"左移"到发布流水线
清单把安全要求延伸到持续集成与持续部署阶段,强调"上线前必须完成"的动作:
- 用单元测试与集成测试覆盖率审计设计与实现,把安全断言(越权、输入校验、状态码)写进测试用例。
- 建立代码审查流程,禁止自我批准(disregard self-approval)。 任何变更至少经过一位同行评审,避免"自己写、自己合并、自己背锅"的单点风险。
- 推送生产前,用杀毒/静态扫描软件扫描服务的所有组件,包括第三方库与其他依赖,防止供应链投毒进入生产。
- 持续运行安全测试(静态/动态分析)。静态分析找代码缺陷,动态分析(DAST)验证运行期漏洞。
- 检查依赖项(软件与操作系统)的已知漏洞,例如通过依赖扫描工具与漏洞数据库比对。
- 为部署设计回滚方案(rollback)。安全问题的修复往往需要快速回退,没有回滚预案的发布是高风险发布。
八、监控与告警(Monitorear):上线之后还要看得见
安全不是一次性动作,而是持续的观测:
- 所有服务与组件使用集中式日志,把散落各处的日志收敛到统一平台,便于关联分析。
- 使用代理(agent)监控所有流量、错误、请求与响应,形成可查询、可度量的观测数据。
- 配置告警通道:SMS、Slack、Email、Telegram、Kibana、CloudWatch 等,让异常第一时间触达负责人。
- 确保日志中不记录敏感数据(信用卡号、密码、PIN 等)。日志系统是数据泄露的高发盲区,必须在写日志时就做字段过滤与脱敏。
- 使用 IDS 和/或 IPS 系统监控 API 请求与实例,在网络层识别入侵行为并及时阻断。
九、进阶最佳实践:Rate Limiting、GraphQL、密钥管理与零信任
清单的后半部分集中了四个面向现代 API 形态的进阶专题,原文为英文(各语言版本均保留原文,见 README-es.md 第 92 行起的 "API Security Best Practices (Advanced)" 章节):
9.1 Rate Limiting 与滥用防护
- 按 API Key 和 IP 实现滑动窗口限流。相比固定窗口,滑动窗口能更平滑地抑制突发流量与爬取行为。
- 对连续失败的认证尝试使用指数退避(exponential backoff),让攻击者每次尝试的代价随时间递增。
- 在可疑活动后引入 CAPTCHA 或工作量证明(proof-of-work)挑战,用成本不对称原理劝退自动化攻击。
- 监控并告警异常的 API 使用模式(时间、流量、端点维度),例如凌晨时段的批量拉取。
9.2 GraphQL 专属安全
- 生产环境禁用 introspection,避免攻击者自动枚举完整的类型系统与字段结构。
- 实现查询深度限制(query depth limiting),阻断深度嵌套查询攻击。
- 使用查询成本分析(query cost analysis),防止高开销查询耗尽资源。
- 生产环境尽可能对允许的查询做白名单,只放行已审计的查询语句。
9.3 密钥管理(Secrets Management)
- 按固定周期轮换 API Key 与密钥,缩短单次泄露的有效窗口。
- 签名操作使用硬件安全模块(HSM),把私钥物理隔离在不可导出介质中。
- 在 CI/CD 流水线中实现密钥扫描,在代码入库前发现硬编码秘密。
- 永不把密钥提交到版本控制,改用环境变量或专用密钥管理器。
9.4 零信任架构(Zero Trust)
- 服务间通信启用双向 TLS(mTLS),让调用双方互为认证。
- 即使来自内部服务,也校验每一个请求——内网不等于可信。
- 使用短生命周期令牌并自动刷新,缩小令牌被窃取后的有效时间窗。
- 对敏感操作实现请求签名,确保请求内容在传输途中未被篡改。
十、如何把这份清单用起来
- 对照逐条检查:清单以复选框形式组织,最适合在设计与发布评审会上逐条打勾,任何一条未满足都应有明确的拒绝上线理由。
- 按阶段落地:身份认证、访问控制、输入校验在设计阶段就应定案;处理、输出安全在编码阶段落实;CI/CD 与监控在发布与运维阶段持续生效。
- 结合语言版本交叉阅读:本文以 README-es.md 为骨架,README.md 为英文原版,README-zh.md 与 README-tw.md 提供了中文语境下的补充条目(如防硬编码密钥、后端统一脱敏、统一错误页面等),多版本对照可避免翻译细节带来的理解偏差。
- 安全是动态的:清单给出的是基线而非终点。新攻击手法出现后,应参照进阶最佳实践持续扩展自己的检查项。
最后说明:仓库本身以 MIT 协议(见 LICENSE)开源,仅包含清单文档及其多语言版本,不含可运行的代码示例;本文中的代码片段均为帮助理解落地方式的示意写法。若想参与维护,可按 CONTRIBUTING.md 的要求 fork 仓库、翻译 README.md 并提交 Pull Request。