B2C 自定义策略登录 - "The username or password provided in the request are invalid"
B2C custom policy login - "The username or password provided in the request are invalid"
我们有一个遗留系统,我们正在升级到 Azure B2C。用户需要能够使用他们的用户名或电子邮件登录。在遗留系统中,多个用户可以拥有相同的电子邮件地址。
我们创建了自定义策略(基于 https://github.com/azure-ad-b2c/samples/tree/master/policies/username-signup-or-signin ),以便用户可以使用他们的用户名登录。为了能够支持用户使用电子邮件登录,我们更改了政策。
用户在登录时输入的电子邮件被传递给我们的 api 如果电子邮件仅链接到一个用户,则 returns 用户名或如果电子邮件链接到多个用户,则为 409 错误用户(returns 用户需要使用他的用户名登录的错误消息,因为我们不知道他是谁)。
如果 api returns 用户名,我们将用户名传递给 login-NonInteractive 配置文件,但配置文件 returns “请求中提供的用户名或密码无效”。
所以基本上,我们尝试传递从 api 获得的用户名来登录,而不是电子邮件用户输入。但这是行不通的。如果我们使用从 api 获得的相同用户名登录,它就可以正常工作。
即使我们为了测试目的调整了策略并对用户名进行了硬编码(而不是调用 api)并尝试使用电子邮件登录,但还是失败了。如果我们使用在 b2c 中作为用户不存在的虚拟用户名登录,它可以正常工作,因为它使用硬编码的用户名。但是,一旦我们尝试使用电子邮件登录,即使我们在策略中使用相同的硬编码用户名,它也不起作用。
我们正在使用声明转换(通过调用 singInNameCopyText 技术配置文件)将硬编码值设置为 login-NonInteractive 配置文件中使用的 signInNamePlainText 声明。
我们使用硬编码用户名值的登录政策示例 – 登录与注册分开:
<TechnicalProfile Id="SelfAsserted-LocalAccountSignin-Username">
<DisplayName>Local Account Signin</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.SelfAssertedAttributeProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<Metadata>
<Item Key="ContentDefinitionReferenceId">api.selfasserted</Item>
<Item Key="UserMessageIfClaimsTransformationStringsAreNotEqual">The last names you provided are not the same</Item>
<Item Key="AllowGenerationOfClaimsWithNullValues">true</Item>
<Item Key="setting.operatingMode">Username</Item>
</Metadata>
<InputClaims>
<InputClaim ClaimTypeReferenceId="signInName" />
</InputClaims>
<DisplayClaims>
<DisplayClaim ClaimTypeReferenceId="signInName" Required="true" />
<DisplayClaim ClaimTypeReferenceId="password" Required="true" />
</DisplayClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="signInName" Required="true" />
<OutputClaim ClaimTypeReferenceId="password" Required="true" />
<OutputClaim ClaimTypeReferenceId="objectId" />
<OutputClaim ClaimTypeReferenceId="authenticationSource" />
<OutputClaim ClaimTypeReferenceId="isEmailBoolean" />
<OutputClaim ClaimTypeReferenceId="signInNamePlainText" Required="true"/>
</OutputClaims>
<ValidationTechnicalProfiles>
<ValidationTechnicalProfile ReferenceId="regexAnalysisUsername"/>
<ValidationTechnicalProfile ReferenceId="SecureREST-AccessToken">
<Preconditions>
<Precondition Type="ClaimEquals" ExecuteActionsIf="false">
<Value>isEmailBoolean</Value>
<Value>True</Value>
<Action>SkipThisValidationTechnicalProfile</Action>
</Precondition>
</Preconditions>
</ValidationTechnicalProfile>
<ValidationTechnicalProfile ReferenceId="REST-CheckEmail" >
<Preconditions>
<Precondition Type="ClaimEquals" ExecuteActionsIf="false">
<Value>isEmailBoolean</Value>
<Value>True</Value>
<Action>SkipThisValidationTechnicalProfile</Action>
</Precondition>
</Preconditions>
</ValidationTechnicalProfile>
<ValidationTechnicalProfile ReferenceId="singInNameCopyText" >
</ValidationTechnicalProfile>
<ValidationTechnicalProfile ReferenceId="log-in-NonInteractive2" />
</ValidationTechnicalProfiles>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-AAD" />
</TechnicalProfile>
<TechnicalProfile Id="log-in-NonInteractive2">
<DisplayName>Local Account SignIn</DisplayName>
<Protocol Name="OpenIdConnect" />
<Metadata>
<Item Key="client_id">{{ProxyIdentityExperienceFramework}}</Item>
<Item Key="IdTokenAudience">{{IdentityExperienceFramework}}</Item>
<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="ProviderName">https://sts.windows.net/</Item>
<Item Key="METADATA">https://log-in.microsoftonline.com/{tenant}/.well-known/openid-configuration</Item>
<Item Key="authorization_endpoint">https://log-in.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="LocalAccountProfile">true</Item>
<Item Key="grant_type">password</Item>
<!-- Policy Engine Clients -->
<Item Key="UsePolicyInRedirectUri">false</Item>
<Item Key="HttpBinding">POST</Item>
</Metadata>
<InputClaims>
<InputClaim ClaimTypeReferenceId="client_id" DefaultValue="{{ProxyIdentityExperienceFramework}}" />
<InputClaim ClaimTypeReferenceId="resource_id" PartnerClaimType="resource" DefaultValue="{{IdentityExperienceFramework}}" />
<InputClaim ClaimTypeReferenceId="signInNamePlainText" PartnerClaimType="username" Required="true"/>
<InputClaim ClaimTypeReferenceId="password" PartnerClaimType="password" Required="true" />
<InputClaim ClaimTypeReferenceId="grant_type" DefaultValue="password" />
<InputClaim ClaimTypeReferenceId="scope" DefaultValue="openid" />
<InputClaim ClaimTypeReferenceId="nca" PartnerClaimType="nca" DefaultValue="1" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="signInName" PartnerClaimType="username" />
<OutputClaim ClaimTypeReferenceId="objectId" PartnerClaimType="oid" />
<OutputClaim ClaimTypeReferenceId="tenantId" PartnerClaimType="tid" />
<OutputClaim ClaimTypeReferenceId="givenName" PartnerClaimType="given_name" />
<OutputClaim ClaimTypeReferenceId="surName" PartnerClaimType="family_name" />
<OutputClaim ClaimTypeReferenceId="displayName" PartnerClaimType="name" />
<OutputClaim ClaimTypeReferenceId="userPrincipalName" PartnerClaimType="upn" />
<OutputClaim ClaimTypeReferenceId="authenticationSource" DefaultValue="localAccountAuthentication" />
</OutputClaims>
</TechnicalProfile>
用户旅程:
<UserJourney Id="SignInUsernameOrEmail">
<OrchestrationSteps>
<OrchestrationStep Order="1" Type="ClaimsExchange">
<ClaimsProviderSelections>
<ClaimsProviderSelection ValidationClaimsExchangeId="LocalAccountSigninLog-inNoLog-in" />
</ClaimsProviderSelections>
<ClaimsExchanges>
<ClaimsExchange Id="LocalAccountSigninLog-inNoLog-in" TechnicalProfileReferenceId="SelfAsserted-LocalAccountSignin-Username" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="2" Type="SendClaims" CpimIssuerTechnicalProfileReferenceId="JwtIssuer" />
</OrchestrationSteps>
<ClientDefinition ReferenceId="DefaultWeb" />
</UserJourney>
登录政策依赖方:
<RelyingParty>
<DefaultUserJourney ReferenceId="SignInUsernameOrEmail" />
<UserJourneyBehaviors>
<JourneyInsights TelemetryEngine="ApplicationInsights" InstrumentationKey="{{appInsightsInstrumentationKey}}"
DeveloperMode="true" ClientEnabled="false" ServerEnabled="true" TelemetryVersion="1.0.0" />
</UserJourneyBehaviors>
<TechnicalProfile Id="PolicyProfile">
<DisplayName>PolicyProfile</DisplayName>
<Protocol Name="OpenIdConnect" />
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="signInName" PartnerClaimType="username" />
<OutputClaim ClaimTypeReferenceId="signInNamePlainText" />
<OutputClaim ClaimTypeReferenceId="isEmailBoolean" />
<OutputClaim ClaimTypeReferenceId="objectId" PartnerClaimType="sub" DefaultValue="{Policy:TenantObjectId}"/>
<OutputClaim ClaimTypeReferenceId="tenantId" AlwaysUseDefaultValue="true" DefaultValue="{Policy:TenantObjectId}" />
</OutputClaims>
<SubjectNamingInfo ClaimType="sub" />
</TechnicalProfile>
</RelyingParty>
更新 1:
刚刚发现我们在使用电子邮件登录时遇到问题的原因是我们为 api.
请求的令牌
我们的 api 受到不记名令牌的保护。
我们使用以下技术配置文件从 b2c 获取令牌
<TechnicalProfile Id="SecureREST-AccessToken">
<DisplayName></DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.RestfulProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<Metadata>
<Item Key="ServiceUrl">https://login.microsoftonline.com/{{tenantID}}.onmicrosoft.com/oauth2/v2.0/token</Item>
<Item Key="AuthenticationType">Basic</Item>
<Item Key="SendClaimsIn">Form</Item>
</Metadata>
<CryptographicKeys>
<Key Id="BasicAuthenticationUsername" StorageReferenceId="B2C_1A_SecureRESTClientId" />
<Key Id="BasicAuthenticationPassword" StorageReferenceId="B2C_1A_SecureRESTClientSecret" />
</CryptographicKeys>
<InputClaims>
<InputClaim ClaimTypeReferenceId="grant_type" DefaultValue="client_credentials" />
<InputClaim ClaimTypeReferenceId="scope" DefaultValue="https://{{tenantID}}.onmicrosoft.com/{{registeredApiAppName}}/.default" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="bearerToken" PartnerClaimType="access_token" />
</OutputClaims>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-Noop" />
</TechnicalProfile>
当我删除对 SecureREST-AccessToken 的调用并调用 login-NonInteractive 时,我不再收到“请求中提供的用户名或密码无效”。
但不确定为什么会导致此问题。
更新 2:更新了 SelfAsserted-LocalAccountSignin-Username 以反映 api 调用。
这是因为这些输入声明正在覆盖登录非交互的输入声明名称。
<InputClaim ClaimTypeReferenceId="grant_type" DefaultValue="client_credentials" />
<InputClaim ClaimTypeReferenceId="scope" DefaultValue="https://{{tenantID}}.onmicrosoft.com/{{registeredApiAppName}}/.default" />
</InputClaims>
使用不同的声明名称,并使用 partnerclaimtype 发送具有原始声明名称的值。
我们有一个遗留系统,我们正在升级到 Azure B2C。用户需要能够使用他们的用户名或电子邮件登录。在遗留系统中,多个用户可以拥有相同的电子邮件地址。 我们创建了自定义策略(基于 https://github.com/azure-ad-b2c/samples/tree/master/policies/username-signup-or-signin ),以便用户可以使用他们的用户名登录。为了能够支持用户使用电子邮件登录,我们更改了政策。
用户在登录时输入的电子邮件被传递给我们的 api 如果电子邮件仅链接到一个用户,则 returns 用户名或如果电子邮件链接到多个用户,则为 409 错误用户(returns 用户需要使用他的用户名登录的错误消息,因为我们不知道他是谁)。
如果 api returns 用户名,我们将用户名传递给 login-NonInteractive 配置文件,但配置文件 returns “请求中提供的用户名或密码无效”。
所以基本上,我们尝试传递从 api 获得的用户名来登录,而不是电子邮件用户输入。但这是行不通的。如果我们使用从 api 获得的相同用户名登录,它就可以正常工作。
即使我们为了测试目的调整了策略并对用户名进行了硬编码(而不是调用 api)并尝试使用电子邮件登录,但还是失败了。如果我们使用在 b2c 中作为用户不存在的虚拟用户名登录,它可以正常工作,因为它使用硬编码的用户名。但是,一旦我们尝试使用电子邮件登录,即使我们在策略中使用相同的硬编码用户名,它也不起作用。
我们正在使用声明转换(通过调用 singInNameCopyText 技术配置文件)将硬编码值设置为 login-NonInteractive 配置文件中使用的 signInNamePlainText 声明。
我们使用硬编码用户名值的登录政策示例 – 登录与注册分开:
<TechnicalProfile Id="SelfAsserted-LocalAccountSignin-Username">
<DisplayName>Local Account Signin</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.SelfAssertedAttributeProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<Metadata>
<Item Key="ContentDefinitionReferenceId">api.selfasserted</Item>
<Item Key="UserMessageIfClaimsTransformationStringsAreNotEqual">The last names you provided are not the same</Item>
<Item Key="AllowGenerationOfClaimsWithNullValues">true</Item>
<Item Key="setting.operatingMode">Username</Item>
</Metadata>
<InputClaims>
<InputClaim ClaimTypeReferenceId="signInName" />
</InputClaims>
<DisplayClaims>
<DisplayClaim ClaimTypeReferenceId="signInName" Required="true" />
<DisplayClaim ClaimTypeReferenceId="password" Required="true" />
</DisplayClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="signInName" Required="true" />
<OutputClaim ClaimTypeReferenceId="password" Required="true" />
<OutputClaim ClaimTypeReferenceId="objectId" />
<OutputClaim ClaimTypeReferenceId="authenticationSource" />
<OutputClaim ClaimTypeReferenceId="isEmailBoolean" />
<OutputClaim ClaimTypeReferenceId="signInNamePlainText" Required="true"/>
</OutputClaims>
<ValidationTechnicalProfiles>
<ValidationTechnicalProfile ReferenceId="regexAnalysisUsername"/>
<ValidationTechnicalProfile ReferenceId="SecureREST-AccessToken">
<Preconditions>
<Precondition Type="ClaimEquals" ExecuteActionsIf="false">
<Value>isEmailBoolean</Value>
<Value>True</Value>
<Action>SkipThisValidationTechnicalProfile</Action>
</Precondition>
</Preconditions>
</ValidationTechnicalProfile>
<ValidationTechnicalProfile ReferenceId="REST-CheckEmail" >
<Preconditions>
<Precondition Type="ClaimEquals" ExecuteActionsIf="false">
<Value>isEmailBoolean</Value>
<Value>True</Value>
<Action>SkipThisValidationTechnicalProfile</Action>
</Precondition>
</Preconditions>
</ValidationTechnicalProfile>
<ValidationTechnicalProfile ReferenceId="singInNameCopyText" >
</ValidationTechnicalProfile>
<ValidationTechnicalProfile ReferenceId="log-in-NonInteractive2" />
</ValidationTechnicalProfiles>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-AAD" />
</TechnicalProfile>
<TechnicalProfile Id="log-in-NonInteractive2">
<DisplayName>Local Account SignIn</DisplayName>
<Protocol Name="OpenIdConnect" />
<Metadata>
<Item Key="client_id">{{ProxyIdentityExperienceFramework}}</Item>
<Item Key="IdTokenAudience">{{IdentityExperienceFramework}}</Item>
<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="ProviderName">https://sts.windows.net/</Item>
<Item Key="METADATA">https://log-in.microsoftonline.com/{tenant}/.well-known/openid-configuration</Item>
<Item Key="authorization_endpoint">https://log-in.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="LocalAccountProfile">true</Item>
<Item Key="grant_type">password</Item>
<!-- Policy Engine Clients -->
<Item Key="UsePolicyInRedirectUri">false</Item>
<Item Key="HttpBinding">POST</Item>
</Metadata>
<InputClaims>
<InputClaim ClaimTypeReferenceId="client_id" DefaultValue="{{ProxyIdentityExperienceFramework}}" />
<InputClaim ClaimTypeReferenceId="resource_id" PartnerClaimType="resource" DefaultValue="{{IdentityExperienceFramework}}" />
<InputClaim ClaimTypeReferenceId="signInNamePlainText" PartnerClaimType="username" Required="true"/>
<InputClaim ClaimTypeReferenceId="password" PartnerClaimType="password" Required="true" />
<InputClaim ClaimTypeReferenceId="grant_type" DefaultValue="password" />
<InputClaim ClaimTypeReferenceId="scope" DefaultValue="openid" />
<InputClaim ClaimTypeReferenceId="nca" PartnerClaimType="nca" DefaultValue="1" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="signInName" PartnerClaimType="username" />
<OutputClaim ClaimTypeReferenceId="objectId" PartnerClaimType="oid" />
<OutputClaim ClaimTypeReferenceId="tenantId" PartnerClaimType="tid" />
<OutputClaim ClaimTypeReferenceId="givenName" PartnerClaimType="given_name" />
<OutputClaim ClaimTypeReferenceId="surName" PartnerClaimType="family_name" />
<OutputClaim ClaimTypeReferenceId="displayName" PartnerClaimType="name" />
<OutputClaim ClaimTypeReferenceId="userPrincipalName" PartnerClaimType="upn" />
<OutputClaim ClaimTypeReferenceId="authenticationSource" DefaultValue="localAccountAuthentication" />
</OutputClaims>
</TechnicalProfile>
用户旅程:
<UserJourney Id="SignInUsernameOrEmail">
<OrchestrationSteps>
<OrchestrationStep Order="1" Type="ClaimsExchange">
<ClaimsProviderSelections>
<ClaimsProviderSelection ValidationClaimsExchangeId="LocalAccountSigninLog-inNoLog-in" />
</ClaimsProviderSelections>
<ClaimsExchanges>
<ClaimsExchange Id="LocalAccountSigninLog-inNoLog-in" TechnicalProfileReferenceId="SelfAsserted-LocalAccountSignin-Username" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="2" Type="SendClaims" CpimIssuerTechnicalProfileReferenceId="JwtIssuer" />
</OrchestrationSteps>
<ClientDefinition ReferenceId="DefaultWeb" />
</UserJourney>
登录政策依赖方:
<RelyingParty>
<DefaultUserJourney ReferenceId="SignInUsernameOrEmail" />
<UserJourneyBehaviors>
<JourneyInsights TelemetryEngine="ApplicationInsights" InstrumentationKey="{{appInsightsInstrumentationKey}}"
DeveloperMode="true" ClientEnabled="false" ServerEnabled="true" TelemetryVersion="1.0.0" />
</UserJourneyBehaviors>
<TechnicalProfile Id="PolicyProfile">
<DisplayName>PolicyProfile</DisplayName>
<Protocol Name="OpenIdConnect" />
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="signInName" PartnerClaimType="username" />
<OutputClaim ClaimTypeReferenceId="signInNamePlainText" />
<OutputClaim ClaimTypeReferenceId="isEmailBoolean" />
<OutputClaim ClaimTypeReferenceId="objectId" PartnerClaimType="sub" DefaultValue="{Policy:TenantObjectId}"/>
<OutputClaim ClaimTypeReferenceId="tenantId" AlwaysUseDefaultValue="true" DefaultValue="{Policy:TenantObjectId}" />
</OutputClaims>
<SubjectNamingInfo ClaimType="sub" />
</TechnicalProfile>
</RelyingParty>
更新 1: 刚刚发现我们在使用电子邮件登录时遇到问题的原因是我们为 api.
请求的令牌我们的 api 受到不记名令牌的保护。
我们使用以下技术配置文件从 b2c 获取令牌
<TechnicalProfile Id="SecureREST-AccessToken">
<DisplayName></DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.RestfulProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<Metadata>
<Item Key="ServiceUrl">https://login.microsoftonline.com/{{tenantID}}.onmicrosoft.com/oauth2/v2.0/token</Item>
<Item Key="AuthenticationType">Basic</Item>
<Item Key="SendClaimsIn">Form</Item>
</Metadata>
<CryptographicKeys>
<Key Id="BasicAuthenticationUsername" StorageReferenceId="B2C_1A_SecureRESTClientId" />
<Key Id="BasicAuthenticationPassword" StorageReferenceId="B2C_1A_SecureRESTClientSecret" />
</CryptographicKeys>
<InputClaims>
<InputClaim ClaimTypeReferenceId="grant_type" DefaultValue="client_credentials" />
<InputClaim ClaimTypeReferenceId="scope" DefaultValue="https://{{tenantID}}.onmicrosoft.com/{{registeredApiAppName}}/.default" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="bearerToken" PartnerClaimType="access_token" />
</OutputClaims>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-Noop" />
</TechnicalProfile>
当我删除对 SecureREST-AccessToken 的调用并调用 login-NonInteractive 时,我不再收到“请求中提供的用户名或密码无效”。
但不确定为什么会导致此问题。
更新 2:更新了 SelfAsserted-LocalAccountSignin-Username 以反映 api 调用。
这是因为这些输入声明正在覆盖登录非交互的输入声明名称。
<InputClaim ClaimTypeReferenceId="grant_type" DefaultValue="client_credentials" />
<InputClaim ClaimTypeReferenceId="scope" DefaultValue="https://{{tenantID}}.onmicrosoft.com/{{registeredApiAppName}}/.default" />
</InputClaims>
使用不同的声明名称,并使用 partnerclaimtype 发送具有原始声明名称的值。