API 出问题时,根因通常不是路径叫 /users 还是 /user.get,而是服务端把客户端传来的对象 ID、角色或价格当作可信事实。接口风格影响可读性;安全取决于每一次请求在服务端如何被认证、授权、校验和记录。
本文面向已有 HTTP API 的小团队,优先处理一条真实用户路径,例如“用户查看自己的账单”或“管理员导出某个工作区的数据”。复核日期为 2026-09-03。它不是合规审计,也不替代针对业务模型的渗透测试。
从对象授权开始
假设请求是 GET /invoices/inv_123。认证成功只证明调用者是谁;授权还必须确认该主体是否能读这张具体账单。不要因为前端隐藏了按钮、路径不可预测,或查询条件带了 userId,就跳过这一步。
OWASP 将“对象级授权失效”列为 API 的主要风险之一。可把每个读取、更新和删除操作写成明确检查:根据已认证主体、当前租户和目标对象加载数据;不满足策略时拒绝;不要让调用方提交的租户、所有者或权限字段覆盖服务端判定。详见 OWASP API Security Top 10。
对同一类资源抽样测试至少四种情况:对象所有者、同租户非所有者、其他租户用户、未认证请求。对批量导出、搜索结果和异步任务也做同样检查,因为这些路径往往绕开了详情页的保护逻辑。
认证凭据只解决身份问题
Cookie 会带来跨站请求风险;Bearer token 容易因日志、浏览器存储或错误的重定向而泄露;API key 适合机器身份,却同样需要轮换、范围和撤销方式。没有一种凭据格式自动适合所有客户端。
先记录凭据由谁持有、在哪里传输、如何过期、如何撤销。若使用 JWT,服务端仍要验证签名、允许的算法、有效期、受众与签发方等声明;JWT 不会替你实现对象授权。OAuth 2.0 和 OpenID Connect 的细节应遵循所接入身份提供方的正式文档,不要从通用代码片段推导生产配置。
把输入变成有边界的数据
每个端点都应在业务逻辑之前验证:字段是否存在、类型和长度是否符合预期、枚举值是否在允许集合、嵌套对象是否允许、分页与排序是否有上限。验证器的输出应该是新的、已解析的数据对象,而不是“原始请求再加几个判断”。
参数化查询或 ORM 的绑定参数可把数据与 SQL 指令分离,但它不能保护由字符串拼出的列名、排序方向或任意过滤表达式。对这类结构性输入使用白名单。文件上传、URL 抓取、模板渲染和 webhook 转发各有额外攻击面,应分别设大小、类型、目的地址和超时边界。
错误响应应帮助合法调用者修正请求,但不返回堆栈、密钥、内部主机名或其他用户数据。HTTP 状态码的语义以 IETF RFC 9110 为准;选择 400、401、403 或 429 前,先让团队对错误契约和客户端行为达成一致。
资源限制是一项业务规则
限流不是只在网关上填一个数字。先明确要保护什么:登录尝试、验证码发送、搜索、导出、上传,还是会触发付费第三方服务的操作。限额的键也依赖风险模型:IP、账户、租户、设备或 API key 可能各有不同效果。
命中限制时返回可处理的错误,并记录触发维度而非原始敏感内容。对于创建订单、发送邮件等可重试操作,设计幂等键或等价机制,避免网络重试产生重复副作用。任何固定阈值都要在流量、误报和攻击迹象中复核,而不应从文章直接复制。
浏览器接口要分别处理 CORS 与 CSRF
CORS 是浏览器对跨源读取响应的规则,不是服务端访问控制。对于携带 Cookie 的跨源请求,服务端必须显式允许具体来源;通配符和凭据不能组合使用。CSRF 则要根据认证方式、SameSite 设置和跨站调用需求设计令牌或其他验证。详情请以 MDN 的 CORS 指南 为准。
先区分预检许可与实际响应
以下是2026-10-04补充的合成材料,不是对任何服务的探测。可以复制JSON到CORS文本条件解释,分别审读请求条件、预检、实际响应与头可见性。
{
"origin": "https://app.example",
"method": "POST",
"credentials": "omit",
"requestHeaders": [
"X-Demo"
],
"preflight": {
"status": 204,
"headers": "Access-Control-Allow-Origin: https://app.example\nAccess-Control-Allow-Methods: POST\nAccess-Control-Allow-Headers: X-Demo"
},
"response": {
"status": 200,
"headers": "Access-Control-Allow-Origin: https://app.example\nAccess-Control-Expose-Headers: X-Demo-Result\nX-Demo-Result: synthetic"
},
"readHeaders": [
"X-Demo-Result"
]
}
| 对照 | 所填材料 | 本地预期观察 |
|---|---|---|
| A | 原样输入两段响应 | 所填条件满足支持子集;x-demo-result按输入可见 |
| B | 预检不变,把response.headers改为空串 | 实际响应缺Allow-Origin,存在阻断;OPTIONS成功不能代替实际响应 |
| C | 省略整个response对象 | 实际响应未知;不能把未取得响应当成已修复或确定没有发请求 |
| D | 请求头名称改为Content-Type,未提供值/MIME | 预检条件未知;只看POST和头名不能判断safelist |
成功完成文本解释不代表网站或业务通过。即使脚本不能读取响应,实际请求仍可能已到达服务端;只有服务器观察与浏览器证据能确认本次发生了什么。HTTP错误响应也可能满足CORS条件,仍需单独修正业务错误。标准机制参见Fetch CORS check与预检算法(复核于2026-10-04)。
Authorization只填写名称,不粘贴令牌。它的通配许可存在标准文字与本轮浏览器观察的差异,助手保留未知并建议明确允许后复验。Cookie、Origin等浏览器管理头、null来源、重定向链、缓存/服务工作线程和真实凭据策略不在这个文本子集内;不要为得到确定结论删除未知条件。
缺材料、预检失败或两阶段矛盾时停止推断,按CORS指南取得同一来源、浏览器、方法与头条件下的记录。把文本推演与真实观察分开填入接口排查记录,保存时间、环境、是否看见OPTIONS/实际请求、未知项和下一步;可复制或下载Markdown,也可下载本地JSON草稿继续编辑。记录不是自动验证,不填凭据值。
一次发布前的接口检查
- 每个对象读写操作都用服务端主体和租户重新判定权限。
- 请求体、查询参数、上传与 URL 输入均有类型、允许值和资源边界。
- 凭据不会写入日志、错误页、前端包或 URL。
- 限流、幂等和超时覆盖会造成高成本或重复副作用的路径。
- 错误响应遵循稳定契约,内部细节留在受访问控制的日志中。
- 新旧端点、测试端点和 webhook 都在 API 清单内。
安全不是在上线前加一层中间件,而是让每个会改变或暴露数据的动作都有可测试的服务端判定。
参考资料
- OWASP API Security Top 10(2023)(复核于 2026-09-03)
- IETF RFC 9110:HTTP Semantics(复核于 2026-09-03)
- MDN:CORS(复核于 2026-09-03)
