You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 11:09:23