你当前正在访问 Microsoft Azure Global Edition 技术文档网站。 如果需要访问由世纪互联运营的 Microsoft Azure 中国技术文档网站,请访问 https://docs.azure.cn

在 Azure Active Directory B2C 中设置资源所有者密码凭据流

重要

自 2025 年 5 月 1 日起,Azure AD B2C 将不再可供新客户购买。 在我们的常见问题解答中了解详细信息

在开始之前,请使用此页面顶部的 “选择策略类型 选择器”来选择要设置的策略类型。 Azure Active Directory B2C 提供了两种定义用户如何与应用程序交互的方法:通过预定义的用户流,或者通过可完全配置的自定义策略。 对于每种方法,本文中所需的步骤都不同。

在 Azure Active Directory B2C(Azure AD B2C)中,资源所有者密码凭据(ROPC)流是 OAuth 标准身份验证流。 在此流中,应用程序(也称为信赖方)交换令牌的有效凭据。 凭据包括用户 ID 和密码。 返回的令牌是 ID 令牌、访问令牌和刷新令牌。

警告

建议 不要 使用 ROPC 流。 在大多数情况下,提供了更安全的替代方法,建议使用。 此流要求在应用程序中高度信任,并具有在其他流中不存在的风险。 仅当不能使用其他更安全的流时,才应使用此流。

ROPC 流备注

在 Azure Active Directory B2C(Azure AD B2C)中,支持以下选项:

  • 本机客户端:在用户端设备上运行代码时,身份验证期间会发生用户交互。 设备可以是在原生操作系统(如 Android 和 iOS)中运行的移动应用程序。
  • 公共客户端流:仅在 API 调用中发送由应用程序收集的用户凭据。 应用程序的凭据未被发送。
  • 添加新声明:可以更改 ID 令牌内容以添加新声明。

不支持以下流:

  • 服务器到服务器:标识保护系统需要从调用方(本机客户端)收集的可靠 IP 地址作为交互的一部分。 在服务器端 API 调用中,仅使用服务器的 IP 地址。 如果超出身份验证失败的动态阈值,标识保护系统可能会将重复的 IP 地址标识为攻击者。
  • 机密客户端流:验证应用程序客户端 ID,但未验证应用程序机密。

使用 ROPC 流时,请考虑以下限制:

  • 当需要用户交互的身份验证流发生任何中断时,ROPC 不起作用。 例如,当密码过期或需要更改时,需要 多重身份验证 ,或者登录期间需要收集更多信息(例如用户同意)。
  • ROPC 仅支持本地帐户。 用户无法使用 联合标识提供者 (如 Microsoft、Google+、X、AD-FS 或 Facebook)登录。
  • 会话管理,包括 使我保持登录状态(KMSI),不适用。

注册应用程序

要在 Azure AD B2C 租户中注册应用程序,可以使用新的统一“应用注册”体验或旧版“应用程序(旧版)”体验。 详细了解新体验

  1. 登录到 Azure 门户
  2. 确保正在使用的目录包含 Azure AD B2C 租户:
    1. 在门户工具栏中选择“目录 + 订阅”图标。
    2. 在“门户设置 | 目录+订阅”页上的“目录名称”列表中找到你的 Azure AD B2C 目录,然后选择“切换”。
  3. 在 Azure 门户中,搜索并选择“Azure AD B2C”
  4. 选择“应用注册”,然后选择“新建注册”
  5. 输入应用程序的“名称”。 例如, ROPC_Auth_app
  6. 保留其他值不变,然后选择“注册”。
  7. 记录“应用程序(客户端) ID”,以便在后续步骤中使用。
  8. 在“管理”下,选择“身份验证”。
  9. 选择“ 试用新体验 ”(如果已显示)。
  10. “高级设置”和“ 启用以下移动和桌面流”部分下,选择“ ”以将应用程序视为公共客户端。 ROPC 流需要此设置。
  11. 选择“保存”
  12. 在左侧菜单中,选择“ 清单 ”以打开清单编辑器。
  13. oauth2AllowImplicitFlow 属性设置为 true。 如果该属性不存在,请添加它:
    "oauth2AllowImplicitFlow": true,
    
  14. 选择“保存”

创建资源所有者用户流

  1. 以 Azure AD B2C 租户的外部 ID 用户流管理员身份登录到 Azure 门户
  2. 如果有权访问多个租户,请选择顶部菜单中的“设置”图标,从“目录 + 订阅”菜单切换到你的 Azure AD B2C 租户。
  3. 在 Azure 门户中,搜索并选择 Azure AD B2C
  4. 选择 “用户流”,然后选择“ 新建用户流”。
  5. 选择“使用资源所有者密码凭据登录”(ROPC)。
  6. “版本”下,确保已选择 “预览 ”,然后选择“ 创建”。
  7. 为用户流提供名称,例如 ROPC_Auth
  8. “应用程序声明”下,选择“ 显示更多”。
  9. 选择应用程序所需的应用程序声明,例如“显示名称”、“电子邮件”和“标识提供者”。
  10. 选择确定,然后选择创建

先决条件

如果您未这样做,请了解如何在 Active Directory B2C 中的自定义策略入门指南中使用自定义策略入门套件

创建资源所有者策略

  1. 打开 TrustFrameworkExtensions.xml 文件。

  2. BuildingBlocks 元素下,找到 ClaimsSchema 元素,然后添加以下声明类型:

    <ClaimsSchema>
      <ClaimType Id="logonIdentifier">
        <DisplayName>User name or email address that the user can use to sign in</DisplayName>
        <DataType>string</DataType>
      </ClaimType>
      <ClaimType Id="resource">
        <DisplayName>The resource parameter passes to the ROPC endpoint</DisplayName>
        <DataType>string</DataType>
      </ClaimType>
      <ClaimType Id="refreshTokenIssuedOnDateTime">
        <DisplayName>An internal parameter used to determine whether the user should be permitted to authenticate again using their existing refresh token.</DisplayName>
        <DataType>string</DataType>
      </ClaimType>
      <ClaimType Id="refreshTokensValidFromDateTime">
        <DisplayName>An internal parameter used to determine whether the user should be permitted to authenticate again using their existing refresh token.</DisplayName>
        <DataType>string</DataType>
      </ClaimType>
    </ClaimsSchema>
    
  3. ClaimsSchema 之后,将 ClaimsTransformations 元素及其子元素添加到 BuildingBlocks 元素:

    <ClaimsTransformations>
      <ClaimsTransformation Id="CreateSubjectClaimFromObjectID" TransformationMethod="CreateStringClaim">
        <InputParameters>
          <InputParameter Id="value" DataType="string" Value="Not supported currently. Use oid claim." />
        </InputParameters>
        <OutputClaims>
          <OutputClaim ClaimTypeReferenceId="sub" TransformationClaimType="createdClaim" />
        </OutputClaims>
      </ClaimsTransformation>
    
      <ClaimsTransformation Id="AssertRefreshTokenIssuedLaterThanValidFromDate" TransformationMethod="AssertDateTimeIsGreaterThan">
        <InputClaims>
          <InputClaim ClaimTypeReferenceId="refreshTokenIssuedOnDateTime" TransformationClaimType="leftOperand" />
          <InputClaim ClaimTypeReferenceId="refreshTokensValidFromDateTime" TransformationClaimType="rightOperand" />
        </InputClaims>
        <InputParameters>
          <InputParameter Id="AssertIfEqualTo" DataType="boolean" Value="false" />
          <InputParameter Id="AssertIfRightOperandIsNotPresent" DataType="boolean" Value="true" />
        </InputParameters>
      </ClaimsTransformation>
    </ClaimsTransformations>
    
  4. 找到 ClaimsProvider 元素,其中 DisplayNameLocal Account SignIn ,并添加以下技术配置文件:

    <TechnicalProfile Id="ResourceOwnerPasswordCredentials-OAUTH2">
      <DisplayName>Local Account SignIn</DisplayName>
      <Protocol Name="OpenIdConnect" />
      <Metadata>
        <Item Key="UserMessageIfClaimsPrincipalDoesNotExist">We can't seem to find your account</Item>
        <Item Key="UserMessageIfInvalidPassword">Your password is incorrect</Item>
        <Item Key="UserMessageIfOldPasswordUsed">Looks like you used an old password</Item>
        <Item Key="DiscoverMetadataByTokenIssuer">true</Item>
        <Item Key="ValidTokenIssuerPrefixes">https://sts.windows.net/</Item>
        <Item Key="METADATA">https://login.microsoftonline.com/{tenant}/.well-known/openid-configuration</Item>
        <Item Key="authorization_endpoint">https://login.microsoftonline.com/{tenant}/oauth2/token</Item>
        <Item Key="response_types">id_token</Item>
        <Item Key="response_mode">query</Item>
        <Item Key="scope">email openid</Item>
        <Item Key="grant_type">password</Item>
      </Metadata>
      <InputClaims>
        <InputClaim ClaimTypeReferenceId="logonIdentifier" PartnerClaimType="username" Required="true" DefaultValue="{OIDC:Username}"/>
        <InputClaim ClaimTypeReferenceId="password" Required="true" DefaultValue="{OIDC:Password}" />
        <InputClaim ClaimTypeReferenceId="grant_type" DefaultValue="password" />
        <InputClaim ClaimTypeReferenceId="scope" DefaultValue="openid" />
        <InputClaim ClaimTypeReferenceId="nca" PartnerClaimType="nca" DefaultValue="1" />
        <InputClaim ClaimTypeReferenceId="client_id" DefaultValue="ProxyIdentityExperienceFrameworkAppId" />
        <InputClaim ClaimTypeReferenceId="resource_id" PartnerClaimType="resource" DefaultValue="IdentityExperienceFrameworkAppId" />
      </InputClaims>
      <OutputClaims>
        <OutputClaim ClaimTypeReferenceId="objectId" PartnerClaimType="oid" />
        <OutputClaim ClaimTypeReferenceId="userPrincipalName" PartnerClaimType="upn" />
      </OutputClaims>
      <OutputClaimsTransformations>
        <OutputClaimsTransformation ReferenceId="CreateSubjectClaimFromObjectID" />
      </OutputClaimsTransformations>
      <UseTechnicalProfileForSessionManagement ReferenceId="SM-Noop" />
    </TechnicalProfile>
    

    client_idDefaultValue 替换为在先决条件教程中创建的 ProxyIdentityExperienceFramework 应用程序的应用程序 ID。 然后将 resource_idDefaultValue 替换为在先决条件教程中创建的 IdentityExperienceFramework 应用程序的应用程序 ID。

  5. 将以下 ClaimsProvider 元素及其技术配置文件添加到 ClaimsProviders 元素:

    <ClaimsProvider>
      <DisplayName>Azure Active Directory</DisplayName>
      <TechnicalProfiles>
        <TechnicalProfile Id="AAD-UserReadUsingObjectId-CheckRefreshTokenDate">
          <Metadata>
            <Item Key="Operation">Read</Item>
            <Item Key="RaiseErrorIfClaimsPrincipalDoesNotExist">true</Item>
          </Metadata>
          <InputClaims>
            <InputClaim ClaimTypeReferenceId="objectId" Required="true" />
          </InputClaims>
          <OutputClaims>
            <OutputClaim ClaimTypeReferenceId="objectId" />
            <OutputClaim ClaimTypeReferenceId="refreshTokensValidFromDateTime" />
          </OutputClaims>
          <OutputClaimsTransformations>
            <OutputClaimsTransformation ReferenceId="AssertRefreshTokenIssuedLaterThanValidFromDate" />
            <OutputClaimsTransformation ReferenceId="CreateSubjectClaimFromObjectID" />
          </OutputClaimsTransformations>
          <IncludeTechnicalProfile ReferenceId="AAD-Common" />
        </TechnicalProfile>
      </TechnicalProfiles>
    </ClaimsProvider>
    
    <ClaimsProvider>
      <DisplayName>Session Management</DisplayName>
      <TechnicalProfiles>
        <TechnicalProfile Id="SM-RefreshTokenReadAndSetup">
          <DisplayName>Trustframework Policy Engine Refresh Token Setup Technical Profile</DisplayName>
          <Protocol Name="None" />
          <OutputClaims>
            <OutputClaim ClaimTypeReferenceId="objectId" />
            <OutputClaim ClaimTypeReferenceId="refreshTokenIssuedOnDateTime" />
          </OutputClaims>
        </TechnicalProfile>
      </TechnicalProfiles>
    </ClaimsProvider>
    
    <ClaimsProvider>
      <DisplayName>Token Issuer</DisplayName>
      <TechnicalProfiles>
        <TechnicalProfile Id="JwtIssuer">
          <Metadata>
            <!-- Point to the redeem refresh token user journey-->
            <Item Key="RefreshTokenUserJourneyId">ResourceOwnerPasswordCredentials-RedeemRefreshToken</Item>
          </Metadata>
        </TechnicalProfile>
      </TechnicalProfiles>
    </ClaimsProvider>
    
  6. UserJourneys 元素及其子元素添加到 TrustFrameworkPolicy 元素:

    <UserJourney Id="ResourceOwnerPasswordCredentials">
      <PreserveOriginalAssertion>false</PreserveOriginalAssertion>
      <OrchestrationSteps>
        <OrchestrationStep Order="1" Type="ClaimsExchange">
          <ClaimsExchanges>
            <ClaimsExchange Id="ResourceOwnerFlow" TechnicalProfileReferenceId="ResourceOwnerPasswordCredentials-OAUTH2" />
          </ClaimsExchanges>
        </OrchestrationStep>
        <OrchestrationStep Order="2" Type="ClaimsExchange">
          <ClaimsExchanges>
            <ClaimsExchange Id="AADUserReadWithObjectId" TechnicalProfileReferenceId="AAD-UserReadUsingObjectId" />
          </ClaimsExchanges>
        </OrchestrationStep>
        <OrchestrationStep Order="3" Type="SendClaims" CpimIssuerTechnicalProfileReferenceId="JwtIssuer" />
      </OrchestrationSteps>
    </UserJourney>
    <UserJourney Id="ResourceOwnerPasswordCredentials-RedeemRefreshToken">
      <PreserveOriginalAssertion>false</PreserveOriginalAssertion>
      <OrchestrationSteps>
        <OrchestrationStep Order="1" Type="ClaimsExchange">
          <ClaimsExchanges>
            <ClaimsExchange Id="RefreshTokenSetupExchange" TechnicalProfileReferenceId="SM-RefreshTokenReadAndSetup" />
          </ClaimsExchanges>
        </OrchestrationStep>
        <OrchestrationStep Order="2" Type="ClaimsExchange">
          <ClaimsExchanges>
            <ClaimsExchange Id="CheckRefreshTokenDateFromAadExchange" TechnicalProfileReferenceId="AAD-UserReadUsingObjectId-CheckRefreshTokenDate" />
          </ClaimsExchanges>
        </OrchestrationStep>
        <OrchestrationStep Order="3" Type="SendClaims" CpimIssuerTechnicalProfileReferenceId="JwtIssuer" />
      </OrchestrationSteps>
    </UserJourney>
    
  7. 在 Azure AD B2C 租户的 “自定义策略 ”页上,选择“ 上传策略”。

  8. 启用“覆盖策略(若存在)”,然后浏览到 TrustFrameworkExtensions.xml 文件并选中该文件。

  9. 选择“上传”。

创建信赖方文件

接下来,更新启动你创建的用户旅程的信赖方文件:

  1. 在工作目录中创建 SignUpOrSignin.xml 文件的副本,并将其重命名为 ROPC_Auth.xml

  2. 打开新文件并将 TrustFrameworkPolicyPolicyId 属性的值更改为唯一值。 策略标识符是策略的名称。 例如, B2C_1A_ROPC_Auth

  3. DefaultUserJourneyReferenceId 特性的值更改为 ResourceOwnerPasswordCredentials.

  4. OutputClaims 元素更改为仅包含以下声明:

    <OutputClaim ClaimTypeReferenceId="sub" />
    <OutputClaim ClaimTypeReferenceId="objectId" />
    <OutputClaim ClaimTypeReferenceId="displayName" DefaultValue="" />
    <OutputClaim ClaimTypeReferenceId="givenName" DefaultValue="" />
    <OutputClaim ClaimTypeReferenceId="surname" DefaultValue="" />
    
  5. 在 Azure AD B2C 租户的 “自定义策略 ”页上,选择“ 上传策略”。

  6. 启用“覆盖策略(若存在)”,然后浏览到 ROPC_Auth.xml 文件并选中该文件

  7. 选择“上传”。

测试 ROPC 流

使用偏好的 API 开发应用程序生成 API 调用,并查看用于调试策略的响应。 使用以下信息作为 POST 请求的正文,构造一个与此示例类似的调用:

https://<tenant-name>.b2clogin.com/<tenant-name>.onmicrosoft.com/B2C_1A_ROPC_Auth/oauth2/v2.0/token

  • <tenant-name> 替换为 Azure AD B2C 租户的名称。
  • B2C_1A_ROPC_Auth 替换为资源所有者密码凭据策略的全名。
密钥 价值
用户名 user-account
密码 password1
授权类型 (grant_type) 密码
范围 openid application-id offline_access
客户编号 application-id
response_type(响应类型) token id_token
  • user-account 替换为租户中某个用户帐户的名称。
  • password1 替换为用户帐户的密码。
  • application-id替换为ROPC_Auth_app注册中的应用程序 ID。
  • 如果要接收刷新令牌,Offline_access是可选的。

实际的 POST 请求如以下示例所示:

POST /<tenant-name>.onmicrosoft.com/B2C_1A_ROPC_Auth/oauth2/v2.0/token HTTP/1.1
Host: <tenant-name>.b2clogin.com
Content-Type: application/x-www-form-urlencoded

username=contosouser.outlook.com.ws&password=Passxword1&grant_type=password&scope=openid+00001111-aaaa-2222-bbbb-3333cccc4444+offline_access&client_id=00001111-aaaa-2222-bbbb-3333cccc4444&response_type=token+id_token

使用脱机访问的成功响应如以下示例所示:

{
    "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ik9YQjNhdTNScWhUQWN6R0RWZDM5djNpTmlyTWhqN2wxMjIySnh6TmgwRlki...",
    "token_type": "Bearer",
    "expires_in": "3600",
    "refresh_token": "eyJraWQiOiJacW9pQlp2TW5pYVc2MUY0TnlfR3REVk1EVFBLbUJLb0FUcWQ1ZWFja1hBIiwidmVyIjoiMS4wIiwiemlwIjoiRGVmbGF0ZSIsInNlciI6Ij...",
    "id_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ik9YQjNhdTNScWhUQWN6R0RWZDM5djNpTmlyTWhqN2wxMjIySnh6TmgwRlki..."
}

兑换刷新令牌

构造一个与此处所示示例类似的 POST 调用。 将下表中的信息用作请求正文:

https://<tenant-name>.b2clogin.com/<tenant-name>.onmicrosoft.com/B2C_1A_ROPC_Auth/oauth2/v2.0/token

  • <tenant-name> 替换为 Azure AD B2C 租户的名称。
  • B2C_1A_ROPC_Auth 替换为资源所有者密码凭据策略的全名。
密钥 价值
授权类型 (grant_type) 刷新令牌
response_type(响应类型) id_token
客户编号 application-id
资源 application-id
刷新令牌 refresh-token
  • 请将application-id替换为ROPC_Auth_app注册中的应用程序 ID。
  • refresh-token 替换为上一个响应中发回的 refresh_token

成功的响应如以下示例所示:

{
    "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ilg1ZVhrNHh5b2pORnVtMWtsMll0djhkbE5QNC1jNTdkTzZRR1RWQndhT...",
    "id_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ilg1ZVhrNHh5b2pORnVtMWtsMll0djhkbE5QNC1jNTdkTzZRR1RWQn...",
    "token_type": "Bearer",
    "not_before": 1533672990,
    "expires_in": 3600,
    "expires_on": 1533676590,
    "resource": "bef2222d56-552f-4a5b-b90a-1988a7d634c3",
    "id_token_expires_in": 3600,
    "profile_info": "eyJ2ZXIiOiIxLjAiLCJ0aWQiOiI1MTZmYzA2NS1mZjM2LTRiOTMtYWE1YS1kNmVlZGE3Y2JhYzgiLCJzdWIiOm51bGwsIm5hbWUiOiJEYXZpZE11IiwicHJlZmVycmVkX3VzZXJuYW1lIjpudWxsLCJpZHAiOiJMb2NhbEFjY291bnQifQ",
    "refresh_token": "eyJraWQiOiJjcGltY29yZV8wOTI1MjAxNSIsInZlciI6IjEuMCIsInppcCI6IkRlZmxhdGUiLCJzZXIiOiIxLjAi...",
    "refresh_token_expires_in": 1209600
}

故障排除

提供的应用程序未配置为允许“OAuth”隐式流

  • 症状 - 运行 ROPC 流并获取以下消息: AADB2C90057:提供的应用程序未配置为允许“OAuth”隐式流
  • 可能的原因 - 应用程序不允许隐式流。
  • 解决方法:在 Azure AD B2C 中创建 应用注册 时,需要手动编辑应用程序清单并将属性的值 oauth2AllowImplicitFlow 设置为 true。 配置 oauth2AllowImplicitFlow 属性后,更改可能需要几分钟(通常不超过 5 分钟)才能生效。

使用本机 SDK 或 App-Auth

Azure AD B2C 符合公共客户端资源所有者密码凭据的 OAuth 2.0 标准,并且应与大多数客户端 SDK 兼容。 有关最新信息,请参阅 适用于 OAuth 2.0 的本机应用 SDK 和实现新式最佳做法的 OpenID Connect