我遇到了同样的问题,Jas Suri 的回答对我帮助很大。但是,我在让它工作时遇到了一些问题。所以,这就是为什么我要分享我的最终解决方案,以防其他人面临同样的问题。
我使用了以下内容作为基础:https://github.com/azure-ad-b2c/samples/tree/master/policies/password-reset-not-last-password/policy,但做了一些调整。以下是我必须添加到现有政策中才能使其发挥作用的内容:
ClaimSchema:
这些声明将在以后使用。
<ClaimsSchema>
<ClaimType Id="SamePassword">
<DisplayName>samePassword</DisplayName>
<DataType>boolean</DataType>
<UserHelpText />
</ClaimType>
<ClaimType Id="resetPasswordObjectId">
<DisplayName>User's Object ID</DisplayName>
<DataType>string</DataType>
<DefaultPartnerClaimTypes>
<Protocol Name="OAuth2" PartnerClaimType="oid" />
<Protocol Name="OpenIdConnect" PartnerClaimType="oid" />
<Protocol Name="SAML2" PartnerClaimType="http://schemas.microsoft.com/identity/claims/objectidentifier" />
</DefaultPartnerClaimTypes>
<UserHelpText>Object identifier (ID) of the user object in Azure AD.</UserHelpText>
</ClaimType>
</ClaimsSchema>
ClaimsTransformation:
第一个声明转换检查是否设置了 resetPasswordObjectId,如果没有,之前尝试使用新密码登录显然不起作用,因此 newPassword 不一样作为旧/当前密码。第二个声明转换检查声明 SamePassword 是否等于 false。如果不是,则会引发错误并显示“您不能使用旧密码”。稍后会详细介绍。
<ClaimsTransformations>
<ClaimsTransformation Id="CheckPasswordEquivalence" TransformationMethod="DoesClaimExist">
<InputClaims>
<InputClaim ClaimTypeReferenceId="resetPasswordObjectId" TransformationClaimType="inputClaim" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="SamePassword" TransformationClaimType="outputClaim" />
</OutputClaims>
</ClaimsTransformation>
<ClaimsTransformation Id="AssertSamePasswordIsFalse" TransformationMethod="AssertBooleanClaimIsEqualToValue">
<InputClaims>
<InputClaim ClaimTypeReferenceId="SamePassword" TransformationClaimType="inputClaim" />
</InputClaims>
<InputParameters>
<InputParameter Id="valueToCompareTo" DataType="boolean" Value="false" />
</InputParameters>
</ClaimsTransformation>
</ClaimsTransformations>
ClaimsProvider:
总体思路是使用新输入的密码并尝试登录用户。如果登录成功,newPassword 与旧/当前密码相同,不允许使用。登录时,我们创建一个输出声明并将其命名为 resetPasswordObjectId,该声明未设置或等于登录用户的对象 ID。然后我们检查 resetPasswordObjectId 是否存在(在声明转换部分完成),如果不存在,则可以使用 newPassword,因为它与旧/当前密码不同。
为了在用户输入旧密码的情况下显示正确的错误消息,我们需要在 <LocalizedResources Id="api.localaccountpasswordreset.en"> 中的 TrustFrameworkLocalization.xml 中重写 UserMessageIfClaimsTransformationBooleanValueIsNotEqual,就像这样 <LocalizedString ElementType="ErrorMessage" StringId="UserMessageIfClaimsTransformationBooleanValueIsNotEqual">You must not use your old password.</LocalizedString>。
使用下面的声明提供程序时,请确保替换所有标有 TODO 的部分。
<ClaimsProviders>
<ClaimsProvider>
<DisplayName>Password Reset without same password</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="login-NonInteractive-PasswordChange">
<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="ProviderName">https://sts.windows.net/</Item>
<!-- TODO replace YOUR-TENANT-ID -->
<Item Key="METADATA">https://login.microsoftonline.com/YOUR-TENANT.onmicrosoft.com/.well-known/openid-configuration</Item>
<!-- TODO replace YOUR-TENANT-ID -->
<Item Key="authorization_endpoint">https://login.microsoftonline.com/YOUR-TENANT.onmicrosoft.com/oauth2/token</Item>
<Item Key="response_types">id_token</Item>
<Item Key="response_mode">query</Item>
<Item Key="scope">email openid</Item>
<!-- TODO ensure this line is commented out-->
<!-- <Item Key="grant_type">password</Item> -->
<Item Key="UsePolicyInRedirectUri">false</Item>
<Item Key="HttpBinding">POST</Item>
<!-- TODO -->
<!-- ProxyIdentityExperienceFramework application / client id -->
<Item Key="client_id">YOUR-PROXY-CLIENT-ID</Item>
<!-- Native App -->
<!-- TODO -->
<!-- IdentityExperienceFramework application / client id -->
<Item Key="IdTokenAudience">YOUR-IDENTITY-CLIENT-ID</Item>
<!-- Web Api -->
</Metadata>
<InputClaims>
<InputClaim ClaimTypeReferenceId="signInName" PartnerClaimType="username" Required="true" />
<!-- INFO: replaced oldPassword with newPassword, that way we try logging in with the new password. If the login is successful, we know the newPassword is the same as the old / current password-->
<InputClaim ClaimTypeReferenceId="newPassword" PartnerClaimType="password" Required="true" />
<InputClaim ClaimTypeReferenceId="grant_type" DefaultValue="password" />
<InputClaim ClaimTypeReferenceId="scope" DefaultValue="openid" />
<InputClaim ClaimTypeReferenceId="nca" PartnerClaimType="nca" DefaultValue="1" />
<!-- TODO -->
<!-- ProxyIdentityExperienceFramework application / client id -->
<InputClaim ClaimTypeReferenceId="client_id" DefaultValue="YOUR-PROXY-CLIENT-ID" />
<!-- TODO -->
<!-- IdentityExperienceFramework application / client id -->
<InputClaim ClaimTypeReferenceId="resource_id" PartnerClaimType="resource" DefaultValue="YOUR-IDENTITY-CLIENT-ID" />
</InputClaims>
<OutputClaims>
<!-- INFO: assign the objectId (oid) to resetPasswordObjectId, since the claim objectId might already be set. In that case there would be no way of knowing whether it was set due to the attempted login with the newPassword-->
<OutputClaim ClaimTypeReferenceId="resetPasswordObjectId" 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>
<!--Logic to check new password is not the same as old password
Validates old password before writing new password-->
<TechnicalProfile Id="LocalAccountWritePasswordUsingObjectId">
<DisplayName>Reset password</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.localaccountpasswordreset</Item>
<!-- set in the TrustFrameworkLocalization.xml -->
<!-- <Item Key="UserMessageIfClaimsTransformationBooleanValueIsNotEqual">You must not use your old password.</Item> -->
</Metadata>
<CryptographicKeys>
<Key Id="issuer_secret" StorageReferenceId="B2C_1A_TokenSigningKeyContainer" />
</CryptographicKeys>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="newPassword" Required="true" />
<OutputClaim ClaimTypeReferenceId="reenterPassword" Required="true" />
</OutputClaims>
<ValidationTechnicalProfiles>
<ValidationTechnicalProfile ReferenceId="login-NonInteractive-PasswordChange" ContinueOnError="true" />
<ValidationTechnicalProfile ReferenceId="ComparePasswords" />
<ValidationTechnicalProfile ReferenceId="AAD-UserWritePasswordUsingObjectId">
<Preconditions>
<Precondition Type="ClaimEquals" ExecuteActionsIf="true">
<Value>SamePassword</Value>
<Value>True</Value>
<Action>SkipThisValidationTechnicalProfile</Action>
</Precondition>
</Preconditions>
</ValidationTechnicalProfile>
</ValidationTechnicalProfiles>
</TechnicalProfile>
<!-- Runs claimsTransformations to make sure new and old passwords differ -->
<TechnicalProfile Id="ComparePasswords">
<DisplayName>Compare Email And Verify Email</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.ClaimsTransformationProtocolProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="SamePassword" />
</OutputClaims>
<OutputClaimsTransformations>
<OutputClaimsTransformation ReferenceId="CheckPasswordEquivalence" />
<OutputClaimsTransformation ReferenceId="AssertSamePasswordIsFalse" />
</OutputClaimsTransformations>
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>