我们的 Web 平台使用 Azure AD B2C 做统一登录,并且因为要做自定义登录 UI,走的是 Custom Policy(Identity Experience Framework) 而不是 User Flow。
对于本身就在 Microsoft 生态里的客户(Azure AD / Microsoft 365 租户),走标准的 B2B 跨租户邀请 + IT admin 走完 onboarding,员工就能直接登录。但总有一部分客户内部用的是自建的 AD FS(或者其他支持 SAML 2.0 的 IdP),完全没有 Azure 云上的身份。这部分客户的员工要登录我们的平台,就必须把他们的 AD FS 作为一个 SAML Identity Provider 联合进 B2C。
这个工作是我一个人完成的,这里把整个联合登录的技术细节过一遍:登录时序图、B2C 侧 Custom Policy 的完整配置清单、客户侧需要提供的资料和需要做的 AD FS 配置。
先明确一个容易混淆的点:这里出现了两条独立的协议链路:
B2C 本质上是一个身份代理(broker):对上(业务平台)它是 OIDC IdP,对下(客户 AD FS)它是 SAML SP。理解这一点之后,后面所有的证书、Metadata、Claim 映射就不会搞反方向。
完整时序如下:
id_token,重定向回业务平台id_token,建立本地会话,登录完成对员工来说体验很简单:选一下"用公司账号登录",然后正常输入 AD 密码。中间 B2C 与 AD FS 之间的 SAML 交换是完全透明的。
这一部分比较容易遗漏东西,按顺序过一遍,把完整流程拆开:
B2C 在这条联合链路里是 SP,SP 也需要有自己的密钥对:
B2C-ADFS-SigningCert。如果多个客户共用一个 SAML TechnicalProfile 模板,这张证书通常可以复用;除非某个客户明确要求独立证书。
让客户提供 AD FS 的 Federation Metadata XML(通常地址是 https://sts.<cutomer-domain>/FederationMetadata/2007-06/FederationMetadata.xml,需要客户网络可公开访问,或者客户直接把文件发过来),存进 Azure Storage(Blob)以备后用。
这里有一个容易被忽略的坑:AD FS 的 Token-Signing 证书默认会自动轮换(一般一年左右一次),如果你把 Metadata 存成一份静态快照,然后一直用这份旧的证书信息去校验签名,轮换之后签名校验就会突然失败。两种应对方式:
PartnerEntity 直接配置成客户 Metadata 的实时 URL,让 B2C 每次都去动态拉取,而不是用存在 Blob 里的静态副本。前提是客户的 Metadata 端点长期可公开访问。我的方法是在后台管理系统里,针对每个客户记录一个"Metadata 来源类型"(动态 URL / 静态快照)和"上次刷新时间",方便排查登陆相关的问题。对于静态管理的客户,证书快到期时会显示一个警告,并发送邮件给管理员。
这是核心配置,每接入一个新客户,就在 Custom Policy 的 Extension 文件里加一段类似这样的 ClaimsProvider:
<ClaimsProvider>
<Domain>contoso.com</Domain>
<DisplayName>Contoso Corp Login</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="Contoso-SAML2">
<DisplayName>通过 Contoso 登录</DisplayName>
<Protocol Name="SAML2" />
<Metadata>
<!-- 客户 metadata 可以公网访问,直接动态拉取 -->
<Item Key="PartnerEntity">
https://sts.contoso.com/FederationMetadata/2007-06/FederationMetadata.xml
</Item>
<!-- 客户 metadata 只在内网,保存至我们自己的 Azure storage -->
<!--
<Item Key="PartnerEntity">
https://<storage-account-name>.blob.core.windows.net/adfs/<customer-id>/FederationMetadata.xml
</Item>
-->
<Item Key="WantsSignedAssertions">true</Item>
<Item Key="WantsEncryptedAssertions">false</Item>
<Item Key="SignAuthnRequest">true</Item>
<Item Key="NameIdPolicyFormat">
urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress
</Item>
</Metadata>
<CryptographicKeys>
<Key Id="SamlMessageSigning" StorageReferenceId="B2C-ADFS-SigningCert" />
</CryptographicKeys>
<InputClaims />
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="issuerUserId"
PartnerClaimType="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier" />
<OutputClaim ClaimTypeReferenceId="email"
PartnerClaimType="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress" />
<OutputClaim ClaimTypeReferenceId="displayName"
PartnerClaimType="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name" />
<OutputClaim ClaimTypeReferenceId="identityProvider"
DefaultValue="contoso.com" AlwaysUseDefaultValue="true" />
</OutputClaims>
<OutputClaimsTransformations>
<OutputClaimsTransformation ReferenceId="CreateRandomUPNUserName" />
</OutputClaimsTransformations>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-Saml-idp" />
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
几个需要注意的字段:
PartnerEntity:既可以是 URL(动态拉取),也可以直接内嵌 Metadata XML 内容,二选一。WantsSignedAssertions / SignAuthnRequest:要不要求 AD FS 签名 Assertion、要不要 B2C 给 AuthnRequest 签名,这两个开关要和客户 AD FS 那边的策略对齐,否则会出现"签名验证失败"。OutputClaims 的 PartnerClaimType:必须和 AD FS 侧"发行转换规则(Claim Issuance Policy)"里实际输出的 Claim URI 完全一致(包括命名空间),这是最容易对不上的地方,后面第 4 节会细说。NameIdPolicyFormat 可能需要按客户实际情况调整(有的老环境仍用 unspecified)。因为要接入不止一个客户,不能简单地把这个 TechnicalProfile 写死成唯一登录选项,通常有两种做法:
ClaimsProviderSelection,用户自己点"用 XX 公司账号登录"。客户数量少的时候够用,但客户一多,登录页会变得很挤。为考虑扩展和UI,基本选择后者。当然,如果确定就几个个客户,直接添加按钮,不必增加复杂性。
在 UserJourney 的对应 Orchestration Step 里,大概是这样引用:
<OrchestrationStep Order="2" Type="ClaimsProviderSelection" ContentDefinitionReferenceId="api.idpselections">
<ClaimsProviderSelections>
<ClaimsProviderSelection TargetClaimsExchangeId="ContosoExchange" />
</ClaimsProviderSelections>
</OrchestrationStep>
<OrchestrationStep Order="3" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="ContosoExchange" TechnicalProfileReferenceId="Contoso-SAML2" />
</ClaimsExchanges>
</OrchestrationStep>
配置发布后,B2C 会为每一个 SAML TechnicalProfile 自动生成一份 SP 的 Federation Metadata,地址格式固定为:
https://{your-tenant}.b2clogin.com/{your-tenant}.onmicrosoft.com/{custom-policy-name}/samlp/metadata?idptp={TechnicalProfile的Id}
比如:
https://contosoapp.b2clogin.com/contosoapp.onmicrosoft.com/B2C_1A_signup_signin/samlp/metadata?idptp=Contoso-SAML2
这个 URL 就是要发给客户 IT admin 的 "Relying Party's federation metadata URL" , 后面再展开。
把这部分整理成一张表,方便直接搬进你后台系统的"新客户接入"表单:
| 项目 | 说明 | 必需/可选 |
|---|---|---|
| Federation Metadata(URL 或 XML 文件) | 优先要 URL(如 https://sts.客户域名/FederationMetadata/2007-06/FederationMetadata.xml),要求长期可公网访问;拿不到 URL 才退而求其次要静态 XML | 必需 |
| 企业邮箱域名 | 用于 Home Realm Discovery 路由,一个客户可能有多个域名,都要列出来 | 必需 |
| 期望映射的用户属性 | 至少要 email;如果平台里要用到姓名、工号、部门等,需要提前和客户对齐输出哪些 Claim | 必需 |
| IT 联系人 | 用于联调期间沟通证书、Claim 问题 | 必需 |
| 测试账号 | 一个真实的 AD 域账号,用于走通端到端登录测试 | 强烈建议 |
| AD FS 是否有网络限制 | 判断客户 AD FS 登录页/Federation 端点是否需要通过 AD FS Web Application Proxy 才能被外网(含移动端、家庭网络)访问到 | 必需确认 |
| 客户内部的访问控制策略 | 是否要求只对特定 AD 组开放这个 Relying Party Trust | 可选 |
完整步骤如下:
?idptp= 的地址)SignAuthnRequest)向导后半段会要求选择 Access Control Policy:
这一步决定 AD FS 到底往外发哪些 Claim,也是和 B2C 侧 OutputClaims 的 PartnerClaimType 必须严格对齐的地方:
E-Mail-Addresses → 映射为 E-Mail Address(对应 .../claims/emailaddress);把 Display-Name → 映射为 Name(对应 .../claims/name)NameIdPolicyFormat 配的是 emailAddress),还需要额外加一条 Transform an Incoming Claim 规则,把邮箱 Claim 转换成 Name ID 类型输出OutputClaim 里 PartnerClaimType 写的 URI 一字不差,包括大小写和命名空间前缀——这里对不上是联调阶段最常见的"登录成功但拿不到 email"问题。SignAuthnRequest=true,AD FS 需要能校验我们的签名,这一步一般在导入 Metadata 时已经自动带上了我们的证书,不需要额外手工上传,但联调时建议让客户确认一下 Relying Party Trust 的证书标签页里确实有我们的证书。用客户提供的测试账号,走一遍第 1 节的完整时序,重点检查:
http://www.w3.org/2001/04/xmldsig-more#rsa-sha256 . Expected signature algorithm is http://www.w3.org/2000/09/xmldsig#rsa-sha1解决方法:客户AD FS 服务器上,AD FS Tool -> relying party -> Properties -> Advanced -> secure has algorithm: SHA1
解决方法:Set-AdfsRelyingPartyTrust -TargetName <RP Name> -SamlResponseSignature MessageAndAssertion
PartnerClaimType 是否完全一致。把上面这一整套流程放到后台系统的"新增客户接入"向导后,本质上每次新客户接入需要做的就是:
ClaimsProvider XML 片段并合并进 Custom Policy(模板化,替换域名、TechnicalProfile Id、Metadata 地址即可)这样后续接入新客户就不再需要工程师介入改 XML,客服照着清单收资料、点几下按钮就能完成,同时把"证书轮换监控"作为一个长期运维项挂在后台,避免联合关系跑了几个月之后突然因为证书过期而登录失败。
AD FS: https://learn.microsoft.com/en-us/windows-server/identity/ad-fs/ad-fs-overview
暂无评论,快来抢沙发吧!