site logo

Marico's space

设计 CMS REST API:路由、授权与版本管理

前端技术 2026-11-04 14:48:58 4

最近折腾 CMS(内容管理系统)API(应用程序接口)时,踩了几个权限、并发和重试相关的坑,这篇把 CMS REST(表述性状态转移)API 里最容易出问题的地方说清楚。重点不在于返回 JSON(JavaScript 对象表示法),而在于如何定义权限、变更行为,以及网络异常时怎么恢复。

下面的例子描述的是一个虚构的服务。真正接入自己的 CMS 前,先核对对应版本的文档,确认它的路由和授权机制,不要直接照搬。

先建模资源,再设计接口

先从业务流程入手:谁负责创建文章,谁有权发布,标题归属于哪个服务,调用方需要草稿还是只允许读取已发布内容。所有权冲突要提前说清楚。比如商品价格既能通过 CMS 修改,也能在库存系统里调整,在同步任何一端之前,先决定发生冲突时以哪一端为准。

一个典型的资源化 API(应用程序接口)可能会提供这些操作:

  • GET /api/v1/articles:列出调用方有权查看的文章。
  • GET /api/v1/articles/42:获取一篇有权限访问的文章。
  • POST /api/v1/articles:创建文章。
  • PUT /api/v1/articles/42:按照接口契约提交完整的替换表示。
  • PATCH /api/v1/articles/42:执行接口支持的部分更新。
  • DELETE /api/v1/articles/42:请求删除;具体产品也可能定义为归档。

这些资源名只是示例,不是所有 CMS(内容管理系统)都使用同样的路由。JSON(JavaScript 对象表示法)很常见,但返回 JSON(JavaScript 对象表示法)并不等于接口就是 REST(表述性状态转移)风格的。REST(表述性状态转移)是一种带有一系列约束的架构风格,包括统一接口和无状态请求。

一个示例响应可能如下:

HTTP/1.1 200 OK
Content-Type: application/json { "id": 42, "title": "Choosing a CMS", "status": "published", "updated_at": "2026-10-20T12:00:00Z"
}
Enter fullscreen mode Exit fullscreen mode

接口文档要明确数据类型、可选字段、时间戳、分页方式和错误响应体。不要默认所有调用方都能看到所有字段。即便是负责渲染内容的客户端,也必须安全地处理 HTML(超文本标记语言)和链接。

身份认证和权限授权要分开

身份认证用来确定调用方是谁,权限授权决定这个调用方能做什么。公开访客可能只能访问已发布文章,作者可能只能编辑自己的草稿,商城系统可能只需要读取部分商品字段。

每次请求、每个对象都要在服务端执行这些规则。浏览器里隐藏编辑按钮,并不能保护接口安全。攻击者只要猜到连续递增的文章 ID(编号),就不能因此读到别人的草稿。

服务端之间的调用,要使用独立身份,并只授予刚好够用的权限。如果服务支持 Bearer Token(持有者令牌),应当通过 HTTPS(超文本传输安全协议)加密连接,在 Authorization 请求头中发送,不要放进查询字符串,否则令牌可能通过日志或浏览器历史泄露。同时要配置安全存储、轮换和撤销机制。

如果服务支持 OAuth 2.0(授权框架 2.0),可以用它完成委托授权,具体流程取决于服务提供方。

不要把管理员密钥嵌进浏览器 JavaScript(脚本语言)或移动应用里。使用服务支持的非机密客户端流程,或者接入受控的服务端中间层,并在中间层再次做权限校验。基于 Cookie(会话标识)的浏览器会话,还需要配套的 CSRF(跨站请求伪造)防护。CORS(跨域资源共享)是浏览器访问策略,不是授权系统。

把状态码当成接口契约的一部分

读取成功通常返回 200,创建成功可能返回 201,没有响应体的操作可能返回 204。输入不合法时,根据接口设计,可能返回 400 或 422。

比较有价值的失败状态包括:

  • 401:缺少可接受的认证凭据,或者凭据被拒绝。
  • 403:当前身份无权访问;有些服务为了不暴露资源存在,会返回 404。
  • 409:请求与资源当前状态冲突。
  • 412:请求前置条件失败,例如 If-Match 没有通过校验。
  • 429:触发限流;如果响应提供了 Retry-After,客户端应当遵守。

客户端应该给出有用的错误提示,但不能暴露访问令牌或内部堆栈信息。日志要保留足够的上下文,方便定位问题,同时对凭据脱敏,也不要收集没有必要的个人数据。

根据请求方法设计重试策略

请求超时不代表写入失败:服务器可能已经保存了文章,只是响应在返回途中丢了。如果无条件重发 POST,就可能创建出重复数据。

HTTP(超文本传输协议)规定,重复发送 PUT 和 DELETE 应当具有幂等性,虽然每次得到的响应可能不同。不要自行假设某个 PATCH 或 POST 可以安全重试。如果服务明确支持幂等键,就按文档使用。否则,在创建新资源前,先通过稳定的外部标识符或操作记录核对执行结果。

避免编辑内容互相覆盖

假设两位编辑同时打开同一篇草稿:一人修改标题,另一人更新导语。如果双方都提交修改前的完整内容,后一次保存可能会悄无声息地覆盖前一次修改。

乐观并发控制可以把冲突显式暴露出来。一种常见实现是在读取文章时返回强 ETag(HTTP 实体标签),客户端保存时通过 If-Match 带回该值。如果资源已经发生变化,支持这种机制的服务端可以返回 412,拒绝本次更新。客户端随后重新加载最新内容,让编辑手动合并修改。

也有一些 API(应用程序接口)使用独立的修订版本字段代替它,因此要检查接口文档,确认具体机制。

发布本身也是一项独立权限。要明确作者能否创建草稿、编辑能否审核,以及由哪个 API(应用程序接口)操作把内容公开。附件和关联数据也要遵循同一套访问规则。

安全地同步集合数据

大型商品目录应该分页读取。偏移量分页适合简单的页面导航,但两次请求之间如果发生插入或删除,行位置会变化,可能造成数据遗漏或重复。对于经常变化的集合,基于稳定排序的游标分页通常更合适,前提是服务端明确记录了它提供的保证。

做增量同步时,要确认 API(应用程序接口)如何处理相同时间戳、时区以及已删除资源。每一页成功处理后再保存检查点。通过记录稳定 ID(编号)并安全处理已经出现过的数据,让整个处理过程具备可重复性。如果服务提供 Webhook(网页钩子),要验证其真实性,并容忍重复投递;即使如此,周期性对账仍然可能有必要。

为兼容性提前规划

版本可以放在路径中,例如 /v1/,也可以放在请求头或其他有文档说明的机制里。REST(表述性状态转移)并不强制要求 URL(统一资源定位符)中包含版本号。无论采用哪种方案,都要说明支持周期和废弃流程。

删除字段、修改标识符类型、重新定义状态含义,都会破坏现有调用方。增加一个可选的新字段通常更容易,但严格的客户端仍然可能拒绝它。要拿真实调用方做测试,发布变更日志,并给它们留出迁移不兼容改动的时间。

一份实用的接入检查清单

  1. 确认每个字段的数据来源,以及允许的数据流向。
  2. 阅读真实的 API(应用程序接口)契约:路由、身份认证、错误、分页和限制。
  3. 创建一个独立接入身份,只授予最小必要权限。
  4. 在经过授权的环境中测试读取和写入,包括无权限的草稿与附件。
  5. 模拟写入后超时、重复事件、过期版本和限流。
  6. 监控故障但不记录密钥,并指定专人负责凭据轮换和版本升级。

一个可靠的 CMS(内容管理系统)接入方案,在一切正常时行为要稳定,出现异常时也要有明确的恢复路径。连接生产系统之前,先把权限、数据契约和恢复方案定下来。

俄文原文:https://morlix.ru/blog/rest-api-dlya-cms-marshruty-avtorizatsiya-i-versii

原文文章(俄语)