三种方式登录 WordPress REST API:Basic Auth、Application Password 与 Cookie Nonce

作者:

上周我想写个脚本把小说章节批量发布到博客,第一道坎就是认证:怎么让脚本以「我」的身份登录?WordPress REST API 官方支持好几种认证方式,但在我的站点(WordPress 7.1 + OpenResty + PHP 8.3,纯 HTTP 无 HTTPS)上实测,只有一种走得通。这篇文章记录完整过程、对比和最终方案。

一、Basic Auth:看起来最省事,实测 401

最直观的方式:把用户名密码放进 HTTP 的 Authorization 头。

curl -u 用户名:密码 \
  http://你的站点/wp-json/wp/v2/users/me

很多教程说这种方式在非 SSL 站点可用,于是我先试了它。结果是 401:

{
  "code": "rest_not_logged_in",
  "message": "您目前没有登录。",
  "data": { "status": 401 }
}

我排除了「密码里特殊字符」这个变量——手工把 Base64 头拼出来再发一次,还是 401。这个错误是 WordPress REST 的标准错误结构,说明请求确实到了 WordPress 核心,只是认证没被接受。结论:我的站点当前环境不接受这种认证方式,可能是宿主反向代理或安全策略拦了 Authorization 头,也可能是这套核心/插件组合没有启用它。原因不重要,重要的是生产流程不要依赖它。

二、Application Password:官方推荐,但要求 HTTPS

这是 WordPress 团队最希望你用的方式:

  1. 后台 → 用户 → 个人资料
  2. 拉到最下面「应用程序密码」一节
  3. 输入一个名字(比如 publish-script),点创建,会得到一串 xxxx xxxx xxxx xxxx xxxx xxxx 格式的密码

用法和 Basic Auth 一样,只是密码换成它:

curl -u 用户名:xxxx-xxxx-xxxx-xxxx-xxxx \
  https://你的站点/wp-json/wp/v2/posts

好处很实在:它是独立凭证,可随时吊销,不会暴露你的登录密码,官方文档也把它作为首选。但有一个硬条件:只在 HTTPS 下生效。我的博客目前是纯 IP + HTTP 访问,建了也用不了。所以这一条被我记进了待办:等域名和证书下来,认证整体迁过去。

三、Cookie + X-WP-Nonce:最终采用的方案

第三种方式的原理和浏览器一致:先用登录表单拿到会话 Cookie,之后每次 REST 请求带上 Cookie 加一个时效性 nonce。完整流程四步:

  1. GET /wp-login.php —— 这一步最容易被忽略。WordPress 在这个页面会种一个 test cookie,跳过它直接 POST,会收到「Cookies 被阻止或者您的浏览器不支持」
  2. POST /wp-login.php(提交 log、pwd、testcookie)——成功后拿到 wordpress_logged_in_* 会话 Cookie
  3. GET /wp-admin/post-new.php(任意加载了区块编辑器的后台页面)——页面源码里有一个 wpApiSettings JS 对象,其中的 "nonce" 字段就是需要的 X-WP-Nonce
  4. 每次 REST 请求带上 Cookie + X-WP-Nonce

PowerShell 核心实现(.NET HttpClient 会自动管理 Cookie 容器):

$handler = New-Object System.Net.Http.HttpClientHandler
$client  = New-Object System.Net.Http.HttpClient($handler)

# 1) 先 GET 登录页(拿 test cookie)
$null = $client.GetAsync('http://site/wp-login.php').GetAwaiter().GetResult()

# 2) POST 登录
$body = 'log=user&pwd=' + [uri]::EscapeDataString($pass) + '&testcookie=1'
$content = New-Object System.Net.Http.StringContent(
    $body, [System.Text.Encoding]::UTF8, 'application/x-www-form-urlencoded')
$null = $client.PostAsync('http://site/wp-login.php', $content).GetAwaiter().GetResult()

# 3) 从后台页面取 nonce
$admin = $client.GetAsync('http://site/wp-admin/post-new.php').GetAwaiter().GetResult()
$html  = $admin.Content.ReadAsStringAsync().GetAwaiter().GetResult()
$nonce = ([regex]::Match($html, '"nonce"\s*:\s*"([a-f0-9]+)"')).Groups[1].Value

# 4) 调 API
$req = [System.Net.Http.HttpRequestMessage]::new('GET', 'http://site/wp-json/wp/v2/users/me')
$req.Headers.Add('X-WP-Nonce', $nonce)
$me = $client.SendAsync($req).GetAwaiter().GetResult()

两个注意点:

  • nonce 有时效窗口。默认 tick 是 15 分钟,一个 nonce 在「当前 + 上一」两个窗口内(约 30 分钟)可重复使用。批量任务很快跑完,一次 nonce 够用;长任务要在过期前重新取一次
  • 如果用 PowerShell 的 Invoke-WebRequest -WebSession,注意 Cookie 集合解析可能不稳定(我实测拿到过名字、值全空的 Cookie)。换成 .NET HttpClient 自带的 Cookie 容器更稳

四、三种方式对比

方式 需要 HTTPS 脚本友好度 凭证生命周期 在我站的结论
Application Password 可长期,随时吊销 首选(等证书)
Cookie + Nonce nonce 约 30 分钟,cookie 按设置 当前在用
Basic Auth 理论不需要 实测 401,不可用

五、结论

  • 站点有 HTTPS:直接 Application Password。凭证可吊销、不暴露登录密码,官方推荐
  • 像我这样纯 HTTP、本机自动化:Cookie + Nonce 可行。本质是把浏览器登录过程脚本化
  • Basic Auth:当调试时的快捷手段用,别在正式流程里赌它

完整的发布脚本在下一篇(开发实战分类)讲:内容清洗、幂等设计,以及路上踩的坑。

评论

发表回复

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