WordPress Integration 入站方案

第三方系统向 WordPress 推送或回调数据时使用本方案。

要解决什么问题

WordPress 内置角色(Administrator / Editor / Shop Manager / Subscriber)都不适合给第三方:

  • Administrator 的 Application Password 等于整站 REST
  • Shop Manager / Editor 能改商品、文章
  • Subscriber 几乎没写权限;若接口只检查 is_user_logged_in(),小程序 JWT 用户也能打回调口

Application Password 没有 scope:认证后等于该用户的全部 current_user_can()。因此必须:

  1. 专用角色、尽量少的能力
  2. 业务接口自己用 permission_callback 检查能力
  3. 每个第三方一个 WP 用户,密码可单独吊销

核心决策

选择原因
认证WP Application Password + HTTP Basic核心自带,第三方易对接;与 JWT Bearer 不冲突
角色integration不复用内置角色
能力仅复用核心 import管理员本就有 import,便于联调;不授予 read
命名空间integration/v1不占用 auth/v1、业务 BFF、wp/v2
权限检查is_user_logged_in() && current_user_can('import')禁止「只要登录就放行」
wp-admin进不去(符合预期)核心进后台需要 read;不必再写 admin_init 拦截

不要把生产环境的 Application Password 配在管理员账号上。

架构

Third party
  Authorization: Basic username:app_password
      │
      ▼
WP Application Passwords  →  wp_set_current_user
      │
      ▼
register_rest_route  permission_callback
  · 未登录 → 401
  · 无 import → 403
      │
      ├─ POST /uploads              → 解压到 uploads/integration/{folder}
      ├─ POST /mini-program-routes  → update_option('mini_program_routes')
      └─ POST /{payments|users|products} → 写入 integration_events,do_action

JWT 中间件只处理 Authorization: Bearer,Basic 交给核心,二者可共存。

目录与职责

本仓库对应实现(Acorn + App\ 命名空间)。移植时类名可改,职责不要混。

职责本仓库路径
角色 / 能力常量与幂等注册src/wp-bootstrap/app/Integration/Capabilities.php
initensure()src/wp-bootstrap/app/Hooks/IntegrationHooks.php
REST 注册与 permission_callbacksrc/wp-bootstrap/app/Routes/Integration/IntegrationRoute.php
入站事件落库src/wp-bootstrap/app/Services/IntegrationEventService.php
事件表 Modelsrc/wp-bootstrap/app/Models/IntegrationEvent.php
topic / 同步状态枚举src/wp-bootstrap/app/Enums/IntegrationTopic.phpIntegrationEventStatus.php
表结构src/wp-bootstrap/database/migrations/2026_09_18_020700_create_integration_events_table.php

无 Acorn 时:Hook 用普通插件 add_action('init', …);表用 dbDelta 或手工 SQL;Service 照搬即可。

移植清单

1. 角色(每个请求只在缺 cap 时写库)

add_role('integration', 'Integration', ['import' => true]);
// 已存在则:仅当 capabilities['import'] !== true 时 add_cap('import')

不要在每次 init 无条件 add_cap()WP_Roles::add_cap()update_option

不要授予 read。WordPress 仪表盘 / 个人资料都要求 read,缺它时登录 wp-admin 会得到 Sorry, you are not allowed to access this page.,这是预期,不是漏做拦截。

不要在代码里维护「禁止列表」去剥能力。额外权限交给后台(如 Members)管理。注意:若有人在后台去掉 importensure() 仍会加回来,因为接口门禁依赖它。若有人给这个角色加上 read,就能进后台。

2. REST 门禁

所有 integration/v1 路由共用:

if (!is_user_logged_in()) {
    return new WP_Error('rest_not_logged_in', 'Authentication required.', ['status' => 401]);
}
if (!current_user_can('import')) {
    return new WP_Error('rest_forbidden', 'Sorry, you are not allowed to do that.', ['status' => 403]);
}
return true;

JSON 请求体:Content-Type: application/json 时只用 get_json_params()。PHP 里 json_decode('{}')[],不要用「空数组则回落到 get_params()」,否则会把 REST 内部参数写进业务存储。

3. 入站事件表(可选,用于异步同步)

表名不要加 wp_ 前缀(若用 Acorn / $wpdb->prefix)。

说明
user_id推送方 WP 用户
topicpayments / users / products
payloadJSON
status0 未同步,1 已同步
retry默认 0,业务侧可限制 0–5
note同步日志
created_at / updated_at时间戳

接收成功返回 202 { eventId, topic },并 do_action('wp_integration_{topic}', $event, $payload)。第一期不在接收时写业务表。

运维:给第三方开账号

  1. 用管理员账号后台新建用户,角色选 Integration(不要 Administrator)
  2. 管理员打开该用户的编辑页 → Application Passwords → 创建(integration 用户自己进不了 wp-admin)
  3. 用户名 + 明文密码 交给对方(密码只显示一次)
  4. 吊销:管理员删除对应 Application Password,不必删用户

本地可用 WP-CLI:

wp user create payments-bot payments-bot@inbound.invalid --role=integration --user_pass="$(wp eval 'echo wp_generate_password(32, true, true);')"
wp user application-password create payments-bot "Payment Gateway" --porcelain

Application Password 在非 HTTPS 环境仅 WP_ENVIRONMENT_TYPE=local(或等价)时可用。

反代必须把 Authorization 传到 PHP。若站点曾用 HTTP Basic 保护整站,确认不会吞掉 REST 的 Basic 头。

安全边界

已做:

  • 专用角色 + 仅 import,不用管理员账号给生产第三方
  • 不授予 read,因此登录 wp-admin 会被核心拒绝(Sorry, you are not allowed to access this page.

未做(有意从简):

  • Application Password 不能按 path 授权。持有 import 的登录用户可以调用所有只检查 import / is_user_logged_in() 的 REST,包括本命名空间全部接口
  • 未把 integration 用户锁死在 integration/v1(曾实现过命名空间锁定,已去掉)
  • 未再写 admin_init 拦截:缺 read 已经进不去后台。若有人用 Members 等给该角色加上 read,就能进 wp-admin,且因有 import 能打开「工具 → 导入」
  • 事件接口无 schema、无幂等、无 body 大小上限

若其他项目威胁模型更严,建议加回:

  1. rest_pre_dispatch:有 integration 角色且无 manage_options 时,只允许 integration/v1
  2. admin_init 再拒绝该角色(双保险,防止被加上 read
  3. 自造 cap 替代 import,避免一旦有人补上 read 就能用「工具 → 导入」

扩展

  • 新推送类型:给 IntegrationTopic 加 case,或单独 register_rest_route
  • 真正写订单 / 用户 / 商品:监听 wp_integration_{topic},更新 status / retry / note
  • 多第三方最小权限:继续「一家一个用户」;若要按接口拆权,再拆 cap,而不是再拆角色

本仓库注册点

// Application::registerInitHooks
$container->get(\App\Hooks\IntegrationHooks::class)->load();

// Application::registerRestApiHooks
$container->get(\App\Routes\Integration\IntegrationRoute::class)->register_routes();

其他项目把这两处接到自己的 bootstrap / plugins_loaded 即可。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注