SSO 解决了”用户怎么登录”,但没有解决”用户怎么出现在系统里”。一个 500 人的公司,新员工入职第一天就需要访问 15 个 SaaS 工具——IT 部门手动在每个系统里创建账号、配权限,这个过程可能持续到入职后 3-5 天。而当员工离职时,如果 IT 遗漏了任何一个系统的账号注销,登录权限就会在离职后继续存在——这是安全审计的噩梦。
SCIM(System for Cross-domain Identity Management,跨域身份管理系统,RFC 7643/7644)就是为了自动化这件事而设计的。它定义了从企业 IdP 到 SaaS SP 之间账号(User)和群组(Group)的标准化 CRUD 接口。
一、账号生命周期的四个关键时刻
flowchart LR
A["入职 (JML-Joiner)"] --> B["变岗 (Mover)"]
B --> C["休假/冻结 (Suspension)"]
C --> D["离职 (Leaver)"]
D -.-> A
| 事件 | SCIM 操作 | 业务语义 |
|---|---|---|
| 入职(Joiner) | POST /Users |
在 SP 创建用户账号,分配初始权限 |
| 变岗(Mover) | PATCH /Users/{id} 或
PUT /Users/{id} |
更新用户的部门、职位、关联群组 |
| 暂停(Suspension) | PATCH /Users/{id}
(active: false) |
临时冻结账号(如产假、出差、安全事件) |
| 离职(Leaver) | PATCH /Users/{id}
(active: false) 或
DELETE /Users/{id} |
注销账号,移除所有访问权限 |
二、SCIM 2.0 协议核心
SCIM 2.0 定义了两类 HTTP REST 端点:
/Users:用户资源的 CRUD/Groups:群组资源的 CRUD
2.1 User Schema
一个标准的 SCIM User 资源(RFC 7643, Section 4.1):
{
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
"id": "2819c223-7f76-453a-919d-413861904646",
"externalId": "employee-12345",
"userName": "zhangsan@company.com",
"name": {
"familyName": "Zhang",
"givenName": "San",
"formatted": "San Zhang"
},
"displayName": "San Zhang",
"emails": [
{"value": "zhangsan@company.com", "type": "work", "primary": true}
],
"active": true,
"groups": [
{"value": "e9e30dba-f08f-4109-8486-d5c6a331660a", "$ref": "/Groups/e9e30dba", "display": "Engineering"}
],
"meta": {
"resourceType": "User",
"created": "2026-01-15T08:30:00Z",
"lastModified": "2026-06-14T10:00:00Z",
"version": "W\/\"f250dd84f067\"",
"location": "https://sp.example.com/scim/v2/Users/2819c223"
}
}关键字段:
id:SP 内部生成的唯一标识(不可变)。externalId:IdP 侧的标识——通常是 HR 系统的员工号或 AD 的 objectGUID。这是连接 IdP 和 SP 用户的关键桥梁。userName:登录名,在 SP 中唯一。active:账号是否激活。false表示禁用但不是删除。meta.version:ETag,用于乐观并发控制。schemas:指示这个资源顺应哪些 schema——核心 schema + 可选的企业扩展。
2.2 Patch 操作:比 PUT 更关键
SCIM 的 PATCH /Users/{id}
支持三种操作类型(RFC
7644, Section 3.5.2):
| 操作 | 含义 |
|---|---|
add |
添加属性或成员 |
replace |
替换属性值(整个属性级别,不是部分修改) |
remove |
删除属性或成员 |
一个典型的”变岗”PATCH 请求:
{
"schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
"Operations": [
{
"op": "replace",
"path": "title",
"value": "Senior Engineer"
},
{
"op": "replace",
"path": "department",
"value": "Platform Engineering"
},
{
"op": "add",
"path": "groups",
"value": [
{"value": "groupId-ops", "display": "Operations"}
]
},
{
"op": "remove",
"path": "groups",
"value": [
{"value": "groupId-intern", "display": "Interns"}
]
}
]
}工程陷阱 1:
replace操作是属性级别的整体替换,不是 JSON Patch 的”修改某个嵌套字段”。如果你只想改name.givenName,需要发送"path": "name.givenName"——SCIM 支持用点号路径访问子属性。如果直接"path": "name",会替换整个name对象。
2.3 ETag 并发控制
SCIM 使用
ETag(meta.version)做乐观并发控制(RFC
7644, Section 3.13):
- 读取用户时,返回
meta.version作为 ETag。 - 更新用户时,客户端在
If-MatchHeader 中传递当前 ETag。 - SP 检查 ETag 是否匹配,不匹配则返回
409 Conflict。
这防止了多个 IdP 管理员同时修改同一个用户导致的丢失更新。
三、同步模式:Delta Sync 是唯一正确的选择
SCIM 定义了两种数据同步模式:
3.1 Full Sync(全量同步)
IdP 定期拉取 SP 的全部用户列表,与自己侧的数据做 Diff,然后推送变更。问题很明显:一个 10000 人的公司,每次全量拉取传输几 MB 数据,与 100 个 SP 对接,轮询周期导致变更延迟——效率低下。
3.2 Delta Sync(增量同步)
IdP
在每次同步后记住上次同步的时间点,下次只拉取该时间点之后变更过的资源。SCIM
的
GET /Users?filter=meta.lastModified gt "2026-06-14T08:00:00Z"
实现。
工程陷阱 2:SCIM 的 filter 语法(RFC 7644, Section 3.4.2.2)不是 SQL,不要被
filter=参数名骗了。它只支持有限的操作符:eq、ne、co、sw、ew、pr、gt、ge、lt、le,以及and/or。不支持嵌套逻辑、不支持 JOIN、不支持NOT。复杂的筛选逻辑需要 SP 自己扩展或在应用层做二次过滤。
3.3 实际同步节奏
在企业 SCIM 系统中,实际的同步模式是两个层次:
- 事件驱动 + 批量回退:IdP 在用户变更发生时立即推送 SCIM 请求到 SP(事件驱动),同时保留每日一次的全量对账(批量回退),确保没有遗漏的变更。
- 限流与重试:SP 的 SCIM 端点应该有速率限制——一个 50000 人的公司可能在某些事件(如组织重组)下触发大量同时的 SCIM 调用。IdP 收到 429 后应退避重试。
四、实际对接:与 Azure AD 和 Okta 的差异
4.1 Azure AD Provisioning
Azure AD 的 SCIM 实现要求 SP 实现特定的端点发现路径:
- 用户端点:
/scim/v2/Users - 群组端点:
/scim/v2/Groups - Schema
端点(可选但建议):
/scim/v2/Schemas - 服务配置端点(可选):
/scim/v2/ServiceProviderConfig
Azure AD 默认每 40 分钟执行一次同步周期。对于紧急变更(如离职注销),管理员可以在 Azure AD Portal 手动触发”Provision on Demand”。
Azure AD 特有的坑: - 默认不推送群组成员关系——需要在
Provisioning 配置中显式启用 “Group Provisioning”。 - 对
DELETE 操作的支持有限——Azure AD 倾向于用
PATCH active: false
软删除,而不是物理删除。
4.2 Okta Provisioning
Okta 的 SCIM 实现支持更灵活的属性映射——管理员可以在 Okta Admin Console 中自定义 Okta 用户属性到 SCIM Schema 属性之间的映射。
Okta 特有的坑: - Okta 的 SCIM 集成模板分为 “SCIM 2.0
Template” 和 “SCIM 1.1 Template”——确认客户用哪个版本。 -
Okta 的 import 功能(从 SP 拉取用户到
Okta)通常需要 SP 实现完整的 /Users GET
和分页逻辑。
五、Schema 扩展:企业用户属性
SCIM 2.0 核心 schema 的 User 属性非常基础。RFC 7643
定义了企业用户扩展
schema(urn:ietf:params:scim:schemas:extension:enterprise:2.0:User):
{
"schemas": [
"urn:ietf:params:scim:schemas:core:2.0:User",
"urn:ietf:params:scim:schemas:extension:enterprise:2.0:User"
],
"urn:ietf:params:scim:schemas:extension:enterprise:2.0:User": {
"employeeNumber": "EMP-12345",
"costCenter": "CC-BJ-ENG",
"organization": "Platform Engineering",
"division": "Infrastructure",
"department": "Security",
"manager": {
"value": "26118915-6090-4610-87e4-49d8ca9f808d",
"$ref": "/Users/26118915",
"displayName": "Li Si"
}
}
}如果你的 SP 需要特定的业务属性(如”来自中国团队”“有 AWS 管理员角色”),SCIM 允许自定义 schema 扩展,但需要和 IdP 确认对方是否支持把这些属性写入对应的 SCIM 字段。常见的情况是:SP 的自定义 schema 很丰富,但客户的 IdP(尤其是 Azure AD 和 Okta 的默认配置)只填充核心字段——你需要和客户对齐”哪些字段是一定会推送过来的”。
六、小结
SCIM 是 SSO 的”另一半”——SSO 解决”怎么进来”,SCIM 解决”谁能进来”。对于要做企业级 SaaS 的团队:
- 实现 SCIM 2.0
/UsersCRUD + PATCH + filter(至少支持userName和externalId的eq过滤器)。 - 支持 Delta Sync(基于
meta.lastModified的过滤)和分页(避免一次返回所有用户)。 - 与客户对齐属性映射——确认
userName的来源(email 还是 AD UPN)、externalId的值(员工号还是 objectGUID)、企业扩展 schema 中哪些字段会推送。 - 离职注销默认用
active: false软删除——Azure AD 和 Okta 都倾向这种模式。 - 做好限流和重试——SCIM 端点也是生产 API,需要和网关层的限流策略协同。
上一篇:SAML 还值得学吗:企业遗留 SSO 的现实世界 下一篇:JWT、JWS、JWE、JWKS 一次讲透
同主题继续阅读
把当前热点继续串成多页阅读,而不是停在单篇消费。
【身份与访问控制工程】自建还是采购:Keycloak、Auth0、Entra、Okta 对比
自建 Keycloak 省下的 license 费用,会在运维、高可用、多活、定制开发和知识积累上还回去。采购 Auth0/Okta/Entra 省下的运维成本,会在 license 账单、供应商锁定和功能黑盒上付出代价。本文不是产品推荐,而是一个工程决策框架——在什么规模、什么场景下,哪种选择的总成本(TCO)合理。
【身份与访问控制工程】IAM 全景:为什么这是高价值赛道
从 2020 年 SolarWinds 到 2024 年 Okta 支持系统泄露,身份基础设施的安全失败反复证明一件事:IAM 不是 IT 支撑系统,而是安全架构的承重墙。本文建立现代 IAM 的全景地图——从认证协议、令牌体系、权限模型到身份治理与平台选型,给出 5 个贯穿全系列的核心问题。
身份与访问控制工程
从 OIDC、OAuth 2.1、SAML、SCIM 到多租户权限、CIAM、PAM 与身份平台选型——系统拆解现代身份与访问控制的协议、架构与工程实践。
【身份与访问控制工程】企业单点登录:OIDC 与现代 SSO
OIDC 是当下企业 SSO 的事实标准,但大多数实现只用了它 20% 的规范。本文从 OIDC 核心规范出发,拆解 Authorization Code Flow + PKCE 的完整交互、ID Token 的验证规则、Discovery 与 Dynamic Registration 的互操作性机制,以及 RP-Initiated Logout 和 Session Management 的工程实现细节。