Elgg 7.1.2 API 使用详解

Elgg Web Services REST API 全链路实测:端点与响应约定、api_key/HMAC 双层认证与 auth_token 用户认证、HMAC 签名算法逐字源码对照、五场景认证矩阵回显、hello_world 插件注册受保护方法(含 Elgg 7 必需 composer.json 坑)与错误码处置。

ElggElggREST APIPHP 应用2026/11/02

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
  • 演示用户: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 密钥对(管理员操作,实测)

  1. 登录 admin 后访问 /webservices/api_key/add,取表单 __elgg_ts/__elgg_token
  2. 提交动作(需带 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
  1. 在 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 即出现(本文第五节实测)。

八、参考链接

Elgg 7.1.2 API 使用详解 - Quzuo