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: *,则要求资源不存在时才执行,可用于防止重复创建。这一机制在多实例同步、并发写入场景中尤为关键。

SCIM 资源操作与并发控制 客户端 IdP / 同步引擎 SCIM 服务端 /scim/v2/Users 身份存储 用户 / 组数据 六类核心操作 POST 创建 201 GET 查询 200 PUT 替换 整体覆盖 PATCH 修改 局部 DELETE 删除 204 Bulk 批量 /Bulk 查询参数 filter=userName eq "a@corp.com" startIndex=1 & count=50 sortBy=meta.lastModified & sortOrder=descending attributes=userName,active 响应:totalResults / itemsPerPage ETag 并发控制 GET → ETag: "v7" 读取当前版本 PATCH + If-Match 携带 "v7" 提交 200 OK 新 ETag 版本推进 412 Precondition 版本冲突,需重读

批量操作

当需要一次提交多个变更时,使用 POST /Bulk。请求体包含 Operations 数组,每项有 method、path、bulkId 和可选的 data,并可用 failOnErrors 指定允许的最大失败数。响应按顺序返回每个子操作的状态码与结果,便于定位部分失败。批量适合初始化导入或大批量属性刷新,但要注意服务端对单次请求条数和总大小的限制。

接入任何 SCIM 服务前,先读取 /ServiceProviderConfig 与 /ResourceTypes,确认其支持的过滤操作符、分页上限、PATCH 能力和 ETag 支持情况,再据此设计同步策略。
PUT 会清空未提交的属性,若只想改一个字段却误用 PUT,可能把用户的邮箱、组关系等一并抹掉。局部更新优先使用 PATCH,并在提交前用 GET 确认当前完整状态。

把上述语义串起来,一次安全的账号变更流程是:GET 读取资源并记录 ETag → 构造 PATCH 只改目标属性 → 携带 If-Match 提交 → 若返回 412 则重新 GET 后重试 → 成功后校验响应中的新 ETag 与目标属性值。这样既满足 REST 语义,也能在并发环境下保证目录数据的一致性。