土法炼钢兴趣小组的算法知识备份

【身份与访问控制工程】CIAM 架构:面向 B2B / B2C SaaS 的身份平台

文章导航

分类入口
architecturesecurity
标签入口
#ciam#b2b#b2c#saas#identity#oidc#scim#gdpr#organization

目录

CIAM(Customer Identity and Access Management)是面向”客户”而非”员工”的身份系统。过去十年 SaaS 爆发,企业内部的 IAM(员工身份)与对外的 CIAM(客户身份)彻底分化:前者追求合规与可控,后者追求规模、体验与合规的三重平衡。本文系统梳理 CIAM 的工程全貌,从 B2C 的海量注册、B2B 的企业 SSO,到 B2B2C 场景下的组织模型,再到隐私合规与典型坑点。

CIAM 拓扑

一、CIAM 与员工 IAM 的根本差异

很多工程师第一次做 CIAM,都会把员工 IAM 的经验直接套用,结果在规模、体验、合规三个维度同时翻车。两者的差异不是”技术难度”的差异,而是”产品目标”的差异。

1.1 规模差异

员工 IAM 的典型规模是几百到几千人,大型企业极限也就几十万;而 CIAM 动辄百万、千万甚至亿级 MAU(月活跃用户)。规模差异直接决定架构:

这直接影响了组件拆分:Auth Service(处理登录流程,偏有状态)与 Token Service(签发/校验 JWT,偏无状态)必须独立部署,Token Service 放在靠近 CDN 的边缘节点。

1.2 用户体验优先级

员工 IAM 的用户是受雇关系,登录再麻烦也得走完;CIAM 的用户随时会流失,多一次点击可能意味着几个百分点的转化率。

一个经验法则是:CIAM 的每一个额外字段、每一次额外跳转都要用转化率数据证明其价值。

1.3 隐私合规要求

员工数据在公司控制之内,合规主要是”对内的数据安全”。而消费者数据受 GDPR、CCPA、PIPL 等法律保护,合规成为架构的一部分:

这些要求不能事后”贴补丁”,必须进入数据模型(例如 consent 表、data residency 字段)和部署拓扑(多区域、数据分区)。

1.4 社交登录

企业 IAM 通常不接第三方社交登录(员工的 Google 账号不能登录公司系统);CIAM 几乎标配 Google、Apple、GitHub、微信等社交 IdP。这带来了 Account Linking、外部 subject identifier、第三方 API 稳定性等一整套独有问题。

1.5 账号安全策略

这里的权衡从不是”更安全 vs 更便捷”,而是”风险识别是否足够精确以在大多数情况下不打扰用户”。关于授权的架构演进,可参考授权架构演进

二、B2C 特点详解

B2C(Business to Consumer)场景下,CIAM 面对的是海量陌生用户。

2.1 海量注册与登录峰值

一家中型电商大促开始的前 10 分钟,登录 QPS 可能从平时的 500 飙升到 5 万。对 CIAM 的压力体现在:

工程上的通用手段:

2.2 社交登录接入

社交登录是 B2C CIAM 的核心。工程上要处理几个问题:

  1. 协议适配:Google、Apple、GitHub 是标准 OIDC;微信是自定义协议(微信 OAuth2);Twitter/X 历史上一直在改 API。
  2. Callback URL 设计:不要给每个租户一个 callback URL,而是统一走 CIAM 的 https://login.example.com/callback/{idp},然后用 state 参数回传租户信息。
  3. Account Linking:用户先用 email 注册,后用 Google 登录,应该识别为同一人吗?这涉及安全陷阱,后面单独讨论。
  4. API 稳定性:Google、Apple 正常;微信 API 响应时间偶尔超 5 秒,必须设独立的超时策略,不要阻塞整个登录流程。

代码层面的一个典型实现:

type ExternalIdentity struct {
    Provider    string // "google", "github", "wechat"
    SubjectID   string // provider 返回的稳定 ID
    UserID      string // 指向 canonical user
    Email       string // 冗余,允许为空(Apple 首次登录才返回)
    UnionID     string // 仅微信:同一开发者账号下的跨应用唯一 ID
    LinkedAt    time.Time
}

2.3 用户自助服务

GDPR 赋予用户几项可以自助行使的权利:

2.4 渐进式画像(Progressive Profiling)

注册时只要 email(甚至允许 magic link 无密码注册),后续在用户真实使用产品时,按需收集手机号、生日、地址等。

时机设计是门艺术:

错误的时机设计会直接拉升流失率。

2.5 转化漏斗与登录失败监控

B2C CIAM 必须提供完整的漏斗分析:

访问登录页 → 点击注册 → 填写 email → 点击提交
 → 验证邮箱 → 设置密码 → 完成注册 → 首次登录

每一步的流失率都要监控。登录失败率是另一个核心指标:正常水平应该在 5%~15% 之间,如果突然飙升到 50%,几乎一定是遇到了撞库攻击或上游身份源故障。

三、B2B 特点详解

B2B(Business to Business)场景下,CIAM 的客户是企业,真正的最终用户是企业的员工。工程复杂度来自”企业客户希望用自己的方式管理用户”。

3.1 企业 SSO 要求

B2B SaaS 到了一定规模,企业客户必然会提出 SSO 需求:他们不希望员工在你的 SaaS 里单独维护账号密码,而是希望员工用自己公司的 Okta/Entra/Google Workspace 登录你的服务。

对 CIAM 而言,这意味着:

一个典型的 SSO 连接配置表:

CREATE TABLE sso_connections (
    id              UUID PRIMARY KEY,
    organization_id UUID NOT NULL,
    protocol        TEXT NOT NULL, -- 'saml' | 'oidc'
    entity_id       TEXT,
    sso_url         TEXT,
    certificate     TEXT,
    attribute_map   JSONB,
    enabled         BOOLEAN,
    created_at      TIMESTAMPTZ
);

3.2 SCIM 自动配置

有了 SSO 解决了”登录”,但企业客户还会问:我在 Okta 里给某人加了这个 SaaS 的权限,他能不能直接登录?我在 Okta 里禁用了某员工,他的账号是不是也要同步禁用?

答案是 SCIM(System for Cross-domain Identity Management)。SaaS 需要提供 SCIM 2.0 endpoint,让企业 IdP 推送用户生命周期事件:

关于 SCIM 的详细设计参考 SCIM 自动配置

3.3 审计要求

企业客户不仅自己要合规,也会要求 SaaS 提供审计能力。典型要求:

这些日志需要:

3.4 自定义域名(Custom Domain / Custom Branding)

企业客户不希望登录页是 login.saas-vendor.com,他们希望是 login.their-company.com。这意味着:

3.5 SLA 要求

B2C 用户个人登录失败,骂两句就算了;B2B 客户登录失败,可能是一整家公司几千人无法工作。SLA 要求显著提高:

四、B2B2C 模型

B2B2C(SaaS 同时服务企业和终端用户)是近几年最典型的场景。Notion、Figma、Slack 都是例子:

4.1 数据模型

核心三张表:

-- 租户(组织 / 工作区)
CREATE TABLE organizations (
    id         UUID PRIMARY KEY,
    name       TEXT,
    slug       TEXT UNIQUE,
    plan       TEXT,
    created_at TIMESTAMPTZ
);

-- canonical 用户(跨组织的唯一身份)
CREATE TABLE users (
    id            UUID PRIMARY KEY,
    primary_email TEXT,
    created_at    TIMESTAMPTZ
);

-- 成员关系(user 与 org 的 N:M)
CREATE TABLE memberships (
    user_id         UUID,
    organization_id UUID,
    role            TEXT, -- owner | admin | member
    joined_at       TIMESTAMPTZ,
    PRIMARY KEY (user_id, organization_id)
);

再加上 identity 表:

CREATE TABLE identities (
    id          UUID PRIMARY KEY,
    user_id     UUID NOT NULL,
    provider    TEXT NOT NULL, -- 'password' | 'google' | 'github' | 'saml:org-123'
    subject_id  TEXT NOT NULL, -- provider 返回的稳定 ID
    email       TEXT,
    linked_at   TIMESTAMPTZ,
    UNIQUE (provider, subject_id)
);

这样就可以支持:一个 User 有多个 Identity(email/password + Google + GitHub),同时属于多个 Organization,在不同 Organization 里有不同角色。

4.2 权限模型

B2B2C 的权限分两层:

两者必须清晰分离。关于多租户权限设计可进一步参考 多租户授权设计

4.3 典型交互场景

五、数据模型深挖

5.1 User 与 Identity 的分离

新手设计最容易犯的错:把 email、password、provider 字段全放在 users 表里。结果一个用户想绑定第二个登录方式就要重建整个 schema。

正确做法是从第一天起就把 User(canonical 身份)和 Identity(某种具体的登录凭据)分开:

5.2 Account Linking

Account Linking 决定了”不同登录方式是不是同一人”。典型策略:

  1. 显式 linking:用户登录后,在设置页主动”绑定 GitHub”。最安全。
  2. 基于 verified email 的隐式 linking:用户先用 email 注册并验证,后用 Google(返回相同 email 且 email_verified=true)登录,自动合并。
  3. 完全自动 linking:只要 email 一样就合并。这是危险的,后面单独讲。

5.3 External Identity 的 subject identifier

每个 IdP 都有自己稳定的 subject 标识:

唯一索引必须建在 (provider, subject_id) 上,而不是 email。

六、隐私合规

合规不是法务部门的事,是架构师必须理解的约束。

6.1 GDPR

适用范围:处理欧盟数据主体个人数据的所有组织(无论是否在欧盟)。

核心要求:

6.2 CCPA

美国加州法律,适用于达到一定规模的企业处理加州居民数据。核心要求:

实现上往往要在数据模型里加 region=CA 的标签,方便合规检索。

6.3 PIPL(中国个人信息保护法)

核心要求:

对架构的影响最显著:中国区用户数据不能存在海外数据库。这不是一个”加字段”的事,而是必须独立部署中国区集群,数据库物理隔离。

6.4 架构影响

GDPR 要求每一次用户同意都要可追溯。一个合规的 consent 记录表:

CREATE TABLE consents (
    id               UUID PRIMARY KEY,
    user_id          UUID,
    purpose          TEXT NOT NULL, -- 'marketing' | 'analytics' | 'third_party_share'
    policy_version   TEXT NOT NULL, -- 用户同意的条款版本号
    granted          BOOLEAN NOT NULL,
    granted_at       TIMESTAMPTZ,
    revoked_at       TIMESTAMPTZ,
    source_ip        INET,
    user_agent       TEXT,
    evidence_hash    TEXT -- 用户点击时页面内容的哈希
);

关键细节:

6.6 跨境传输的具体实现

PIPL 最严的是”跨境传输”条款。常见场景:

对应的工程处理:

七、典型架构设计

7.1 全球部署拓扑

一个成熟 CIAM 的全球部署通常包括:

登录流程大致:

用户 → Edge → 路由到对应 Region 的 Auth Service
           → 写入 Regional DB
           → 签发 JWT(region 信息编码在 claim 里)
           → 返回给用户

7.2 Token 服务独立

Token Service 独立部署的原因:

典型优化:

7.3 社交 IdP Callback 设计

常见错误:给每个租户配置独立的 redirect_uri。正确做法:

https://auth.example.com/callback/google?state=<encoded_state>

state 里编码租户 ID、原始 return URL、nonce:

type OAuthState struct {
    Nonce      string
    TenantID   string
    ReturnTo   string
    CreatedAt  int64
}

state 用 HMAC 签名或加密存在 Redis 中(TTL 5 分钟)。

7.4 登录页的 CDN 加速

登录页是用户访问频率最高的页面之一,也是转化漏斗的第一道门槛。性能优化的常见手段:

八、性能考量

8.1 认证服务的 QPS 画像

典型分布(以 1000 QPS 登录为例):

设计时要把这四类流量分开优化,不要一股脑塞给同一个服务。

8.2 Refresh Token Rotation 的热点锁问题

Refresh Token Rotation 要求:每次刷新签发新的 refresh token,同时把旧的失效。为了防重放,还要检测”旧 refresh token 被用了两次”的异常情况。

问题:如果一个用户的客户端并发调用刷新(比如多个 tab 同时刷新),会撞锁甚至判误杀。

解法:

8.3 外部 IdP 的超时处理

Google/Apple 的 token endpoint 通常 200ms 内返回;微信 API 偶尔 3-5 秒,极端情况下 10+ 秒。必须:

8.4 缓存策略

登录链路上多层缓存:

8.5 压测场景

CIAM 必做的几类压测:

九、工程坑点

9.1 Account Linking 的安全陷阱

场景:攻击者知道受害者的 email 是 victim@example.com。攻击者在你的 SaaS 上用 victim@example.com 注册一个账号(未验证 email)。受害者稍后用 Google 登录(Google 返回同样的 email)。如果你的系统基于 email 做自动 account linking,就会把 Google identity 挂到攻击者先创建的 User 上,攻击者只需登录那个 User 就能看到受害者的所有数据。

修复

9.2 GDPR “删号”操作的级联清理

一次”删号”绝不是 DELETE FROM users WHERE id = ?。真正要清理的包括:

工程上通常用”删除请求”表跟踪:

CREATE TABLE erasure_requests (
    id           UUID PRIMARY KEY,
    user_id      UUID,
    requested_at TIMESTAMPTZ,
    deadline_at  TIMESTAMPTZ, -- 通常是 requested_at + 30 天
    status       TEXT, -- pending | processing | completed | failed
    steps        JSONB -- 各子系统的删除进度
);

9.3 微信的 OpenID 与 UnionID

微信对一个用户,在不同公众号/小程序/网站应用里返回的 openid 是不同的(开发者隔离设计)。如果你的 SaaS 有微信公众号 + 小程序 + 网站登录三个入口,同一个用户会得到三个 openid。

解决方案:在微信开放平台下绑定所有应用,使用 unionid(同开发者主体下唯一)作为用户的稳定标识。

9.4 渐进式画像的时机设计

错误做法:用户刚登录就弹窗”补全资料”。效果:用户关掉页面走人。

正确做法:把画像收集嵌入到业务动作里:

关键原则:每一次字段收集都要对用户有”可见收益”

9.5 Apple Sign in 的 email 只在首次登录返回

Apple 的怪行为:用户首次用 Apple 登录时,id_token 里会有 email;第二次登录起,id_token 里不再有 email 字段(只有 sub)。

如果你没在首次登录时缓存 email,后续就没办法再拿到。

修复

9.6 被枚举的 email

几乎所有 CIAM 早期都犯过这个错:密码找回页面输入不存在的 email 时返回”用户不存在”,存在时返回”已发送重置邮件”。这让攻击者能枚举哪些 email 是你平台的用户。

修复:无论 email 是否存在,始终返回相同文案”如果该 email 已注册,重置邮件已发送”。

9.7 MFA 绕过的恢复码

用户启用了 TOTP MFA,但忘了 secret,只能靠恢复码。如果恢复码的使用流程比 TOTP 弱(比如只要 email 验证),MFA 就被削弱成了”email 安全性”的水平。

正确做法:恢复码的使用本身要求额外身份验证(账户活跃度、风险评估),并且使用后立刻轮换所有 session。

9.8 Custom Domain 的证书续签

B2B 客户使用 Custom Domain 后,证书续签是你的责任(Let’s Encrypt 90 天一次)。一旦续签失败,客户的登录页直接挂掉。必须:

9.9 SSO 连接配置的”幽灵管理员”

B2B 场景下,一个企业客户在你的 SaaS 上有多个管理员。如果只允许一个管理员配置 SSO,这个人离职后 SSO 配置就无人维护。更糟的情况:SSO 配置错误导致所有员工无法登录,而能改配置的人恰好也在被锁在外面。

修复:

9.10 JWT claim 膨胀

新手喜欢把用户所有属性都塞到 JWT claim 里,结果 JWT 动辄 8KB,HTTP header 被打爆。

修复:

9.11 移动端的 Refresh Token 保管

Web 端 refresh token 放 HttpOnly cookie 就行,移动端却不能享受这个安全模型。常见错误:直接把 refresh token 存 SharedPreferences / NSUserDefaults,root 设备轻易读取。

正确做法:

10.1 典型产品画像

10.2 选型建议

纯 B2C 初创(<100 万 MAU):

B2C 规模化(>100 万 MAU):

B2B SaaS(需要企业 SSO / SCIM):

B2B2C(同时做个人和企业):

特殊要求(中国区 PIPL 合规)

完整的自建 vs 采购决策框架参考 自建还是采购

10.3 架构演进路径

大多数团队的 CIAM 演进路径是:

  1. MVP 阶段:托管服务(Firebase / Supabase / Clerk)快速上线。
  2. 规模化阶段:替换为 Auth0 或自建,加 MFA、加审计。
  3. B2B 化阶段:加 SSO、SCIM、Custom Domain、SLA。
  4. 全球化阶段:多区域部署、PIPL 合规、区域化数据分区。

每次架构升级都对应一次用户规模或商业模式的跳跃。关键是在每一阶段留好升级的接口(至少 User/Identity 分离、Organization 作为一等公民)。

十一、参考资料

十二、小结

CIAM 不是员工 IAM 的”加个注册页”。它是一个同时承担 UX、规模、隐私合规、多租户组织模型、全球部署的大型系统。关键的架构决策:

CIAM 的工程难度在于每一层决策都牵动其他层:你选 Auth0 就要接受它的数据模型;你选自建就要承担合规的长期投入;你做 B2B2C 就要从一开始把组织模型设计好。这类系统最怕的是”先凑合用,回头再重构”——身份数据的迁移几乎不可能做到无缝。


上一篇自建还是采购:Keycloak、Auth0、Entra、Okta 对比

下一篇PAM、IGA 与审计合规

同主题继续阅读

把当前热点继续串成多页阅读,而不是停在单篇消费。

2026-04-21 · architecture / security

身份与访问控制工程

从 OIDC、OAuth 2.1、SAML、SCIM 到多租户权限、CIAM、PAM 与身份平台选型——系统拆解现代身份与访问控制的协议、架构与工程实践。

2026-04-21 · architecture / security

【身份与访问控制工程】IAM 全景:为什么这是高价值赛道

身份与访问控制从一个登录框演进为横跨合规、运维、平台工程和安全的系统工程。本文从一家 SaaS 公司被大客户卡在 SOC 2 合规的真实触发器切入,拆解 IAM、CIAM、IGA、PAM、SSO、目录服务六个子领域的边界,分析 Okta、Entra ID、Auth0、Keycloak、Ping 等主流厂商的定位与落差,给出工程师视角的介入判据与选型路径。

2026-04-21 · architecture / security

【身份与访问控制工程】B2B SaaS 多租户权限设计

B2B SaaS 的权限问题远比 B2C 复杂:多个企业客户、各自的内部角色体系、跨租户协作、行列级数据权限、租户自助管理。本文从隔离模型出发,给出租户内 RBAC + 租户间 ReBAC 的混合方案、超级管理员设计、行级权限实现,以及 GitHub、Slack、Notion 的权限模型速览。


By .