DSpace REST API 使用详解(HAL 超媒体认证、CRUD 与检索实测)
DSpace REST API 使用详解:10.1 后端逐条 curl 实测——HAL 根资源即契约(v3/api-docs 404 取证)、XSRF+JWT 双要素认证(403/401 负向实录、30 分钟 TTL 解码验证)、社区/收藏全生命周期 CRUD(201/200/PATCH/405/204/404)、dso/find 302 寻址与 discover 全文检索、eperson 隐私边界与 19 项方法-端点状态码速查。
DSpace REST API 使用详解(HAL 超媒体认证、CRUD 与检索实测)
目标:以 2026-11-01 实测的 DSpace 10.1 REST 后端(入口
http://192.168.1.200:8139/server,部署见《DSpace 10.1 Docker Compose 部署指南》)为对象,逐条 curl 实测 HAL 超媒体 API 的契约发现、XSRF+JWT 认证、社区/收藏全生命周期 CRUD 与三类检索,所有请求与回显均为实录。配置项对 API 回显的影响见《DSpace 配置参数详解(env/local.cfg/dspace.cfg 三层优先级链与关键项)》。
一、环境信息与 API 契约取证
| 项目 | 内容 |
|---|---|
| 实测日期 | 2026-11-01 |
| API 基址 | http://192.168.1.200:8139/server/api(本机 curl 走 127.0.0.1:8139) |
| 版本 | DSpace 10.1(根资源 dspaceVersion 实测回显) |
| 媒体类型 | application/hal+json;charset=UTF-8(响应头实测) |
| 认证方式 | 表单登录换 JWT Bearer + XSRF 双要素(第三、四节实测) |
| 示例账号 | admin@dspace.local / q1w2e3r4(部署篇第六节创建) |
先给一个关键取证结论——10.1 不发布 OpenAPI 契约。常规机器可读契约端点实测 404:
$ curl -s http://127.0.0.1:8139/server/v3/api-docs
{"type":"about:blank","title":"Not Found","status":404,"detail":"No static resource v3/api-docs.","instance":"/server/v3/api-docs"}
DSpace REST 的契约就是 HAL 超媒体本身:从根资源出发,沿 _links 逐层发现端点与 URI 模板(第二节)。写集成代码前不需要抓包猜路径,curl /server/api 即是"接口文档"。
二、HAL 根资源:接口即文档
$ curl -s http://127.0.0.1:8139/server/api
{
"dspaceUI" : "http://192.168.1.200:8139",
"dspaceName" : "DSpace via local.cfg",
"dspaceServer" : "http://192.168.1.200:8139/server",
"dspaceVersion" : "DSpace 10.1",
"type" : "root",
"uniqueType" : "root.root",
"_links" : {
"actuator" : { "href" : "http://192.168.1.200:8139/server/actuator" },
"authn" : { "href" : "http://192.168.1.200:8139/server/api/authn" },
"communities" : { ... },
...
}
}
三个实测要点:
- 站点标识来自配置链:
dspaceName显示 local.cfg 生效值、dspaceServer/dspaceUI显示环境变量覆写值——API 根是验证配置是否生效的最快探针(配置篇第二、五节方法)。 - 顶层链接共 85 个(实测
_links键计数),覆盖全部对象域:communities、collections、items、bitstreams、bundles、epersons、groups、resourcepolicies、workflowitems、workspaceitems、pooltasks、versions、discover、statistics、oai相关走pid/vocabularies等;管理域scripts、features、securitysettings、systemwidealerts等。 - 链接带 URI 模板,如 DSO 全局寻址链接原文:
"dso" : { "href" : "http://192.168.1.200:8139/server/api/dso/find{?uuid}" }
{?uuid} 是 RFC 6570 模板占位,拼 ?uuid=<对象ID> 即得查询(第五节实测)。
三、认证实测:XSRF 双要素 + JWT
DSpace 的写操作保护采用"Cookie + 同名请求头"双要素(CSRF 防护),登录本身也要求先取得 XSRF 上下文。完整流程三步:
3.1 先 GET 任意 API 端点,落 XSRF Cookie
$ curl -s -c dscj.txt http://127.0.0.1:8139/server/api/authn/status -o /dev/null
$ grep DSPACE-XSRF dscj.txt
#HttpOnly_127.0.0.1 FALSE /server FALSE 0 DSPACE-XSRF-COOKIE 30ce2ecb-657c-4910-a90d-cc538cd083b6
匿名状态查询确认当前未登录:
$ curl -s http://127.0.0.1:8139/server/api/authn/status
{ "type": "status", "authenticated": false, ... }
3.2 表单登录换 JWT(负向对照先测)
不带 XSRF 头直接登录——凭据正确也拒绝,实测 403:
$ curl -s -X POST http://127.0.0.1:8139/server/api/authn/login \
-H 'Content-Type: application/x-www-form-urlencoded' \
-d 'user=admin@dspace.local&password=q1w2e3r4'
{"timestamp":"2026-11-01T14:12:17.123+00:00","status":403,"error":"Forbidden","message":"Access is denied. Invalid CSRF token.","path":"/server/api/authn/login"}
带 Cookie + 同名头登录,实测 200,JWT 在响应头 Authorization 里返回,响应体为空(Content-Length: 0):
$ curl -s -b dscj.txt -X POST http://127.0.0.1:8139/server/api/authn/login \
-H "X-XSRF-TOKEN: 30ce2ecb-657c-4910-a90d-cc538cd083b6" \
-H 'Content-Type: application/x-www-form-urlencoded' \
-d 'user=admin@dspace.local&password=q1w2e3r4' -D -
HTTP/1.1 200
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJlaWQiOiJiYzAyM2RhZi01OWE1LTRiMjItOTc0Yi1kNmJlYjhhZjNmMGQiLCJzZyI6W10sImV4cCI6MTc5MzU0NDEzNywiYXV0aGVudGljYXRpb25NZXRob2QiOiJwYXNzd29yZCJ9....
解出 payload 实录:
{"eid":"bc023daf-59a5-4b22-974b-d6beb8af3f0d","sg":[],"exp":1793544137,"authenticationMethod":"password"}
exp 与登录时刻(22:12:17)相差 1800 秒——令牌有效期 30 分钟(默认 authentication.token.validity=1800)。过期后持旧令牌请求实测 HTTP 401,重新登录即可(长任务集成需自行刷新)。
3.3 持令牌验证身份
$ curl -s -H "Authorization: Bearer <token>" http://127.0.0.1:8139/server/api/authn/status
{
"id": null,
"okay": true,
"authenticated": true,
"authenticationMethod": "password",
"type": "status",
"uniqueType": "authn.status",
"_links": {
"eperson": { "href": ".../server/api/eperson/epersons/bc023daf-59a5-4b22-974b-d6beb8af3f0d" },
"specialGroups": { "href": ".../server/api/authn/status/specialGroups" },
"self": { "href": ".../server/api/authn/status" }
}
}
后续写操作统一带两个头:Authorization: Bearer <token> 与 X-XSRF-TOKEN: <cookie 值>。
四、CRUD 实测:社区与收藏全生命周期
4.1 创建社区(201)
$ curl -s -b dscj.txt -X POST http://127.0.0.1:8139/server/api/core/communities \
-H "Authorization: Bearer <token>" -H "X-XSRF-TOKEN: <xsrf>" \
-H 'Content-Type: application/json' \
-d '{"metadata":{"dc.title":[{"value":"HAL Demo Community","language":"en"}],"dc.description.abstract":[{"value":"quzuo S9 D5 evidence"}]}}'
HTTP/1.1 201
响应体即完整资源表示(实测摘录):
{ "uuid": "0f6658ff-7682-4b4e-8876-ac6d94eca2ff", "name": "HAL Demo Community",
"handle": "123456789/7", "uniqueType": "core.community", ... }
两个实测细节:① 元数据以 DC 限定集键 + 值数组表达(dc.title 是数组,支持多值/语言标注);② 201 不带 Location 头,新资源 ID 从响应体 uuid 取。handle 前缀 123456789 来自配置项 handle.prefix(配置篇第六节),序号全局递增;Handle 解析页形如 http://192.168.1.200:8139/handle/123456789/1(实测元数据 dc.identifier.uri 原文)。
4.2 读取(200)
$ curl -s -H "Authorization: Bearer <token>" .../core/communities/0f6658ff-7682-4b4e-8876-ac6d94eca2ff
HTTP 200
name= HAL Demo Community | abstract= quzuo S9 D5 evidence
4.3 更新:PATCH 是唯一正解(PUT 负向实测)
DSpace 元数据更新走 JSON Patch 风格 operations:
$ curl -s -b dscj.txt -X PATCH .../core/communities/0f6658ff-... \
-H "Authorization: Bearer <token>" -H "X-XSRF-TOKEN: <xsrf>" \
-H 'Content-Type: application/json' \
-d '[{"op":"replace","path":"/metadata/dc.title/0","value":{"value":"HAL Demo Community R2","language":"en"}}]'
HTTP/1.1 200
name now= HAL Demo Community R2
改 PUT 整资源替换?实测 405 明确告知不支持:
$ curl -s -X PUT .../core/communities ...
{"type":"about:blank","title":"Method Not Allowed","status":405,"detail":"Method 'PUT' is not supported.","instance":"/server/api/core/communities"}
4.4 收藏挂到社区下(201,parent 用查询参数)
$ curl -s -b dscj.txt -X POST ".../core/collections?parent=0f6658ff-7682-4b4e-8876-ac6d94eca2ff" \
-H "Authorization: Bearer <token>" -H "X-XSRF-TOKEN: <xsrf>" \
-H 'Content-Type: application/json' \
-d '{"metadata":{"dc.title":[{"value":"HAL Demo Collection","language":"en"}]}}'
HTTP/1.1 201
uuid= b46cc234-b2d2-42f9-969d-24f09f7f81c5 | name= HAL Demo Collection | handle= 123456789/8
层级关系即时可查(HAL 嵌入链接,实测):
$ curl -s .../core/communities/0f6658ff-.../collections
total= 1 names= ['HAL Demo Collection']
4.5 删除与删除后验证(204 → 404)
$ curl -s -b dscj.txt -X DELETE .../core/collections/b46cc234-... -H ...
collection DELETE: HTTP 204
$ curl -s -b dscj.txt -X DELETE .../core/communities/0f6658ff-... -H ...
community DELETE: HTTP 204
$ curl -s .../core/communities/0f6658ff-...
HTTP 404
删除顺序需先子后父(收藏→社区);204 无响应体,删除后原 URL 读回 404。
五、检索实测:三条路径
5.1 资源集自带 search 端点
$ curl -s http://127.0.0.1:8139/server/api/core/communities/search
['findAddAuthorized', 'findAdminAuthorized', 'findEditAuthorized', 'top']
每个资源集的可检索入口以 HAL 文档形式自描述(如 top 返回最活跃社区),无需外部文档。
5.2 DSO 全局寻址:dso/find?uuid= 用 302 跳转到规范 URL
$ curl -s -i "http://127.0.0.1:8139/server/api/dso/find?uuid=01f44fb5-b1d5-4fff-91d1-17bd6546747f"
HTTP/1.1 302
Location: http://127.0.0.1:8139/server/api/core/communities/01f44fb5-b1d5-4fff-91d1-17bd6546747f
$ curl -sL "http://127.0.0.1:8139/server/api/dso/find?uuid=01f44fb5-..."
type= core.community name= Demo Community
给一个对象 UUID、不知道类型时,这个端点统一解析(curl 需 -L 跟随)。
5.3 全文检索:discover/search/objects(Solr search core)
$ curl -s "http://127.0.0.1:8139/server/api/discover/search/objects?query=HAL&size=3"
objects total= 2
self= http://127.0.0.1:8139/server/api/discover/search/objects?query=HAL&size=3
实测 total=2 正是第四节刚建又刚删的社区+收藏(查询发生在删除前)——社区/收藏元数据即时进 Solr search core,写删链路同步索引。标准分页语义在响应 page 对象(size/totalElements/totalPages/number),分面参数与排序键可叠加(fetchqueries、spellcheck=true 等)。
六、账号端点与隐私边界
- 常理的"当前用户"端点实测 404:
$ curl -s -H "Authorization: Bearer <token>" http://127.0.0.1:8139/server/api/eperson/epersons/me
{"timestamp":"2026-11-01T13:38:29.872+00:00","status":404,"error":"Not Found","message":"An exception has occurred","path":"/server/api/eperson/epersons/me"}
正确姿势是取 authn/status 的 _links.eperson.href(第三节 3.3 实测已给出)再 GET:
$ curl -s -H "Authorization: Bearer <token>" .../eperson/epersons/bc023daf-59a5-4b22-974b-d6beb8af3f0d
name: admin user
email exposed: False
metadata keys: ['eperson.firstname', 'eperson.language', 'eperson.lastname']
- 隐私边界实测:即使是管理员自己读自己,
eperson.email也不在返回元数据中(DSpace 默认隐藏邮箱元数据,受properties.exposed类配置控制)——批量导出用户邮箱需走数据库或调整暴露配置。 - 组、权限策略、工作流等管理域端点均可从根资源
_links(groups、resourcepolicies、workflowdefinitions等)按本节同样方法发现与调用。
七、方法与端点速查(全部实测状态码)
| 操作 | 方法 + 路径 | 实测状态码 | 备注 |
|---|---|---|---|
| API 发现 | GET /server/api | 200 | 85 个顶层链接,契约即 HAL |
| OpenAPI 契约 | GET /server/v3/api-docs | 404 | 10.1 不提供 |
| 匿名状态 | GET /api/authn/status | 200 | authenticated=false,兼取 XSRF Cookie |
| 登录(无 XSRF 头) | POST /api/authn/login | 403 | Invalid CSRF token |
| 登录(Cookie+头成对) | POST /api/authn/login | 200 | JWT 在 Authorization 响应头,体空 |
| 持令牌状态 | GET /api/authn/status | 200 | authenticated=true |
| 过期令牌请求 | GET /api/eperson/epersons/<uuid> | 401 | 30 分钟 TTL |
| 建社区 | POST /api/core/communities | 201 | 体含 uuid/handle,无 Location 头 |
| 读社区 | GET /api/core/communities/<uuid> | 200 | 删除后同请求 404 |
| 改元数据 | PATCH /api/core/communities/<uuid> | 200 | operations 数组 |
| 整资源替换 | PUT /api/core/communities | 405 | Method 'PUT' is not supported |
| 建收藏(带 parent) | POST /api/core/collections?parent=<uuid> | 201 | handle 序号递增 |
| 层级嵌入 | GET /api/core/communities/<uuid>/collections | 200 | page.totalElements |
| 删除 | DELETE .../<uuid> | 204 | 先子后父 |
| 当前用户 me | GET /api/eperson/epersons/me | 404 | 改用 authn/status 的 eperson 链接 |
| DSO 寻址 | GET /api/dso/find?uuid= | 302 | curl 加 -L |
| 全文检索 | GET /api/discover/search/objects?query= | 200 | Solr search core |
| 配置读取 | GET /api/config/properties/<key> | 404 | 用 CLI dsprop 替代(配置篇) |
八、排障实录
- 403 Invalid CSRF token:写操作 403 且凭据无误时,检查 XSRF 双要素是否成对(Cookie
DSPACE-XSRF-COOKIE+ 头X-XSRF-TOKEN同值);Cookie 作用域是/server路径(实测 jar 第 4 列),跨路径不带不回。 - 405 Method 'PUT' is not supported:别找"整资源替换",DSpace 更新语义就是 PATCH operations;单值替换路径
/metadata/dc.title/0的尾号是数组下标。 - 401 但刚登录过:JWT 30 分钟过期(第三节实测 exp 差值 1800 秒),集成侧要做令牌刷新,勿长期缓存。
- epersons/me 404:非笔误,10.1 路由不提供该别名;从
authn/status._links.eperson.href拿规范地址。 - 201 后拿不到 Location:本 API 的 201 不保证
Location头(实测缺失),以响应体uuid为准。 - 检索"没结果":新建对象索引有秒级延迟,且
discover默认面向条目(Item);社区/收藏可先用dso/find或资源集search端点验证存在性。
九、参考链接
- 站内:《DSpace 介绍与选型指南》
- 站内:《DSpace 10.1 Docker Compose 部署指南》
- 站内:《DSpace 配置参数详解(env/local.cfg/dspace.cfg 三层优先级链与关键项)》
- DSpace 官方文档 DSDOC10x(REST Contract 章节,查询日期 2026-11-01):https://wiki.lyrasis.org/display/DSDOC10x
- HAL 超媒体格式规范(查询日期 2026-11-01):https://stateless.halformat.org/spec
- RFC 6570 URI Template(根资源链接模板语法,查询日期 2026-11-01):https://datatracker.ietf.org/doc/html/rfc6570