Voocii博客
首页博客AI 热榜作品集读书友链工具关于

© 2026 Voocii. Built with Next.js & tRPC.

GitHubXEmailRSS


实战记录:把客户自有 AD FS 接入 Azure AD B2C

工作总结rick-hayekrick-hayek2024年8月10日

0. 背景

我们的 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 配置。

1. 整体架构 & 登录时序图

先明确一个容易混淆的点:这里出现了两条独立的协议链路:

  • 「业务平台 ↔ B2C」:走的是标准的 OIDC 授权码流程,B2C 是 IdP,我们的 Web App 是 RP(Relying Party)。
  • 「B2C ↔ 客户 AD FS」:走的是 SAML 2.0 联合,这里 B2C 反过来扮演 SAML SP(Service Provider) 的角色,客户 AD FS 是 SAML IdP。

B2C 本质上是一个身份代理(broker):对上(业务平台)它是 OIDC IdP,对下(客户 AD FS)它是 SAML SP。理解这一点之后,后面所有的证书、Metadata、Claim 映射就不会搞反方向。

完整时序如下:

AD FS 接入 B2C 登录时序图

  1. 员工访问业务平台的受保护页面
  2. Web App 发起 OIDC 授权请求,302 重定向到 B2C 的 Custom Policy 授权端点
  3. B2C 展示登录页,或者根据员工输入的邮箱域名自动做 Home Realm Discovery
  4. 员工选择"通过 XX 公司登录"(或系统按域名自动识别)
  5. B2C 为对应客户生成 SAML AuthnRequest(如果开启了签名,此时会用 B2C 自己的证书签名),重定向浏览器到客户 AD FS 的登录页
  6. 员工在 AD FS 页面用企业域账号完成认证(这一步完全发生在客户自己的系统里,我们看不到密码)
  7. AD FS 生成一个签名的 SAML Response(Assertion),通过浏览器 POST 到 B2C 的 ACS(AssertionConsumerService)端点
  8. B2C 校验签名、Issuer、NameID,把 SAML 声明映射成 B2C 自己的 Claim
  9. B2C 签发自己的 id_token,重定向回业务平台
  10. Web App 校验 id_token,建立本地会话,登录完成

对员工来说体验很简单:选一下"用公司账号登录",然后正常输入 AD 密码。中间 B2C 与 AD FS 之间的 SAML 交换是完全透明的。

2. B2C 侧:Custom Policy 完整配置清单

这一部分比较容易遗漏东西,按顺序过一遍,把完整流程拆开:

2.1 准备一张证书,作为 B2C 的 SAML 签名/解密密钥

B2C 在这条联合链路里是 SP,SP 也需要有自己的密钥对:

  • 生成一个证书(自签名或 CA 签发均可,很多团队直接用自签名,有效期到了记得轮换),上传到 B2C 租户的 Policy Keys(Identity Experience Framework → Policy Keys),比如命名为 B2C-ADFS-SigningCert。
  • 这个证书有两个用途:① 给 AuthnRequest 签名;② B2C 自动生成的 SP Metadata 里会带上这个证书的公钥,客户导入这份 Metadata 时会自动信任它。

如果多个客户共用一个 SAML TechnicalProfile 模板,这张证书通常可以复用;除非某个客户明确要求独立证书。

2.2 获取并保存客户的 Federation Metadata

让客户提供 AD FS 的 Federation Metadata XML(通常地址是 https://sts.<cutomer-domain>/FederationMetadata/2007-06/FederationMetadata.xml,需要客户网络可公开访问,或者客户直接把文件发过来),存进 Azure Storage(Blob)以备后用。

这里有一个容易被忽略的坑:AD FS 的 Token-Signing 证书默认会自动轮换(一般一年左右一次),如果你把 Metadata 存成一份静态快照,然后一直用这份旧的证书信息去校验签名,轮换之后签名校验就会突然失败。两种应对方式:

  • 方案 A(推荐):PartnerEntity 直接配置成客户 Metadata 的实时 URL,让 B2C 每次都去动态拉取,而不是用存在 Blob 里的静态副本。前提是客户的 Metadata 端点长期可公开访问。
  • 方案 B:如果客户的 Metadata 端点不对外网开放(很多企业内网限制),只能保存静态副本,那就需要在后台系统里加一个定期刷新任务(比如每周拉一次客户的 Metadata 并比对证书是否变化),并在证书快过期前提醒客服跟进。

我的方法是在后台管理系统里,针对每个客户记录一个"Metadata 来源类型"(动态 URL / 静态快照)和"上次刷新时间",方便排查登陆相关的问题。对于静态管理的客户,证书快到期时会显示一个警告,并发送邮件给管理员。

2.3 定义 SAML ClaimsProvider TechnicalProfile

这是核心配置,每接入一个新客户,就在 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 节会细说。
  • 如果同时要支持多种 NameID 格式或者客户的 AD FS 版本比较老,NameIdPolicyFormat 可能需要按客户实际情况调整(有的老环境仍用 unspecified)。

2.4 让 B2C 知道"什么时候用这个客户的 IdP"

因为要接入不止一个客户,不能简单地把这个 TechnicalProfile 写死成唯一登录选项,通常有两种做法:

  • 按钮式选择:在统一登录页上,为每个客户加一个 ClaimsProviderSelection,用户自己点"用 XX 公司账号登录"。客户数量少的时候够用,但客户一多,登录页会变得很挤。
  • 域名路由(Home Realm Discovery):员工先输入企业邮箱,后台通过一个 REST API TechnicalProfile 查一张"域名 → ClaimsExchangeId"的映射表(这张表就可以维护在你自己的后台系统里),再动态决定跳转到哪个客户的 SAML TechnicalProfile。这也是可扩展性更好的方案,正好能和你说的"半自动化后台流程"对应起来——新增一个客户,本质上就是后台系统里新增一条"域名 → TechnicalProfile"的映射记录 + 生成一段 ClaimsProvider XML 片段。

为考虑扩展和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>

2.5 发布策略,生成给客户的 SP Metadata URL

配置发布后,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" , 后面再展开。


3. 客户侧需要提供的资料清单

把这部分整理成一张表,方便直接搬进你后台系统的"新客户接入"表单:

项目说明必需/可选
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可选

4. 客户侧需要做的配置(AD FS Relying Party Trust)

完整步骤如下:

4.1 新建 Relying Party Trust

  1. 打开 AD FS 管理控制台 → Relying Party Trusts → 右键 Add Relying Party Trust
  2. 选择 "Import data about the relying party published online or on a local network",粘贴我们提供的 SP Metadata URL(就是上面那条带 ?idptp= 的地址)
  3. AD FS 会自动从这个 URL 拉取 B2C 的 SP 信息,包括:
    • Entity ID(Identifier)
    • AssertionConsumerService(ACS)端点地址
    • 我们上传的那张证书的公钥(用于校验我们签名的 AuthnRequest,如果开启了 SignAuthnRequest)
  4. 填写一个可识别的显示名称(比如 "XX 平台 SSO")

4.2 Access Control Policy

向导后半段会要求选择 Access Control Policy:

  • 最简单的是 "Permit everyone"
  • 如果客户要求只对特定 AD 安全组开放,选 "Permit specific group" 并指定组

4.3 Claim Issuance Policy(最容易踩坑的一步)

这一步决定 AD FS 到底往外发哪些 Claim,也是和 B2C 侧 OutputClaims 的 PartnerClaimType 必须严格对齐的地方:

  1. 在新建好的 Relying Party Trust 上右键 → Edit Claim Issuance Policy
  2. 添加规则,典型配置:
    • Send LDAP Attributes as Claims:把 E-Mail-Addresses → 映射为 E-Mail Address(对应 .../claims/emailaddress);把 Display-Name → 映射为 Name(对应 .../claims/name)
    • 如果 B2C 侧用邮箱作为 NameID(NameIdPolicyFormat 配的是 emailAddress),还需要额外加一条 Transform an Incoming Claim 规则,把邮箱 Claim 转换成 Name ID 类型输出
  3. 保存后,AD FS 输出的 Claim URI 一定要和 B2C 那边 OutputClaim 里 PartnerClaimType 写的 URI 一字不差,包括大小写和命名空间前缀——这里对不上是联调阶段最常见的"登录成功但拿不到 email"问题。

4.4 校验 Endpoint / 绑定方式

  • 在 Relying Party Trust 的 Endpoints 标签下确认 SAML 相关端点(SSO、可选的 SLO)都是启用状态,绑定方式一般是 POST binding
  • 如果 B2C 侧开启了 SignAuthnRequest=true,AD FS 需要能校验我们的签名,这一步一般在导入 Metadata 时已经自动带上了我们的证书,不需要额外手工上传,但联调时建议让客户确认一下 Relying Party Trust 的证书标签页里确实有我们的证书。

4.5 联调测试

用客户提供的测试账号,走一遍第 1 节的完整时序,重点检查:

  • AD FS 登录页能否正常展示(排查网络/证书问题)
  • 登录成功后是否顺利回跳到我们平台,而不是卡在 AD FS 的错误页
  • 登录后平台里拿到的 email/姓名等信息是否正确

5. 常见问题排查

5.1 我遇到的问题

  • SAML request is not signed with expected signature algorithm. SAML request is signed with signature algorithm 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

  • AADB2C90168 : The HTTP-Redirect request does not contain the required parameter 'Signature' for a signed request.

解决方法:Set-AdfsRelyingPartyTrust -TargetName <RP Name> -SamlResponseSignature MessageAndAssertion

5.2 可能的其他问题

  • "AADB2C90268: Invalid SAML Response signature":多半是客户 AD FS 证书轮换了,我们用的是缓存的静态 Metadata,参考第 2.2 可选方案。
  • 登录成功但用户信息为空:检查 AD FS 的 Claim Issuance Policy 输出 URI 和 B2C PartnerClaimType 是否完全一致。
  • AD FS 页面加载不出来/超时:多半是客户 AD FS 没有对外暴露(缺 Web Application Proxy),只在内网可访问,员工在家或移动网络下打不开。
  • AuthnRequest 签名校验失败:确认 B2C 那张签名证书没过期,且客户侧 Relying Party Trust 导入的 Metadata 是最新的(证书公钥没变)。
  • 时钟偏差(Clock Skew)导致断言过期:AD FS 和 B2C 两边服务器时间不同步时会出现,一般容忍窗口是几分钟,客户侧服务器时间异常是常见诱因。

6. 流程产品化的思考

把上面这一整套流程放到后台系统的"新增客户接入"向导后,本质上每次新客户接入需要做的就是:

  1. 收集第 3 节的资料清单(表单化)
  2. 后台自动生成一段 ClaimsProvider XML 片段并合并进 Custom Policy(模板化,替换域名、TechnicalProfile Id、Metadata 地址即可)
  3. 自动发布策略,生成对应的 SP Metadata URL
  4. 把这个 URL 通过邮件/工单发给客户 IT admin,附上第 4 节的操作说明
  5. 客户配置完成后,客服在后台走一遍测试账号登录,确认打通

这样后续接入新客户就不再需要工程师介入改 XML,客服照着清单收资料、点几下按钮就能完成,同时把"证书轮换监控"作为一个长期运维项挂在后台,避免联合关系跑了几个月之后突然因为证书过期而登录失败。

7.参考文档

AD FS: https://learn.microsoft.com/en-us/windows-server/identity/ad-fs/ad-fs-overview

OpenId: https://docs.microsoft.com/en-us/azure/active-directory-b2c/identity-provider-adfs?tabs=windows&pivots=b2c-custom-policy

SAML: https://docs.microsoft.com/en-us/azure/active-directory-b2c/identity-provider-adfs-saml?tabs=windows&pivots=b2c-custom-policy

评论 (0)

暂无评论,快来抢沙发吧!

目录
  • 0. 背景
  • 1. 整体架构 & 登录时序图
  • 2. B2C 侧:Custom Policy 完整配置清单
  • 3. 客户侧需要提供的资料清单
  • 4. 客户侧需要做的配置(AD FS Relying Party Trust)
  • 5. 常见问题排查
  • 6. 流程产品化的思考
  • 7.参考文档