在 Azure Active Directory 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 流时,请考虑下列限制:

注册应用程序

要在 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 租户的全局管理员身份登录到 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. 找到 DisplayNameLocal Account SignInClaimsProvider 元素,并添加以下技术配置文件:

    <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.chinacloudapi.cn/</Item>
        <Item Key="METADATA">https://login.partner.microsoftonline.cn/{tenant}/.well-known/openid-configuration</Item>
        <Item Key="authorization_endpoint">https://login.partner.microsoftonline.cn/{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_id 的 DefaultValue 替换为在先决条件教程中创建的 ProxyIdentityExperienceFramework 应用程序的应用程序 ID 。 将 resource_id 的 DefaultValue 替换为同样在先决条件教程中创建的 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 属性的值更改为唯一的值。 策略 ID 是策略的名称。 例如,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.cn/<tenant-name>.partner.onmschina.cn/B2C_1A_ROPC_Auth/oauth2/v2.0/token

  • <tenant-name> 替换为 Azure AD B2C 租户的名称。
  • B2C_1A_ROPC_Auth 替换为资源所有者密码凭据策略的全名。
密钥
username user-account
password password1
grant_type password
scope openid application-id offline_access
client_id application-id
response_type token id_token
  • user-account 替换为租户中某个用户帐户的名称。
  • password1 替换为该用户帐户的密码。
  • application-id 替换为 ROPC_Auth_app 注册中的应用程序 ID。
  • 如果想要接收刷新令牌,则 Offline_access 是可选的。

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

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

username=contosouser.outlook.com.ws&password=Passxword1&grant_type=password&scope=openid+bef22d56-552f-4a5b-b90a-1988a7d634ce+offline_access&client_id=bef22d56-552f-4a5b-b90a-1988a7d634ce&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.cn/<tenant-name>.partner.onmschina.cn/B2C_1A_ROPC_Auth/oauth2/v2.0/token

  • <tenant-name> 替换为 Azure AD B2C 租户的名称。
  • B2C_1A_ROPC_Auth 替换为资源所有者密码凭据策略的全名。
密钥
grant_type refresh_token
response_type id_token
client_id application-id
resource application-id
refresh_token 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 属性后,更改可能需要几分钟(通常不超过五分钟)才能生效。

使用本机 SDK 或 App-Auth

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