正文

API 接口上线前,不能只看“前端能调通”。接口一旦发布到公网或内网网关,别人可以绕过页面、改请求参数、换 token、并发重放、批量导出、构造错误输入。按钮隐藏了,不代表接口安全;页面不展示,不代表接口不能被调用。

真实事故已经反复说明这一点。

2023-01-19,T-Mobile 披露一起影响 3700 万客户的数据事件。AP 报道称攻击者从 2022-11-25 左右开始访问数据,T-Mobile 在 2023-01-05 发现问题;Wired 进一步提到攻击者通过 API 访问了客户姓名、邮箱、电话号码、账单地址、生日、账号和套餐信息。这个案例对开发者的提醒很直接:API 能被正常调用,不代表它应该允许被持续批量调用。

2021-05-05,TechCrunch 报道 Peloton 的 API 漏洞:即使用户把资料设为 private,攻击者也能通过 API 拉到私密账号数据。问题不在页面,而在服务端没有正确检查调用者是否有权拿这些字段。API 安全不能依赖“用户在网页上看不到入口”。

OWASP API Security Top 10 2023 把 Broken Object Level Authorization 放在第一项。2026 年一篇分析 100 多个 bug bounty 披露的 BOLA 研究也指出,在确认的 BOLA 案例中,未经授权的状态变更和直接对象引用是主要类型。换句话说,最常见的 API 风险不是复杂加密算法,而是“用户 A 能不能拿用户 B 的数据或替用户 B 操作”。

上线前先查这五件事:

鉴权没有合法身份不能调用私有接口
越权用户只能操作自己有权操作的资源
限流批量请求、短信、邮件、导出、搜索不会被刷爆
幂等重试、双击、网络抖动不会重复扣款或重复创建
错误返回客户端能处理,攻击者拿不到内部细节

先列出哪些接口真的要上线

很多问题来自“以为没上线”。测试接口、调试接口、旧版本接口、管理接口、导出接口,只要能被路由到,就算上线。

先做一张接口清单:

用命令先扫项目里的路由定义:

如果接口没有 owner、没有调用身份、没有错误码和没有验证方式,先不要发布。

方法和路径POST /api/orders
入口公网、内网、管理后台、移动端
调用身份匿名、登录用户、管理员、系统服务
操作资源订单、用户、文件、账单、消息
是否写操作查询、创建、更新、删除、导出
是否高成本发短信、发邮件、调用模型、生成文件
上线标准通过鉴权、越权、限流、幂等和错误测试
PowerShell
rg -n "router\\.|app\\.(get|post|put|patch|delete)|@Get|@Post|@Put|@Patch|@Delete|defineEventHandler|server/api" src server apps

鉴权先查有没有匿名入口

鉴权检查回答的是:这个接口是否需要知道调用者是谁。

先用无 token 请求:

再用无效 token 请求:

通过标准:

不要只在前端路由里判断登录。后端每个私有接口都要校验身份。网关校验也不够,业务服务仍要知道当前用户是谁、角色是什么、租户是什么。

没有 token私有接口返回 401
token 无效返回 401,不要降级成匿名用户
token 过期返回 401 或明确的过期错误
权限不足返回 403,不要返回资源内容
公共接口只返回公开字段,不返回内部字段
PowerShell
curl.exe -i https://api.example.com/api/orders
PowerShell
curl.exe -i https://api.example.com/api/orders -H "Authorization: Bearer invalid-token"

越权用两个账号测

越权检查回答的是:用户能不能访问不属于自己的资源。OWASP 对 BOLA 的说明很明确:只要接口从客户端接收对象 ID,并基于这个 ID 访问数据,就要做对象级授权检查。

准备两个普通账号:

用用户 B 的 token 访问用户 A 的资源:

再试更新和删除:

通过标准:

不要写这种逻辑:

更稳的是:

如果系统是多租户,还要加租户维度。id 对了但 tenantId 不对,也必须拒绝。

B 读 A 的订单403404
B 改 A 的订单403404
B 删 A 的订单403404
B 传 userId=A 创建数据后端忽略请求里的 userId,使用 token 身份
普通用户访问管理接口403
Text
用户 A:创建订单 order_a
用户 B:创建订单 order_b
PowerShell
curl.exe -i https://api.example.com/api/orders/order_a `
  -H "Authorization: Bearer user-b-token"
PowerShell
curl.exe -i -X PATCH https://api.example.com/api/orders/order_a `
  -H "Authorization: Bearer user-b-token" `
  -H "Content-Type: application/json" `
  -d "{\"status\":\"cancelled\"}"
Text
从 request.body.userId 读取用户 ID,然后查询订单。
Text
从 token/session 得到 currentUserId。
查询订单时同时带上 resourceId 和 currentUserId / tenantId。
写操作前检查当前用户对资源的 action 权限。

管理接口要查功能级权限

对象级授权解决“这条数据是不是你的”。功能级授权解决“你是不是能做这个动作”。

重点查这些接口:

测试方式:

管理权限不要只靠菜单隐藏。只要接口存在,权限就必须在服务端执行。

/api/admin/*普通用户可能访问管理功能
/api/users/{id}/role普通用户提升自己或别人权限
/api/orders/export批量导出敏感数据
/api/refund重复退款、越权退款
/api/feature-flags打开未发布功能
/api/internal/*内部接口被公网路由暴露
Text
1. 用普通用户 token 调管理员接口。
2. 用只读用户 token 调写接口。
3. 用同租户低权限用户调高权限接口。
4. 用跨租户用户调同名资源接口。
5. 用系统服务 token 调用户接口,确认 scope 不过宽。

限流要按资源成本设置

限流不是只防 DDoS。OWASP API4 提到,API 请求会消耗网络、CPU、内存、存储,也可能触发短信、邮件、电话、第三方 API 和云服务账单。没有限制时,攻击者不一定要打垮系统,也能把成本打上去。

先给接口分级:

用脚本做最小压测:

通过标准:

GitHub REST API 文档把 primary rate limit、secondary rate limit、认证/匿名请求的不同配额拆开说明,这是成熟 API 的基本做法:限流不是一个全局数字,而是按身份、资源、操作和成本分层。

登录 / 验证码按账号、IP、设备限制,防爆破和短信轰炸
搜索 / 列表限制分页大小、查询复杂度和频率
文件上传限制大小、类型、总量、并发
导出 / 报表限制频率、后台任务、审批或队列
发送邮件 / 短信限制单用户、单手机号、单 IP、每日总量
AI / 第三方 API设置预算、并发、超时和熔断
连续请求超过阈值返回 429
返回 429Retry-After 或可理解的重试信息
导出接口频繁调用进入队列或拒绝
GraphQL 批量请求限制 depth、cost、batch 数
第三方 API 异常不无限重试,不拖垮自己
PowerShell
1..30 | ForEach-Object {
  curl.exe -s -o NUL -w "%{http_code}`n" https://api.example.com/api/sms/send `
    -H "Authorization: Bearer user-a-token"
}

幂等要覆盖创建、支付和重试

幂等检查回答的是:同一个业务动作被重复提交时,会不会重复生效。

最容易出问题的接口:

Stripe 的 API 文档建议对创建或更新对象的请求使用 idempotency key,服务端用这个 key 识别重试,避免同一操作重复执行。它还提醒不要用邮箱、个人标识这类敏感数据当幂等 key。

一个最小约定:

服务端要保存:

测试方式:

通过标准:只创建一次订单。第二次返回第一次的结果,或者返回明确的幂等冲突,不要再执行一次业务动作。

创建订单双击提交生成两笔订单
支付回调重复回调导致重复发货
退款接口重复请求导致重复退款
发券 / 发积分网络重试导致重复发放
创建用户超时重试导致重复账号或脏数据
消息发送用户收到多封邮件或多条短信
idempotency_key识别同一次业务请求
user_id / tenant_id防止 key 被跨用户复用
request_hash防止同一个 key 带不同参数
statusprocessing / succeeded / failed
response_body重复请求返回相同结果
expires_at控制保留时间
Text
POST /api/orders
Idempotency-Key: 7f0e2d0e-3c9b-4a2c-8a61-7a0f1e0a1111
PowerShell
$key = "11111111-1111-4111-8111-111111111111"

curl.exe -i -X POST https://api.example.com/api/orders `
  -H "Authorization: Bearer user-a-token" `
  -H "Idempotency-Key: $key" `
  -H "Content-Type: application/json" `
  -d "{\"sku\":\"basic\",\"quantity\":1}"

curl.exe -i -X POST https://api.example.com/api/orders `
  -H "Authorization: Bearer user-a-token" `
  -H "Idempotency-Key: $key" `
  -H "Content-Type: application/json" `
  -d "{\"sku\":\"basic\",\"quantity\":1}"

错误返回要能调试但不能泄露

错误返回有两个目标:客户端能处理,攻击者拿不到内部细节。

不要把这些内容返回给客户端:

RFC 9457 定义了 HTTP API 的 Problem Details 格式,可以用来统一错误响应。你不一定要完全照搬,但至少要固定字段。

建议错误码:

日志里可以保存更多排查信息,但要脱敏。客户端拿 requestId,服务端用 requestId 查日志。

  • SQL 语句。
  • stack trace。
  • 内部文件路径。
  • Redis key、S3 bucket、队列名。
  • token、cookie、手机号、邮箱全量。
  • 详细权限规则和内部角色名。
未登录或 token 无效401
已登录但无权限403
资源不存在或不想暴露存在性404
参数格式错误400
语义校验失败422
限流429
幂等冲突409
服务端错误500,但不要返回内部异常
JSON
{
  "type": "https://api.example.com/problems/forbidden",
  "title": "Forbidden",
  "status": 403,
  "detail": "You do not have permission to access this resource.",
  "instance": "/request/req_123456"
}

日志和审计要能追到谁做了什么

上线检查不能只看接口返回。出问题后,你要能回答:

建议每个关键接口记录:

不要记录完整 token、完整身份证、完整手机号、完整邮箱、原始密码、完整请求体。日志是排查工具,不是第二份用户数据库。

  • 谁调用了接口?
  • 调用了哪个资源?
  • 操作是否成功?
  • 请求来自哪里?
  • 是否命中限流?
  • 是否重复请求?
  • 是否触发权限拒绝?
Text
request_id
user_id_hash
tenant_id
method
path_template
resource_type
resource_id_hash
action
status_code
auth_result
rate_limit_result
idempotency_key_hash
duration_ms

上线前跑一张最小检查表

把这张表放进 PR 或发布流程里。

再用两组账号做验收:

API 上线不是把 Controller、Route 或 Handler 合进去就结束。真正的上线标准是:身份说得清,资源边界守得住,高成本操作刷不爆,重复请求不会造成二次副作用,错误返回能排查但不泄密。

只要这五件事没有跑过,接口就还只是“能用”,不是“能上线”。

Text
API release check:
- [ ] All private endpoints reject anonymous requests
- [ ] Invalid and expired tokens return 401
- [ ] Normal user cannot access admin endpoints
- [ ] User B cannot read/update/delete User A resources
- [ ] Tenant boundary is checked on every tenant resource
- [ ] Request body userId / tenantId is not trusted directly
- [ ] List endpoints have pagination and maximum page size
- [ ] Costly operations have per-user and global limits
- [ ] Retry-sensitive POST endpoints support idempotency
- [ ] Duplicate payment/order/refund/webhook requests do not repeat side effects
- [ ] Errors return stable codes and requestId
- [ ] Errors do not expose SQL, stack trace, token or internal path
- [ ] Logs can trace requestId without storing secrets
- [ ] Old, debug and internal endpoints are not publicly routable
Text
账号 A:普通用户,有自己的资源
账号 B:普通用户,有自己的资源
管理员 C:只用于管理接口测试

必须验证:
1. A 不能操作 B 的资源。
2. B 不能操作 A 的资源。
3. A / B 不能访问管理员接口。
4. 管理员接口也要记录审计日志。
5. 重复提交创建、支付、退款,不会重复生效。

参考来源

T-Mobile says data on 37 million customers stolenAP NewsT-Mobile's $150 Million Security Plan Isn't Cutting ItWiredPeloton's leaky API let anyone grab riders' private account dataTechCrunchOWASP API Security Top 10 2023OWASPAPI1:2023 Broken Object Level AuthorizationOWASPAPI4:2023 Unrestricted Resource ConsumptionOWASPIdempotent requestsStripe DocsProblem Details for HTTP APIsIETF RFC 9457Broken Object Level Authorization in the WildarXiv

相关文章

Vibe Coding 小应用上线前要查哪些安全坑智能编程 / 约 12 分钟Docker 镜像里最容易泄露什么:.env、构建缓存、SSH key 和多阶段构建工程实践 / 约 12 分钟npm 供应链攻击后,开发者要怎么检查依赖、lockfile 和 CI 密钥工程实践 / 约 13 分钟7 个关键洞察:AI Coding 工具真正改变的不是写代码,而是验证代码智能编程 / 约 18 分钟AI Coding 的下一步:从 prompt 技巧到工程约束智能编程 / 约 18 分钟AI Agent 权限怎么设:哪些命令能自动跑,哪些必须人工确认智能编程 / 约 13 分钟如何让 AI 只改该改的文件:保障代码安全与项目完整的策略智能编程 / 约 9 分钟Claude Code 项目上下文怎么检查开发环境 / 约 11 分钟

作者信息