Azure AD B2C自定义密码重置策略存哈希密码报内部服务器错误
Azure AD B2C 密码历史自定义策略运行报内部错误排查
问题场景
我在使用Azure AD B2C自定义策略实现「禁止用户复用最近10个历史密码」的需求时,参考官方密码历史策略示例编写密码重置策略,尝试落地哈希密码存储校验逻辑,但策略运行时直接抛出内部服务器错误。
抛出异常:An internal error has occurred.
关联Correlation ID:ce643089-eec8-496f-96b6-275fa705c0dd
当前使用的策略配置代码如下:
<?xml version="1.0" encoding="UTF-8"?> <TrustFrameworkPolicy xmlns="http://schemas.microsoft.com/online/cpim/schemas/2013/06" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" PolicySchemaVersion="0.3.0.0" TenantId="XXX.onmicrosoft.com" PolicyId="B2C_1A_HISTORYResetPassword" DeploymentMode="Development" UserJourneyRecorderEndpoint="urn:journeyrecorder:applicationinsights" PublicPolicyUri="http://XXX.onmicrosoft.com/B2C_1A_HISTORYResetPassword"> <BasePolicy> <TenantId>XXX.onmicrosoft.com</TenantId> <PolicyId>B2C_1A_TrustFrameworkExtensions</PolicyId> </BasePolicy> <BuildingBlocks> <ClaimsSchema> <ClaimType Id="IsPasswordValid"> <DisplayName>password is valid</DisplayName> <DataType>boolean</DataType> <UserHelpText>password is not one of past 5 passwords</UserHelpText> </ClaimType> <ClaimType Id="userPassHash"> <DisplayName>request JSON body for user password hash info</DisplayName> <DataType>string</DataType> <UserHelpText>request JSON body for user password hash info</UserHelpText> </ClaimType> <ClaimType Id="hashedPassword"> <DisplayName>Hashed password</DisplayName> <DataType>string</DataType> </ClaimType> </ClaimsSchema> <ClaimsTransformations> <ClaimsTransformation Id="HashPasswordWithEmail" TransformationMethod="Hash"> <InputClaims> <InputClaim ClaimTypeReferenceId="newPassword" TransformationClaimType="plaintext" /> <InputClaim ClaimTypeReferenceId="email" TransformationClaimType="salt" /> </InputClaims> <InputParameters> <InputParameter Id="randomizerSecret" DataType="string" Value="B2C_1A_AccountTransformSecret" /> </InputParameters> <OutputClaims> <OutputClaim ClaimTypeReferenceId="hashedPassword" TransformationClaimType="hash" /> </OutputClaims> </ClaimsTransformation> <ClaimsTransformation Id="GeneratePassHashBody" TransformationMethod="GenerateJson"> <InputClaims> <InputClaim ClaimTypeReferenceId="objectid" TransformationClaimType="username" /> <InputClaim ClaimTypeReferenceId="hashedPassword" TransformationClaimType="hash" /> </InputClaims> <OutputClaims> <OutputClaim ClaimTypeReferenceId="userPassHash" TransformationClaimType="outputClaim" /> </OutputClaims> </ClaimsTransformation> </ClaimsTransformations> </BuildingBlocks> <ClaimsProviders> <ClaimsProvider> <DisplayName>Password reset overrides</DisplayName> <TechnicalProfiles> <TechnicalProfile Id="LocalAccountDiscoveryUsingEmailAddress"> <Metadata> <Item Key="EnforceEmailVerification">false</Item> </Metadata> </TechnicalProfile> </TechnicalProfiles> </ClaimsProvider> <ClaimsProvider> <DisplayName>Technical Profiles For Checking Password History</DisplayName> <TechnicalProfiles> <TechnicalProfile Id="ValidatePasswordHash"> <DisplayName>Validate that new password is not one of past 5 passwords</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://XXX.azurewebsites.net/api/ValidatePasswordHash?code=9AWILE36B_BAJJtRrnX0ASyHMIbyK_ZFgoxiUR1OCBxyAzFugayEsA==</Item> <Item Key="AuthenticationType">Basic</Item> <Item Key="AllowInsecureAuthInProduction">true</Item> <Item Key="SendClaimsIn">Body</Item> <Item Key="UserMessageIfDnsResolutionFailed">UserMessageIfRequestTimeout121212</Item> <Item Key="UserMessageIfRequestTimeout">teset1</Item> <Item Key="UserMessageIfCircuitOpen">true</Item> <Item Key="ClaimUsedForRequestPayload">userPassHash</Item> <Item Key="DefaultUserMessageIfRequestFailed">Cannot process your request right now, please try again later.</Item> </Metadata> <!-- <CryptographicKeys> <Key Id="BasicAuthenticationUsername" StorageReferenceId="B2C_1A_MyClientId" /> <Key Id="BasicAuthenticationPassword" StorageReferenceId="B2C_1A_MyClientSecret" /> </CryptographicKeys> --> <InputClaimsTransformations> <InputClaimsTransformation ReferenceId="HashPasswordWithEmail" /> <InputClaimsTransformation ReferenceId="GeneratePassHashBody" /> </InputClaimsTransformations> <InputClaims> <InputClaim ClaimTypeReferenceId="userPassHash" /> </InputClaims> </TechnicalProfile> </TechnicalProfiles> </ClaimsProvider> <ClaimsProvider> <DisplayName>AAD Overrides</DisplayName> <TechnicalProfiles> <TechnicalProfile Id="LocalAccountWritePasswordUsingObjectId-ForHistory"> <DisplayName>Change password (username)</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> </Metadata> <CryptographicKeys> <Key Id="issuer_secret" StorageReferenceId="B2C_1A_TokenSigningKeyContainer" /> </CryptographicKeys> <InputClaims> <InputClaim ClaimTypeReferenceId="objectId" /> </InputClaims> <OutputClaims> <OutputClaim ClaimTypeReferenceId="newPassword" Required="true" /> <OutputClaim ClaimTypeReferenceId="reenterPassword" Required="true" /> <OutputClaim ClaimTypeReferenceId="IsPasswordValid" DefaultValue="true" /> </OutputClaims> <ValidationTechnicalProfiles> <ValidationTechnicalProfile ReferenceId="ValidatePasswordHash" /> <ValidationTechnicalProfile ReferenceId="AAD-UserWritePasswordUsingObjectId" /> </ValidationTechnicalProfiles> </TechnicalProfile> </TechnicalProfiles> </ClaimsProvider> </ClaimsProviders> <UserJourneys> <UserJourney Id="PasswordResetWithHistoryCheck"> <OrchestrationSteps> <OrchestrationStep Order="1" Type="ClaimsExchange"> <ClaimsExchanges> <ClaimsExchange Id="PasswordResetUsingEmailAddressExchange" TechnicalProfileReferenceId="LocalAccountDiscoveryUsingEmailAddress" /> </ClaimsExchanges> </OrchestrationStep> <OrchestrationStep Order="2" Type="ClaimsExchange"> <ClaimsExchanges> <ClaimsExchange Id="NewCredentials" TechnicalProfileReferenceId="LocalAccountWritePasswordUsingObjectId-ForHistory" /> </ClaimsExchanges> </OrchestrationStep> <OrchestrationStep Order="3" Type="SendClaims" CpimIssuerTechnicalProfileReferenceId="JwtIssuer" /> </OrchestrationSteps> <ClientDefinition ReferenceId="DefaultWeb" /> </UserJourney> </UserJourneys> <RelyingParty> <DefaultUserJourney ReferenceId="PasswordResetWithHistoryCheck" /> <UserJourneyBehaviors> <JourneyInsights TelemetryEngine="ApplicationInsights" InstrumentationKey="94701752-973e-4316-afd7-9f2fdb96c028" DeveloperMode="true" ClientEnabled="false" ServerEnabled="true" TelemetryVersion="1.0.0" /> <ScriptExecution>Allow</ScriptExecution> </UserJourneyBehaviors> <TechnicalProfile Id="PolicyProfile"> <DisplayName>PolicyProfile</DisplayName> <Protocol Name="OpenIdConnect" /> <OutputClaims> <OutputClaim ClaimTypeReferenceId="email" /> <OutputClaim ClaimTypeReferenceId="objectId" PartnerClaimType="sub" /> <OutputClaim ClaimTypeReferenceId="tenantId" AlwaysUseDefaultValue="true" DefaultValue="{Policy:TenantObjectId}" /> <OutputClaim ClaimTypeReferenceId="IsPasswordValid" /> </OutputClaims> <SubjectNamingInfo ClaimType="sub" /> </TechnicalProfile> </RelyingParty> </TrustFrameworkPolicy>
错误原因&修复方法
配置里有4个明确问题会直接触发内部错误,按优先级修复即可:
- 声明引用大小写错误:
GeneratePassHashBodyJSON生成转换里引用的用户ID声明写的是objectid(全小写结尾),但B2C流程里读取到的AAD用户对象ID固定为objectId(最后一个字母D大写),大小写不匹配会导致转换时拿不到值直接报错,把这里的引用改成正确的objectId即可。 - 哈希盐值声明不存在:
HashPasswordWithEmail转换用email作为密码哈希的盐值,但密码重置第一步的邮箱验证流程默认输出的邮箱声明是signInNames.emailAddress,不会直接输出名为email的声明,哈希转换拿不到盐值会直接抛错。可以选择两种修复方式:要么加一个声明映射把signInNames.emailAddress的值赋给email,要么直接把哈希转换里的盐值引用改成signInNames.emailAddress。 - REST API认证配置不匹配:
ValidatePasswordHash技术配置里把认证类型设为Basic,但Basic认证需要的用户名、密码密钥配置被整块注释掉了,RESTful技术提供程序在Basic认证模式下找不到对应密钥会直接触发运行时错误。如果密码校验接口不需要认证,直接把AuthenticationType的值改成None即可;如果需要Basic认证,就把注释掉的密钥块放开,配置正确的存储密钥引用。 - 密码写入技术配置缺失基础逻辑:自定义的
LocalAccountWritePasswordUsingObjectId-ForHistory技术配置完全从零编写,没有继承官方默认的密码写入技术配置,会缺失两次密码一致性校验、密码强度校验等默认逻辑,同时错误引用了issuer_secret密钥(自断言密码配置不需要这个密钥)。建议直接通过<IncludeTechnicalProfile>引用原LocalAccountWritePasswordUsingObjectId配置,只覆盖需要修改的校验逻辑部分,不要完全重写整个技术配置。
另外注意当前配置和后端服务写的是校验最近5个历史密码,和需求里的最近10个不匹配,修复完配置后记得同步调整后端校验逻辑的历史密码比对数量。
内容的提问来源于stack exchange,提问作者Mithun Sathyadevan
相关产品推荐
相关产品推荐

