Elgg 7.1.2 API 使用详解
Elgg Web Services REST API 全链路实测:端点与响应约定、api_key/HMAC 双层认证与 auth_token 用户认证、HMAC 签名算法逐字源码对照、五场景认证矩阵回显、hello_world 插件注册受保护方法(含 Elgg 7 必需 composer.json 坑)与错误码处置。
Elgg 7.1.2 API 使用详解
目标:实测讲透 Elgg 自带 Web Services REST API——端点形态、api_key/HMAC 两种 API 认证与 auth_token 用户认证的完整链路、内置方法与自研插件方法的注册和调用,并给出五场景认证矩阵与错误码对照。所有请求与响应 JSON 均为 7.1.2 真实站点逐字回显。
一、环境信息
- Elgg:7.1.2,站点
http://192.168.1.172:8148/(部署形态见《部署篇》) - web_services 插件:默认激活(
plugins:list实测web_services Active) - 演示 API 密钥对(管理员在站内创建,实测值):
- public key:
d24657527dc617211753e66e5620eaf4dd8804bb - secret key:
aab9e73d179ff20c8327f98d6ccc69260376ea8a
- public key:
- 演示用户:admin / q1w2e3r4q1w2e3r4
- 客户端工具:curl + 任意 PHP 运行时(用于计算 HMAC 签名)
- 自研示例插件:
hello_world(注册受保护 REST 方法hello.world,源码见第五节)
二、前置条件
2.1 端点
REST 端点为 /services/api/rest/{view},view 常用 json:
GET/POST http://<站点>/services/api/rest/json?method=<方法名>&<参数>
2.2 创建 API 密钥对(管理员操作,实测)
- 登录 admin 后访问
/webservices/api_key/add,取表单__elgg_ts/__elgg_token - 提交动作(需带 Referer 头):
curl -s -b cookies.txt -c cookies.txt -H "X-Requested-With: XMLHttpRequest" \
-H "Referer: http://192.168.1.172:8148/webservices/api_key/add" \
-F "title=Integration Key" -F "__elgg_ts=$TS" -F "__elgg_token=$TK" \
http://192.168.1.172:8148/action/webservices/api_key/edit
- 在
admin/configure_utilities/ws_tokens列表页查看;secret 默认隐藏,位于管理员可见的隐藏块div.webservices-secret-<guid>(对象视图object/api_key.php实测逻辑),展开即得本文第一节密钥对。
2.3 客户端签名工具
HMAC 需要按服务端算法签名,一行 PHP 即可(在应用环境或任何 PHP 8 环境执行):
php -r 'echo urlencode(base64_encode(hash_hmac("sha256", $argv[1].$argv[2].$argv[3].$argv[4], $argv[5], true)));' \
"$TIME" "$NONCE" "$PUB" "$QUERY_STRING" "$SECRET"
三、API 总览
3.1 协议与响应约定
- 传输:HTTP GET/POST,参数走查询串(GET)或表单(POST)
- 成功:
{"status":0,"result":<数据>} - 失败:
{"status":<负数码>,"message":"<原因>"},实测常见:-32(API 认证/HMAC 错误)、-20(用户认证失败)
3.2 认证分层
Elgg REST 认证是两层独立的门(方法注册时各自声明):
| 层 | 参数/开关 | 说明 |
|---|---|---|
| API 认证(require_api_auth) | api_key 查询参数,或 HMAC 五头组 | 证明"哪个调用方" |
| 用户认证(require_user_auth) | auth_token 参数(由 POST:auth.gettoken 换取) | 证明"哪个用户",通过后该用户以登录态执行方法 |
HMAC 头组(服务端 PAM/API/Hmac.php 逐字校验):
X-Elgg-apikey: <public key>
X-Elgg-time: <unix 秒>
X-Elgg-nonce: <随机串>
X-Elgg-hmac-algo: sha256
X-Elgg-hmac: <签名>
签名算法(源码 mod/web_services/lib/web_services.php:68-83):
hmac = urlencode(base64( hash_hmac(algo, time + nonce + api_key + QUERY_STRING [+ posthash], secret) ))
要点:QUERY_STRING 是原样查询串(服务端取 $_SERVER['QUERY_STRING'] 参与计算);POST 需额外 X-Elgg-posthash(对请求体的 sha1)并计入签名;时间漂移容限 ±90000 秒;同一签名 25 小时内防重放(elgg_ws_cache_hmac_check_replay)。HMAC 认证默认开启(web_services 设置 auth_allow_hmac=1)。
3.3 内置方法清单(实测 system.api.list 节选)
{"status":0,"result":{"POST:auth.gettoken":{"description":"This API call lets a user obtain a user authentication token which can be used for authenticating future API calls. Pass it as the parameter auth_token","parameters":{"username":{"type":"string","required":true},"password":{"type":"string","required":true}},"call_method":"POST","require_api_auth":false,"require_user_auth":false,"assoc":false},...
内置方法均 require_api_auth=false,即不强制调用方认证(实测无效签名调用 system.api.list 依然 200)。
四、认证与首次调用(curl 实测)
4.1 匿名调用
curl -s "http://192.168.1.172:8148/services/api/rest/json?method=system.api.list" | head -c 300
# {"status":0,"result":{"POST:auth.gettoken":...
4.2 换取用户 auth_token
curl -s -X POST "http://192.168.1.172:8148/services/api/rest/json?method=auth.gettoken" \
-d "username=admin&password=q1w2e3r4q1w2e3r4"
# {"status":0,"result":"86d076408f19cb73781b113d99a52c68"}
后续调用带 auth_token=<该值> 即完成用户层认证(实测该 token 用于 4.3 全链路调用成功)。
4.3 调用受保护方法(五场景矩阵,逐字回显)
对自研方法 hello.world(require_api_auth=true + require_user_auth=true)实测:
=== A. 什么都不带 ===
{"status":-32,"message":"Method call failed the API Authentication"}
=== B. 仅 HMAC 五头 ===
{"status":-20,"message":"User could not be authenticated"}
=== C. HMAC 五头 + auth_token 参数 ===
{"status":0,"result":{"greeting":"Hello, World!","authenticated_user":"admin","site":"Elgg Community"}}
=== E. 有效 HMAC + 无效 auth_token ===
{"status":-20,"message":"User could not be authenticated"}
=== D. 错误 HMAC ===
{"status":-32,"message":"HMAC is invalid. AAAAbbbbCCCC != [calc]fp%2BdwCvSgJHi0ngjCkrh4IYvu43u1emrrjESKknWWPs%3D"}
场景 C 完整命令(实测脚本 hello_test.sh 节选):
QS="method=hello.world&name=World&auth_token=$TOKEN"
TIME=$(date +%s); NONCE="f$RANDOM$TIME"
HMAC=$(php -r 'echo urlencode(base64_encode(hash_hmac("sha256", $argv[1].$argv[2].$argv[3].$argv[4], $argv[5], true)));' "$TIME" "$NONCE" "$PUB" "$QS" "$SEC")
curl -s -H "X-Elgg-apikey: $PUB" -H "X-Elgg-time: $TIME" -H "X-Elgg-nonce: $NONCE" \
-H "X-Elgg-hmac-algo: sha256" -H "X-Elgg-hmac: $HMAC" \
"http://192.168.1.172:8148/services/api/rest/json?$QS"
注意:签名对象是整条查询串,加入 auth_token 后必须重新签名;浏览器会话 cookie 对 REST 用户认证无效(实测带 cookie 不带 auth_token 返回 -20,用户认证只认 auth_token 参数,源码 PAM/User/AuthToken.php)。
五、自定义 REST 方法(插件注册,实测)
Elgg 7 中第三方 REST 方法通过插件配置数组 web_services 键注册。示例插件 mod/hello_world/ 三件套:
composer.json(Elgg 7 必需,缺失会导致插件静默不可用,排障见运维篇 7.1):
{
"name": "quzuo/hello_world",
"type": "elgg-plugin",
"description": "Hello World example plugin exposing a protected REST method",
"license": "GPL-2.0-only"
}
elgg-plugin.php:
<?php
return [
'plugin' => ['name' => 'Hello World'],
'web_services' => [
'hello.world' => [
'GET' => [
'callback' => \Elgg\HelloWorld\HelloApi::class,
'description' => 'Returns a greeting and the authenticated user name',
'params' => ['name' => ['type' => 'string', 'required' => false]],
'require_api_auth' => true,
'require_user_auth' => true,
],
],
],
];
classes/Elgg/HelloWorld/HelloApi.php:
<?php
namespace Elgg\HelloWorld;
class HelloApi {
public function __invoke(string $name = 'world'): \GenericResult {
$user = elgg_get_logged_in_user_entity();
return \SuccessResult::getInstance([
'greeting' => "Hello, {$name}!",
'authenticated_user' => $user ? $user->username : null,
'site' => elgg_get_site_entity()->name,
]);
}
}
激活并验证(实测):
php vendor/bin/elgg-cli plugins:activate hello_world
# Activating plugins
# 1/1 [============================] 100%
php vendor/bin/elgg-cli cache:clear
curl -s "http://192.168.1.172:8148/services/api/rest/json?method=system.api.list" | grep -o '"GET:hello.world":{[^}]*}'
# "GET:hello.world":{"description":"Returns a greeting and the authenticated user name","parameters":{"name":{"type":"string","required":false}},...
方法即出现在 API 清单,四、4.3 的五场景全部按声明生效。
5.1 参数与返回约定
params声明类型与 required,服务端自动过滤未声明参数- 回调返回
\GenericResult:SuccessResult::getInstance($data)对应status:0;错误路径用ErrorResult - 回调内可直接使用全部 Elgg API(登录态、实体读写),用户层认证通过后
elgg_get_logged_in_user_entity()即调用者(实测返回admin)
六、分页/过滤/排序通用约定
实测结论:Elgg REST 没有统一的内建分页/过滤协议。内置 system.api.list、auth.gettoken 不涉及列表;检索类能力(elgg_get_entities 系列)只在 PHP 层提供 limit/offset/order_by_metadata 等参数,未默认暴露为 REST 方法。自研列表方法需自行约定参数(如 limit/offset/container_guid)并在 params 中声明,返回体内携带总数。对接第三方 Elgg 站点时先 system.api.list 枚举可用方法与参数,这是唯一可靠的"接口文档"。
七、常见问题
Q1: 调用受保护方法报 Method call failed the API Authentication?
A: 未带 API 认证。要么加 api_key=<public> 查询参数(APIKey 策略),要么按四、4.3 组 HMAC 五头。确认 web_services 设置中允许对应策略。
Q2: HMAC 报 HMAC is invalid. X != [calc]Y?
A: 对照服务端错误里给出的 [calc] 值排查拼接:顺序必须是 time.nonce.api_key.QUERY_STRING[.posthash] 无分隔符直连;QUERY_STRING 用原始串(不要自行排序/重编码);输出需 urlencode(base64(...)) 原样放头(服务端比对的是 urlencode 后的串)。
Q3: 签名正确却报 DupePacket?
A: 命中防重放:同一 HMAC 串只能用一次,重试请更换 nonce/time 重新签名。
Q4: 明明带了登录会话 cookie,REST 仍报 -20?
A: 设计如此。REST 用户认证只认 auth_token 参数(auth.gettoken 换取),浏览器 cookie 不参与(实测场景 B/C 对比)。
Q5: 插件里注册了方法但 system.api.list 看不到?
A: 九成是插件未被识别。先跑 plugins:list:若零输出且退出码 2,是缺 composer.json 的静默崩溃(运维篇 7.1 实录);补齐后 plugins:activate + cache:clear 即出现(本文第五节实测)。
八、参考链接
- Web Services 文档:https://docs.elgg.org/Web_Services.html
- 插件开发:https://docs.elgg.org/Plugin_Guide.html
- 源码:vendor/elgg/elgg/mod/web_services(PAM/API/Hmac.php、lib/web_services.php)
- 本专栏:《认知篇》、《部署篇》、《运维篇》