3. SCIM 协议操作与 REST 语义
SCIM 2.0(RFC 7643/7644)把身份目录抽象为一组 REST 资源,用标准 HTTP 方法表达对用户、组等对象的增删改查。理解这一层语义,是后续做账号自动化、增量同步和冲突处理的基础。本节围绕资源端点、六类核心操作、过滤语法、分页排序、属性选择以及 ETag 并发控制展开,并给出可直接对照的请求示例。
资源端点与 HTTP 方法映射
SCIM 服务通常以 /scim/v2 作为根路径,用户资源位于 /Users,组资源位于 /Groups。每个资源由 id 唯一标识,服务端还会返回 meta.location 给出完整访问地址。核心操作与 HTTP 方法的对应关系如下:
| 操作 | HTTP 方法 | 端点 | 语义 |
|---|---|---|---|
| 创建 | POST | /Users | 新建资源,返回 201 与 Location |
| 查询单个 | GET | /Users/{id} | 读取完整资源 |
| 查询列表 | GET | /Users?filter=... | 按条件检索集合 |
| 替换 | PUT | /Users/{id} | 整体覆盖,未提供属性被清空 |
| 修改 | PATCH | /Users/{id} | 按 PatchOp 局部更新 |
| 删除 | DELETE | /Users/{id} | 移除资源,返回 204 |
PUT 与 PATCH 的差异是实践中最容易出错的地方。PUT 是“替换”语义:客户端必须提交完整资源,服务端用请求体覆盖原对象,未出现的属性会被置空或恢复默认值。PATCH 是“修改”语义:请求体是一个 Operations 数组,每项包含 op(add、remove、replace)、path 和 value,只影响指定属性。例如把用户 active 置为 false,用 PATCH 更安全:
PATCH /scim/v2/Users/2819c223-7f76-453a-919d-413861904646
Content-Type: application/scim+json
{
"schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
"Operations": [
{ "op": "replace", "path": "active", "value": false }
]
}
过滤语法
列表查询通过 filter 参数表达条件,语法为 属性 操作符 值。常用操作符包括 eq(等于)、ne、co(包含)、sw(前缀)、ew(后缀)、pr(存在)、gt/ge/lt/le。多个条件用 and、or 组合,not 用于取反,括号可改变优先级。属性路径支持子属性与多值过滤,例如 emails[type eq "work"].value。典型示例:
GET /scim/v2/Users?filter=userName eq "zhangsan@corp.example.com"
GET /scim/v2/Users?filter=active eq true and meta.lastModified gt "2024-05-01T00:00:00Z"
GET /scim/v2/Groups?filter=displayName co "研发"
注意字符串值需用双引号包裹,布尔值不加引号,日期使用 ISO 8601 UTC 格式。过滤表达式在服务端解析,客户端不应假设所有实现都支持全部操作符,接入前应通过 /ServiceProviderConfig 确认 filter 能力。
分页、排序与属性选择
集合查询默认可能只返回部分结果,分页由 startIndex(从 1 开始)和 count 控制。响应中的 totalResults 表示匹配总数,itemsPerPage 表示本页条数,startIndex 回显起始位置。排序用 sortBy 指定属性、sortOrder 取 ascending 或 descending。属性选择用 attributes 列出需要的属性、excludedAttributes 排除属性,可显著减小报文体积。例如只取用户名和状态:
GET /scim/v2/Users?startIndex=1&count=50&sortBy=meta.lastModified&sortOrder=descending&attributes=userName,active
做全量同步时,建议固定 sortBy=meta.lastModified 并配合分页游标推进,避免因默认排序不稳定导致漏读或重复。若服务端返回 totalResults 大于本页条数,应继续翻页直到取完。
ETag 与并发控制
SCIM 使用 HTTP ETag 实现乐观并发控制。GET 响应头中的 ETag 代表资源当前版本,客户端在 PUT/PATCH/DELETE 时通过 If-Match 携带该值。若资源已被他人修改,服务端返回 412 Precondition Failed,客户端应重新 GET 获取最新版本后再提交。若使用 If-None-Match: *,则要求资源不存在时才执行,可用于防止重复创建。这一机制在多实例同步、并发写入场景中尤为关键。
批量操作
当需要一次提交多个变更时,使用 POST /Bulk。请求体包含 Operations 数组,每项有 method、path、bulkId 和可选的 data,并可用 failOnErrors 指定允许的最大失败数。响应按顺序返回每个子操作的状态码与结果,便于定位部分失败。批量适合初始化导入或大批量属性刷新,但要注意服务端对单次请求条数和总大小的限制。
/ServiceProviderConfig 与 /ResourceTypes,确认其支持的过滤操作符、分页上限、PATCH 能力和 ETag 支持情况,再据此设计同步策略。把上述语义串起来,一次安全的账号变更流程是:GET 读取资源并记录 ETag → 构造 PATCH 只改目标属性 → 携带 If-Match 提交 → 若返回 412 则重新 GET 后重试 → 成功后校验响应中的新 ETag 与目标属性值。这样既满足 REST 语义,也能在并发环境下保证目录数据的一致性。