CIAM(Customer Identity and Access Management)是面向”客户”而非”员工”的身份系统。过去十年 SaaS 爆发,企业内部的 IAM(员工身份)与对外的 CIAM(客户身份)彻底分化:前者追求合规与可控,后者追求规模、体验与合规的三重平衡。本文系统梳理 CIAM 的工程全貌,从 B2C 的海量注册、B2B 的企业 SSO,到 B2B2C 场景下的组织模型,再到隐私合规与典型坑点。
一、CIAM 与员工 IAM 的根本差异
很多工程师第一次做 CIAM,都会把员工 IAM 的经验直接套用,结果在规模、体验、合规三个维度同时翻车。两者的差异不是”技术难度”的差异,而是”产品目标”的差异。
1.1 规模差异
员工 IAM 的典型规模是几百到几千人,大型企业极限也就几十万;而 CIAM 动辄百万、千万甚至亿级 MAU(月活跃用户)。规模差异直接决定架构:
- 员工 IAM 可以用单实例 Keycloak + 一个 Postgres 抗住全公司登录。
- CIAM 在大促、春节、重要社会事件时会经历 10 倍甚至 100 倍的突发流量。注册/登录 QPS 峰值可能达到 10 万级别,且集中在几分钟内。
这直接影响了组件拆分:Auth Service(处理登录流程,偏有状态)与 Token Service(签发/校验 JWT,偏无状态)必须独立部署,Token Service 放在靠近 CDN 的边缘节点。
1.2 用户体验优先级
员工 IAM 的用户是受雇关系,登录再麻烦也得走完;CIAM 的用户随时会流失,多一次点击可能意味着几个百分点的转化率。
- 员工 IAM:强制 MFA、强制改密码、强制法律声明,用户不能拒绝。
- CIAM:MFA 大多是”鼓励启用”而不是强制;注册表单字段要极度精简(理想状态就是一个 email 或一个手机号);社交登录按钮的顺序、颜色都要 A/B 测试。
一个经验法则是:CIAM 的每一个额外字段、每一次额外跳转都要用转化率数据证明其价值。
1.3 隐私合规要求
员工数据在公司控制之内,合规主要是”对内的数据安全”。而消费者数据受 GDPR、CCPA、PIPL 等法律保护,合规成为架构的一部分:
- GDPR:合法基础(consent 记录)、数据可携带、被遗忘权。
- CCPA:California 用户的 opt-out of sale、数据访问权。
- PIPL:中国用户个人信息境内存储、跨境传输需要安全评估。
这些要求不能事后”贴补丁”,必须进入数据模型(例如 consent 表、data residency 字段)和部署拓扑(多区域、数据分区)。
1.4 社交登录
企业 IAM 通常不接第三方社交登录(员工的 Google 账号不能登录公司系统);CIAM 几乎标配 Google、Apple、GitHub、微信等社交 IdP。这带来了 Account Linking、外部 subject identifier、第三方 API 稳定性等一整套独有问题。
1.5 账号安全策略
- 企业 IAM:MFA 强制、密码复杂度策略、登录设备绑定、会话超时强制。
- CIAM:MFA 作为”账号保护”的可选项推销;风险登录时才触发二次验证(自适应 MFA)。强制用户在登录时做 MFA 会直接影响日活。
这里的权衡从不是”更安全 vs 更便捷”,而是”风险识别是否足够精确以在大多数情况下不打扰用户”。关于授权的架构演进,可参考授权架构演进。
二、B2C 特点详解
B2C(Business to Consumer)场景下,CIAM 面对的是海量陌生用户。
2.1 海量注册与登录峰值
一家中型电商大促开始的前 10 分钟,登录 QPS 可能从平时的 500 飙升到 5 万。对 CIAM 的压力体现在:
- 数据库主键冲突、唯一索引抢锁。
- Session/Token 写入风暴。
- 邮件/短信发送通道被打爆(验证码、找回密码)。
工程上的通用手段:
- 认证服务无状态化:登录成功后的结果只通过 JWT 返回,不依赖服务端 session。
- Session 存储用 Redis 集群,分 shard、做冷热分层。
- 验证码发送异步化:前端先显示”已发送”,后端走队列批量出站。
- 登录失败/图形验证码触发阈值:失败率升高时自动打开验证码,避免撞库。
- 数据库层面的热点优化:用户表按 user_id hash 分片,或用 CockroachDB 这类天然分布式数据库。
2.2 社交登录接入
社交登录是 B2C CIAM 的核心。工程上要处理几个问题:
- 协议适配:Google、Apple、GitHub 是标准 OIDC;微信是自定义协议(微信 OAuth2);Twitter/X 历史上一直在改 API。
- Callback URL 设计:不要给每个租户一个
callback URL,而是统一走 CIAM 的
https://login.example.com/callback/{idp},然后用 state 参数回传租户信息。 - Account Linking:用户先用 email 注册,后用 Google 登录,应该识别为同一人吗?这涉及安全陷阱,后面单独讨论。
- 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 赋予用户几项可以自助行使的权利:
- 自助密码重置:经典流程,但要防止枚举攻击(不管 email 是否存在,响应都一样)。
- 账号注销:用户点击”注销账号”后,必须在合理时间内(GDPR 建议 30 天)完成数据删除。
- 数据导出:提供机器可读的 JSON/CSV 导出(data portability)。
- Consent 撤回:用户可以撤回之前同意的条款,撤回要记录。
2.4 渐进式画像(Progressive Profiling)
注册时只要 email(甚至允许 magic link 无密码注册),后续在用户真实使用产品时,按需收集手机号、生日、地址等。
时机设计是门艺术:
- 正确:在用户要下订单时,要求填地址;在开启 MFA 时要求绑手机。
- 错误:登录后弹窗要求”补全资料”;在用户刚注册就 5 个必填字段。
错误的时机设计会直接拉升流失率。
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 而言,这意味着:
- 必须支持 SAML 2.0(传统企业市场)和 OIDC(新兴企业市场)。
- 必须支持 per-tenant 的 IdP 配置:每个企业客户独立配置自己的 IdP 元数据、证书、映射规则。
- 必须支持 IdP-initiated 和 SP-initiated 两种登录入口。
一个典型的 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 推送用户生命周期事件:
POST /Users:创建新用户。PATCH /Users/{id}:更新用户属性。DELETE /Users/{id}:删除用户(通常是软删除/停用)。POST /Groups:创建组(对应 role 或 permission group)。
关于 SCIM 的详细设计参考 SCIM 自动配置。
3.3 审计要求
企业客户不仅自己要合规,也会要求 SaaS 提供审计能力。典型要求:
- 用户登录日志(谁、什么时候、从哪个 IP、用哪种方式)。
- 权限变更日志(谁给谁加了什么权限)。
- 管理操作日志(谁改了 SSO 配置、谁删了用户)。
这些日志需要:
- 不可篡改(append-only)。
- 可以按 org 维度导出(SIEM 集成)。
- 保留时间满足 SOC 2 / ISO 27001 的要求(通常至少 1 年)。
3.4 自定义域名(Custom Domain / Custom Branding)
企业客户不希望登录页是
login.saas-vendor.com,他们希望是
login.their-company.com。这意味着:
- CIAM 必须支持客户自带域名,并自动签发证书(Let’s Encrypt 或客户上传)。
- 登录页要支持品牌化:logo、配色、字体。
- cookie 域名策略要相应调整。
3.5 SLA 要求
B2C 用户个人登录失败,骂两句就算了;B2B 客户登录失败,可能是一整家公司几千人无法工作。SLA 要求显著提高:
- 可用性承诺:4 个 9(99.99%)是起步,5 个 9 是进阶。
- 维护窗口:B2B 客户分布在全球,没有”夜里没人用”的概念。
- 降级策略:SSO IdP 不可用时,是否允许 fallback 到本地账号?这又是安全和可用性的权衡。
四、B2B2C 模型
B2B2C(SaaS 同时服务企业和终端用户)是近几年最典型的场景。Notion、Figma、Slack 都是例子:
- 用户可以个人注册(C 端)。
- 用户可以被企业邀请进入企业工作区(B 端)。
- 同一个用户可能同时属于多个企业工作区,也可能有自己的个人工作区。
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 的权限分两层:
- Org-level role:你在这个 Organization 里是 owner、admin 还是 member。这决定了你能不能改 Org 设置、能不能邀请人。
- App-level role / resource role:你对某个具体资源(比如某个 project、某个 document)有什么权限。这是业务权限。
两者必须清晰分离。关于多租户权限设计可进一步参考 多租户授权设计。
4.3 典型交互场景
- 个人用户 A 用 Google 登录,创建个人工作区 W1。
- 企业 X 邀请 A 加入企业工作区 W2。A 的身份不变,memberships 表里新增一行。
- 企业 X 开启 SAML SSO 后,A 再次登录必须走 SSO。但 A 还可以用 Google 登录访问 W1。
- A 注销自己的账号:W1 直接删除,W2 里 A 的行为需要归档(企业客户的审计要求),membership 解除。
五、数据模型深挖
5.1 User 与 Identity 的分离
新手设计最容易犯的错:把 email、password、provider 字段全放在 users 表里。结果一个用户想绑定第二个登录方式就要重建整个 schema。
正确做法是从第一天起就把 User(canonical 身份)和 Identity(某种具体的登录凭据)分开:
- User:业务意义上的”一个人”。
- Identity:这个人用过的某一种登录方式(email+密码、Google、GitHub、企业 SSO)。
5.2 Account Linking
Account Linking 决定了”不同登录方式是不是同一人”。典型策略:
- 显式 linking:用户登录后,在设置页主动”绑定 GitHub”。最安全。
- 基于 verified email 的隐式 linking:用户先用 email 注册并验证,后用 Google(返回相同 email 且 email_verified=true)登录,自动合并。
- 完全自动 linking:只要 email 一样就合并。这是危险的,后面单独讲。
5.3 External Identity 的 subject identifier
每个 IdP 都有自己稳定的 subject 标识:
- Google:
sub(永不变)。 - GitHub:
id(数字,永不变);不要用login(用户名可改)。 - Apple:
sub(永不变);email 在首次登录时返回,后续不再返回,必须缓存。 - 微信:
openid针对单个应用,unionid针对同一开放平台主体下所有应用。
唯一索引必须建在 (provider, subject_id)
上,而不是 email。
六、隐私合规
合规不是法务部门的事,是架构师必须理解的约束。
6.1 GDPR
适用范围:处理欧盟数据主体个人数据的所有组织(无论是否在欧盟)。
核心要求:
- 合法基础:处理个人数据必须有六种合法基础之一(consent、contract、legal obligation、vital interests、public task、legitimate interests)。对 CIAM 最常见的是 consent 和 contract。
- Consent 记录:用户同意的时间、版本、IP、UA 都要记录,且不可删除(即使用户注销,consent 记录本身要保留以证明合规)。
- Data Portability:用户有权获得自己数据的机器可读导出。
- Right to Erasure(被遗忘权):用户有权要求删除自己的数据,除非有法律保留义务。
- DPA(Data Processing Agreement):如果 CIAM 是作为 data processor 为 B2B 客户处理数据,必须与客户签署 DPA。
6.2 CCPA
美国加州法律,适用于达到一定规模的企业处理加州居民数据。核心要求:
- 用户有权知道你收集了什么、为什么、卖给了谁。
- 用户有权 opt-out of sale(不被卖数据)。
- 需要在网站上提供显著的 “Do Not Sell My Personal Information” 链接。
实现上往往要在数据模型里加 region=CA
的标签,方便合规检索。
6.3 PIPL(中国个人信息保护法)
核心要求:
- 本地化存储:关键信息基础设施和处理大量个人信息的主体,个人信息必须境内存储。
- 跨境传输评估:对外提供个人信息需要通过网信办安全评估、或签订标准合同、或通过保护认证。
- 单独同意:敏感个人信息(生物识别、宗教、医疗等)需要单独同意。
对架构的影响最显著:中国区用户数据不能存在海外数据库。这不是一个”加字段”的事,而是必须独立部署中国区集群,数据库物理隔离。
6.4 架构影响
- 数据分区:按 region 划分数据库集群。用户的 region 在注册时确定(基于 IP + 显式选择),之后不能随便迁移。
- Consent 不可删除:即使用户行使被遗忘权,consent 记录本身要保留(作为合规证据),但要剥离个人身份信息。
- 删号 = 级联清理:下面单独讨论。
6.5 Consent 记录的数据模型
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 -- 用户点击时页面内容的哈希
);关键细节:
- 不做 UPDATE,只做 INSERT:每次同意/撤回都是新行,历史完整保留。
- 用户删号时 consent 记录不能删,要做 pseudonymization:把 user_id 替换为随机 ID,其余字段保留作为合规证据。
- policy_version 必须精确到具体文本版本,否则无法证明”用户当时同意的是这个内容”。
6.6 跨境传输的具体实现
PIPL 最严的是”跨境传输”条款。常见场景:
- 中国区用户在海外登录(需要把验证码发到国内手机)。
- 海外分析系统想看中国区用户的行为数据。
- 全球客服系统需要查看中国区用户的工单。
对应的工程处理:
- 最小化传输:只传必要的字段,敏感字段(手机号、身份证)留在境内。
- 加密传输 + 密钥境内托管:数据走海外链路也要保证海外方无法解密。
- 审计所有跨境调用:每次跨境数据访问都要留审计日志。
- Proxy 模式:海外服务不直接访问中国区 DB,而是通过中国区部署的代理服务,代理服务负责脱敏和合规审查。
七、典型架构设计
7.1 全球部署拓扑
一个成熟 CIAM 的全球部署通常包括:
- Global Control Plane:租户元数据、全局配置。
- Regional Data Plane:每个区域(US、EU、APAC、CN)独立的用户数据库、认证服务。
- CDN 层:托管静态登录页资源(JS、CSS、图片)。
- Edge 认证节点:靠近用户、快速响应 token 校验。
登录流程大致:
用户 → Edge → 路由到对应 Region 的 Auth Service
→ 写入 Regional DB
→ 签发 JWT(region 信息编码在 claim 里)
→ 返回给用户
7.2 Token 服务独立
Token Service 独立部署的原因:
- QPS 高:每个 API 请求都要校验 JWT。
- 无状态:签发和校验都只依赖密钥和 claim。
- 可缓存:公钥可以缓存几小时。
典型优化:
- JWKS endpoint 缓存:
/.well-known/jwks.json设Cache-Control: max-age=3600。 - RS256 或 ES256 签名:允许资源服务独立校验,不需要每次回 Token Service。
- Key rotation:每 90 天自动轮换签名密钥,新旧密钥并存。
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 加速
登录页是用户访问频率最高的页面之一,也是转化漏斗的第一道门槛。性能优化的常见手段:
- 静态资源走 CDN:HTML、JS、CSS、字体、logo 全部 CDN 化。
- 登录页做 SSR + hydration:首屏 HTML 服务端渲染,首屏 LCP 控制在 1 秒内。
- 按 region 选择 CDN 节点:欧洲用户走 CloudFront EU,中国用户走腾讯云 CDN。
- Preconnect 到 Token Service:在登录页
head 里
<link rel="preconnect" href="https://token.example.com">,节省首次握手时间。
7.5 Session 管理的 Cookie 策略
- HttpOnly + Secure + SameSite:防 XSS 窃取、防 CSRF。
- Cookie scope:B2B Custom Domain 场景下,cookie 域要设在客户的自定义域名上,不能跨租户共享。
- Session rotation:敏感操作(改密码、绑定 MFA)后强制轮换 session ID。
- Idle timeout vs Absolute timeout:B2C 可以设 30 天绝对超时;B2B 企业客户经常要求 8 小时 idle、24 小时 absolute。
八、性能考量
8.1 认证服务的 QPS 画像
典型分布(以 1000 QPS 登录为例):
- 70% 是校验现有 token(Token Service 命中 JWKS 缓存,几乎无状态)。
- 20% 是刷新 token(访问 DB,读多写少)。
- 8% 是首次登录(DB 读写,调外部 IdP)。
- 2% 是注册(DB 写,发邮件/短信)。
设计时要把这四类流量分开优化,不要一股脑塞给同一个服务。
8.2 Refresh Token Rotation 的热点锁问题
Refresh Token Rotation 要求:每次刷新签发新的 refresh token,同时把旧的失效。为了防重放,还要检测”旧 refresh token 被用了两次”的异常情况。
问题:如果一个用户的客户端并发调用刷新(比如多个 tab 同时刷新),会撞锁甚至判误杀。
解法:
- Grace period:旧 token 在几秒内仍然可用。
- Token family:给每个 session 一个 family ID,刷新在 family 内串行,跨 family 并行。
- 乐观锁 + 重试。
8.3 外部 IdP 的超时处理
Google/Apple 的 token endpoint 通常 200ms 内返回;微信 API 偶尔 3-5 秒,极端情况下 10+ 秒。必须:
- 设独立超时(比如 3 秒),超时立即失败返回 “IdP unreachable, try again”。
- 不要让外部 IdP 的延迟阻塞你的整个 login flow。
- 监控每个 IdP 的 P50、P95、P99 延迟和错误率。
8.4 缓存策略
登录链路上多层缓存:
- JWKS 公钥:CDN + 资源服务本地缓存,TTL 1 小时。
- 租户 SSO 配置:Redis + 本地缓存双层,TTL 5 分钟;配置变更时主动失效。
- 用户元数据:Redis 按 user_id 缓存,TTL 短(30 秒),失效时惰性加载。
- Session lookup:Redis 主存,MySQL 兜底,异步同步。
8.5 压测场景
CIAM 必做的几类压测:
- 登录峰值:模拟 10 倍平常流量下的登录链路。
- 撞库攻击:模拟大量失败登录,验证限流、验证码、封禁机制。
- 密码重置风暴:用户邮箱泄露事件下大量重置请求,验证邮件队列和 DB 压力。
- IdP 故障演练:模拟 Google/Apple/微信 IdP 超时,验证降级策略。
- 区域切换:模拟某一区域整体不可用时的故障转移。
九、工程坑点
9.1 Account Linking 的安全陷阱
场景:攻击者知道受害者的 email 是
victim@example.com。攻击者在你的 SaaS 上用
victim@example.com 注册一个账号(未验证
email)。受害者稍后用 Google 登录(Google 返回同样的
email)。如果你的系统基于 email 做自动 account
linking,就会把 Google identity 挂到攻击者先创建的 User
上,攻击者只需登录那个 User 就能看到受害者的所有数据。
修复:
- 任何自动 linking 都必须检查对方 email 已经 verified。
- 更严格:本地 email 注册必须先 verify 再允许 linking。
- 最严格:永不自动 linking,只允许用户在已登录状态下手动绑定。
9.2 GDPR “删号”操作的级联清理
一次”删号”绝不是
DELETE FROM users WHERE id = ?。真正要清理的包括:
- 主数据库:users、identities、memberships、sessions。
- 审计日志:用户行为日志(根据保留要求,可能替换为 anonymized ID)。
- 分析系统:Mixpanel、Amplitude、BigQuery 里的事件数据。
- 邮件系统:SendGrid、Mailchimp 里的订阅记录。
- 客服系统:Zendesk、Intercom 里的对话记录。
- 备份:N 天内的增量备份,通常允许保留到备份自然过期。
- 第三方:已经向合作伙伴分享过的数据,需要通知对方删除。
工程上通常用”删除请求”表跟踪:
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(同开发者主体下唯一)作为用户的稳定标识。
坑:
- 用户第一次在某个应用上登录时,如果未关注公众号,可能只返回
openid不返回unionid,需要等用户进一步授权。 - 历史数据里只存了 openid 的用户,迁移到 unionid 时要有一套合并策略。
9.4 渐进式画像的时机设计
错误做法:用户刚登录就弹窗”补全资料”。效果:用户关掉页面走人。
正确做法:把画像收集嵌入到业务动作里:
- 要开启 MFA 时,请用户绑定手机(合理)。
- 要下单时,请用户填地址(合理)。
- 要参加生日营销活动时,请用户填生日(合理)。
关键原则:每一次字段收集都要对用户有”可见收益”。
9.5 Apple Sign in 的 email 只在首次登录返回
Apple 的怪行为:用户首次用 Apple 登录时,id_token 里会有 email;第二次登录起,id_token 里不再有 email 字段(只有 sub)。
如果你没在首次登录时缓存 email,后续就没办法再拿到。
修复:
- 首次登录时立刻把 email 写入 identities 表。
- 提供用户主动更新 email 的入口(Apple 允许用户改 relay 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 天一次)。一旦续签失败,客户的登录页直接挂掉。必须:
- 续签任务必须有独立的告警。
- 续签失败要在证书过期前 14 天内报警。
- 有 fallback 域名(默认的
login.yourvendor.com)在 Custom Domain 挂掉时顶上。
9.9 SSO 连接配置的”幽灵管理员”
B2B 场景下,一个企业客户在你的 SaaS 上有多个管理员。如果只允许一个管理员配置 SSO,这个人离职后 SSO 配置就无人维护。更糟的情况:SSO 配置错误导致所有员工无法登录,而能改配置的人恰好也在被锁在外面。
修复:
- 至少两个 org admin 才能保存 SSO 配置变更(four-eyes principle)。
- 保留 break-glass 账号:一个用本地密码 + MFA 的 fallback 登录方式,平时不用,SSO 挂掉时用。
- SSO 配置变更走 dry-run:先验证连接可用,再正式启用。
9.10 JWT claim 膨胀
新手喜欢把用户所有属性都塞到 JWT claim 里,结果 JWT 动辄 8KB,HTTP header 被打爆。
修复:
- JWT 只放 sub、tid(tenant)、少量必要 claim。
- 复杂权限放 PDP(Policy Decision Point)查询,参考 授权架构。
- 用 opaque token + introspection 对高敏感场景,用 JWT 对低敏感高性能场景。
9.11 移动端的 Refresh Token 保管
Web 端 refresh token 放 HttpOnly cookie 就行,移动端却不能享受这个安全模型。常见错误:直接把 refresh token 存 SharedPreferences / NSUserDefaults,root 设备轻易读取。
正确做法:
- Android:EncryptedSharedPreferences + Keystore。
- iOS:Keychain + Secure Enclave。
- 额外加一层设备绑定:refresh token 与设备公钥绑定,被窃后在其他设备上无效(DPoP / mTLS)。 ## 十、产品画像与选型建议
10.1 典型产品画像
- Auth0:B2C 起家,社交登录、Passwordless、rules/actions 灵活。B2B 部分后来补强。适合中大型 B2C、B2B2C。
- Okta CIC(Customer Identity Cloud):即 Auth0 被 Okta 收购后的重新包装,偏企业客户。
- Stytch:API-first,Passwordless、SMS OTP、Magic Link 做得好。适合开发者主导的团队。
- Clerk:React 生态首选,前端 SDK 非常完善。适合快速起步的 SaaS。
- Corbado:Passkey-first,押注无密码未来。
- AWS Cognito:价格便宜、与 AWS 生态集成好,但定制化差、文档一般。
- Firebase Auth:C 端小应用快速起步,规模上去后局限明显。
- Keycloak:开源自建,员工 IAM 出身,做 CIAM 要自己补社交、合规、审计。
- Supabase Auth:Postgres 生态,和 RLS 天然契合。
10.2 选型建议
纯 B2C 初创(<100 万 MAU):
- 预算紧 → Supabase Auth / Firebase Auth。
- 追求 UX → Clerk / Stytch。
B2C 规模化(>100 万 MAU):
- 需要合规与企业支持 → Auth0 或自建(Keycloak + 合规模块)。
- 追求 passwordless → Stytch 或 Corbado。
B2B SaaS(需要企业 SSO / SCIM):
- 预算允许 → Okta CIC / WorkOS。
- 预算有限且技术能力强 → 自建 SAML/OIDC + SCIM。
B2B2C(同时做个人和企业):
- 首选 Auth0 / Okta CIC(两端都强)。
- 自建的话,组织模型要从第一天设计好。
特殊要求(中国区 PIPL 合规):
- 海外产品要进中国 → 必须独立部署境内集群,海外的 Auth0/Okta 都不满足。
- 选项:自建(Keycloak + 合规改造)、国内厂商(Authing、IDaaS)。
完整的自建 vs 采购决策框架参考 自建还是采购。
10.3 架构演进路径
大多数团队的 CIAM 演进路径是:
- MVP 阶段:托管服务(Firebase / Supabase / Clerk)快速上线。
- 规模化阶段:替换为 Auth0 或自建,加 MFA、加审计。
- B2B 化阶段:加 SSO、SCIM、Custom Domain、SLA。
- 全球化阶段:多区域部署、PIPL 合规、区域化数据分区。
每次架构升级都对应一次用户规模或商业模式的跳跃。关键是在每一阶段留好升级的接口(至少 User/Identity 分离、Organization 作为一等公民)。
十一、参考资料
- Auth0 Docs:Organizations、Actions、Account Linking、Custom Domains
- Okta Customer Identity Cloud Docs:Customer Identity、B2B、Lifecycle
- Microsoft Learn:Microsoft Entra External ID、B2B、B2C、Custom Policies
- Clerk / Stytch / Corbado / Supabase Auth 官方文档
- RFC 6749、OpenID Connect Core 1.0、RFC 7644(SCIM Protocol)
- NIST SP 800-63-3 Digital Identity Guidelines
- GDPR、CCPA、PIPL 公开条例文本与官方解读
- OWASP ASVS 4.0、OWASP Authentication Cheat Sheet
- 本系列相关文章:企业单点登录:OIDC 与现代 SSO、SCIM 与账号生命周期:开通、变更、离职自动化、B2B SaaS 多租户权限设计
十二、小结
CIAM 不是员工 IAM 的”加个注册页”。它是一个同时承担 UX、规模、隐私合规、多租户组织模型、全球部署的大型系统。关键的架构决策:
- User / Identity / Organization 三层数据模型,从第一天分清楚。
- Account Linking 默认显式,自动 linking 必须 verified email。
- 合规(GDPR/CCPA/PIPL)是架构约束,不是贴补丁。
- 托管服务适合起步,成熟后要有自建或深度定制的能力。
- 选型按业务形态(B2C / B2B / B2B2C)和地域(全球 / 中国区)区分。
CIAM 的工程难度在于每一层决策都牵动其他层:你选 Auth0 就要接受它的数据模型;你选自建就要承担合规的长期投入;你做 B2B2C 就要从一开始把组织模型设计好。这类系统最怕的是”先凑合用,回头再重构”——身份数据的迁移几乎不可能做到无缝。
上一篇:自建还是采购:Keycloak、Auth0、Entra、Okta 对比
下一篇:PAM、IGA 与审计合规
同主题继续阅读
把当前热点继续串成多页阅读,而不是停在单篇消费。
身份与访问控制工程
从 OIDC、OAuth 2.1、SAML、SCIM 到多租户权限、CIAM、PAM 与身份平台选型——系统拆解现代身份与访问控制的协议、架构与工程实践。
【身份与访问控制工程】IAM 全景:为什么这是高价值赛道
身份与访问控制从一个登录框演进为横跨合规、运维、平台工程和安全的系统工程。本文从一家 SaaS 公司被大客户卡在 SOC 2 合规的真实触发器切入,拆解 IAM、CIAM、IGA、PAM、SSO、目录服务六个子领域的边界,分析 Okta、Entra ID、Auth0、Keycloak、Ping 等主流厂商的定位与落差,给出工程师视角的介入判据与选型路径。
【身份与访问控制工程】B2B SaaS 多租户权限设计
B2B SaaS 的权限问题远比 B2C 复杂:多个企业客户、各自的内部角色体系、跨租户协作、行列级数据权限、租户自助管理。本文从隔离模型出发,给出租户内 RBAC + 租户间 ReBAC 的混合方案、超级管理员设计、行级权限实现,以及 GitHub、Slack、Notion 的权限模型速览。
【身份与访问控制工程】SCIM 与账号生命周期:开通、变更、离职自动化
从一起僵尸账号安全事件切入,系统讲透 SCIM 2.0 的资源模型、协议操作、Push/Pull 模式、主流 IdP 差异,以及服务端实现、幂等、软删除、孤儿账户清理等工程落地细节