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

Azure AD B2C自定义策略令牌未生成及重试逻辑优化咨询

Azure AD B2C自定义策略问题排查与优化

背景

开发Azure AD B2C自定义策略,目标是仅通过邮箱验证(含验证码)+REST端点校验邮箱有效性后生成令牌,无需密码。当前流程执行到最后一步,仅返回带code参数的跳转URL,未生成预期的JWT令牌。

核心问题

  1. 流程已到最后一步但未生成令牌,遗漏了什么?哪里出错了?
  2. 在步骤4-6之间重复流程实现三次邮箱验证尝试,是否有更优实现方式?

提供的策略代码

信赖方(Relying Party)技术配置

<TechnicalProfile Id="JwtIssuerWithEmail">
    <DisplayName>JWT Issuer With Email</DisplayName>
    <Protocol Name="None" />
    <OutputTokenFormat>JWT</OutputTokenFormat>
    <Metadata>
        <Item Key="client_id">{service:te}</Item>
        <Item Key="SendTokenResponseBodyWithJsonNumbers">true</Item>
        <Item Key="issuer_refresh_token_user_identity_claim_type">email</Item>
    </Metadata>
    <CryptographicKeys>
        <Key Id="issuer_secret" StorageReferenceId="B2C_1A_TokenSigningKeyContainer" />
        <Key Id="issuer_refresh_token_key" StorageReferenceId="B2C_1A_TokenEncryptionKeyContainer" />
    </CryptographicKeys>
    <InputClaims>
        <InputClaim ClaimTypeReferenceId="email" />
    </InputClaims>
    <OutputClaims>
        <OutputClaim ClaimTypeReferenceId="email" />
        <OutputClaim ClaimTypeReferenceId="tenantId" PartnerClaimType="tid" />
    </OutputClaims>
</TechnicalProfile>

用户旅程(User Journey)配置

<UserJourney Id="SignInAndSignUpWithEmailVerification">
    <OrchestrationSteps>
        <!-- Register application insight -->
        <OrchestrationStep Order="1" Type="ClaimsExchange">
            <ClaimsExchanges>
                <ClaimsExchange Id="TrackSignInRequest" TechnicalProfileReferenceId="AzureInsights-SignInRequest" />
            </ClaimsExchanges>
        </OrchestrationStep>
        <!-- Show email registration page -->
        <OrchestrationStep Order="2" Type="ClaimsExchange">
            <ClaimsExchanges>
                <ClaimsExchange Id="SelfAssertedEmailRegistration" TechnicalProfileReferenceId="SelfAsserted-EmailRegistration-Custom" />
            </ClaimsExchanges>
        </OrchestrationStep>
        <!-- Check if the email is valid -->
        <OrchestrationStep Order="3" Type="ClaimsExchange">
            <ClaimsExchanges>
                <ClaimsExchange Id="REST-CheckEmailValidity" TechnicalProfileReferenceId="REST-CheckEmailValidity" />
            </ClaimsExchanges>
        </OrchestrationStep>
        <!-- Second try -->
        <OrchestrationStep Order="4" Type="ClaimsExchange">
            <Preconditions>
                <Precondition Type="ClaimEquals" ExecuteActionsIf="true">
                    <Value>isEmailValid</Value>
                    <Value>true</Value>
                    <Action>SkipThisOrchestrationStep</Action>
                </Precondition>
            </Preconditions>
            <ClaimsExchanges>
                <ClaimsExchange Id="SelfAssertedEmailInvalidRetry2" TechnicalProfileReferenceId="SelfAsserted-EmailRegistration-Custom-Retry" />
            </ClaimsExchanges>
        </OrchestrationStep>
        <!-- Third try -->
        <OrchestrationStep Order="5" Type="ClaimsExchange">
            <Preconditions>
                <Precondition Type="ClaimEquals" ExecuteActionsIf="true">
                    <Value>isEmailValid</Value>
                    <Value>true</Value>
                    <Action>SkipThisOrchestrationStep</Action>
                </Precondition>
            </Preconditions>
            <ClaimsExchanges>
                <ClaimsExchange Id="SelfAssertedEmailInvalidRetry3" TechnicalProfileReferenceId="SelfAsserted-EmailRegistration-Custom-Retry" />
            </ClaimsExchanges>
        </OrchestrationStep>
        <!-- After third try it fails -->
        <OrchestrationStep Order="6" Type="ClaimsExchange">
            <Preconditions>
                <Precondition Type="ClaimEquals" ExecuteActionsIf="true">
                    <Value>isEmailValid</Value>
                    <Value>true</Value>
                    <Action>SkipThisOrchestrationStep</Action>
                </Precondition>
            </Preconditions>
            <ClaimsExchanges>
                <ClaimsExchange Id="SelfAssertedEmailInvalidFailed" TechnicalProfileReferenceId="SelfAsserted-EmailRegistration-Custom-Failed" />
            </ClaimsExchanges>
        </OrchestrationStep>
        <!-- Transform email to readOnlyEmail -->
        <OrchestrationStep Order="7" Type="ClaimsExchange">
            <ClaimsExchanges>
                <ClaimsExchange Id="TransformEmailToReadOnlyEmail" TechnicalProfileReferenceId="TransformEmailToReadOnlyEmail" />
            </ClaimsExchanges>
        </OrchestrationStep>
        <!-- Show custom email verification page -->
        <OrchestrationStep Order="8" Type="ClaimsExchange">
            <ClaimsExchanges>
                <ClaimsExchange Id="UserProfile_EmailVerification_Custom" TechnicalProfileReferenceId="EmailVerification-Custom-UI" />
            </ClaimsExchanges>
        </OrchestrationStep>
        <!-- Generate the token -->
        <OrchestrationStep Order="9" Type="SendClaims" CpimIssuerTechnicalProfileReferenceId="JwtIssuerWithEmail" />
    </OrchestrationSteps>
    <ClientDefinition ReferenceId="DefaultWeb" />
</UserJourney>

问题解答

1. 未生成令牌的原因及修复方案

核心问题

  • Issuer技术配置协议错误:JwtIssuerWithEmail中<Protocol Name="None" />不符合要求,Azure AD B2C的JWT颁发技术配置必须使用<Protocol Name="OpenIdConnect" />,否则无法正确生成并返回令牌。
  • 缺少标准身份标识声明:虽然设置了issuer_refresh_token_user_identity_claim_type为email,但未包含sub(用户唯一标识)等标准令牌声明,导致令牌结构不完整。
  • 可能的流程分支异常:需确认流程中isEmailValid声明的状态是否正确传递,避免令牌生成步骤被意外跳过。

修复步骤

  1. 修改JWT Issuer技术配置的协议与元数据:
<TechnicalProfile Id="JwtIssuerWithEmail">
    <DisplayName>JWT Issuer With Email</DisplayName>
    <!-- 修正协议为OpenIdConnect -->
    <Protocol Name="OpenIdConnect" />
    <OutputTokenFormat>JWT</OutputTokenFormat>
    <Metadata>
        <Item Key="client_id">{service:te}</Item>
        <Item Key="SendTokenResponseBodyWithJsonNumbers">true</Item>
        <Item Key="issuer_refresh_token_user_identity_claim_type">email</Item>
        <!-- 添加必要的响应配置 -->
        <Item Key="response_types">id_token</Item>
        <Item Key="response_mode">form_post</Item>
    </Metadata>
    <CryptographicKeys>
        <Key Id="issuer_secret" StorageReferenceId="B2C_1A_TokenSigningKeyContainer" />
        <Key Id="issuer_refresh_token_key" StorageReferenceId="B2C_1A_TokenEncryptionKeyContainer" />
    </CryptographicKeys>
    <InputClaims>
        <InputClaim ClaimTypeReferenceId="email" />
    </InputClaims>
    <OutputClaims>
        <OutputClaim ClaimTypeReferenceId="email" />
        <OutputClaim ClaimTypeReferenceId="tenantId" PartnerClaimType="tid" />
        <!-- 添加标准用户标识声明 -->
        <OutputClaim ClaimTypeReferenceId="sub" PartnerClaimType="sub" />
    </OutputClaims>
</TechnicalProfile>
  1. 确保email声明的ClaimType定义标记为用户默认身份:
<ClaimType Id="email">
    <DisplayName>Email Address</DisplayName>
    <DataType>string</DataType>
    <UserInputType>TextBox</UserInputType>
    <UserDefaultClaim>true</UserDefaultClaim>
</ClaimType>
  1. 检查用户旅程步骤9的预条件(若有),确保仅在验证成功时执行令牌生成。

2. 多轮邮箱验证尝试的优化方案

当前重复步骤的方式冗余且维护成本高,建议通过声明计数+流程跳转实现更简洁的循环重试逻辑:

优化思路

  • 新增retryCount声明记录重试次数,初始化值为0。
  • 用单个步骤执行邮箱校验,失败后递增计数,达到阈值则跳转至失败页面,否则循环重试。

具体实现

  1. 添加retryCount声明类型:
<ClaimType Id="retryCount">
    <DisplayName>Retry Count</DisplayName>
    <DataType>int</DataType>
    <UserInputType>Hidden</UserInputType>
</ClaimType>
  1. 修改用户旅程步骤,合并重试逻辑:
<UserJourney Id="SignInAndSignUpWithEmailVerification">
    <OrchestrationSteps>
        <!-- 步骤1-2保持不变 -->
        <OrchestrationStep Order="1" Type="ClaimsExchange">...</OrchestrationStep>
        <OrchestrationStep Order="2" Type="ClaimsExchange">...</OrchestrationStep>

        <!-- 初始化重试计数为0 -->
        <OrchestrationStep Order="3" Type="ClaimsExchange">
            <ClaimsExchanges>
                <ClaimsExchange Id="InitializeRetryCount" TechnicalProfileReferenceId="InitializeRetryCount" />
            </ClaimsExchanges>
        </OrchestrationStep>

        <!-- 循环执行邮箱校验 -->
        <OrchestrationStep Order="4" Type="ClaimsExchange">
            <Preconditions>
                <Precondition Type="ClaimEquals" ExecuteActionsIf="true">
                    <Value>isEmailValid</Value>
                    <Value>true</Value>
                    <Action>SkipThisOrchestrationStep</Action>
                </Precondition>
                <Precondition Type="ClaimGreaterThanOrEqual" ExecuteActionsIf="true">
                    <Value>retryCount</Value>
                    <Value>3</Value>
                    <Action>SkipThisOrchestrationStep</Action>
                </Precondition>
            </Preconditions>
            <ClaimsExchanges>
                <ClaimsExchange Id="REST-CheckEmailValidity" TechnicalProfileReferenceId="REST-CheckEmailValidity" />
            </ClaimsExchanges>
        </OrchestrationStep>

        <!-- 递增重试计数并跳转回校验步骤 -->
        <OrchestrationStep Order="5" Type="ClaimsExchange">
            <Preconditions>
                <Precondition Type="ClaimEquals" ExecuteActionsIf="true">
                    <Value>isEmailValid</Value>
                    <Value>true</Value>
                    <Action>SkipThisOrchestrationStep</Action>
                </Precondition>
                <Precondition Type="ClaimGreaterThanOrEqual" ExecuteActionsIf="true">
                    <Value>retryCount</Value>
                    <Value>3</Value>
                    <Action>SkipThisOrchestrationStep</Action>
                </Precondition>
            </Preconditions>
            <ClaimsExchanges>
                <ClaimsExchange Id="IncrementRetryCount" TechnicalProfileReferenceId="IncrementRetryCount" />
            </ClaimsExchanges>
            <Postconditions>
                <Postcondition Type="RedirectToStep" Step="4" />
            </Postconditions>
        </OrchestrationStep>

        <!-- 失败处理 -->
        <OrchestrationStep Order="6" Type="ClaimsExchange">
            <Preconditions>
                <Precondition Type="ClaimEquals" ExecuteActionsIf="true">
                    <Value>isEmailValid</Value>
                    <Value>true</Value>
                    <Action>SkipThisOrchestrationStep</Action>
                </Precondition>
            </Preconditions>
            <ClaimsExchanges>
                <ClaimsExchange Id="SelfAssertedEmailInvalidFailed" TechnicalProfileReferenceId="SelfAsserted-EmailRegistration-Custom-Failed" />
            </ClaimsExchanges>
        </OrchestrationStep>

        <!-- 后续步骤7-9保持不变 -->
        <OrchestrationStep Order="7" Type="ClaimsExchange">...</OrchestrationStep>
        <OrchestrationStep Order="8" Type="ClaimsExchange">...</OrchestrationStep>
        <OrchestrationStep Order="9" Type="SendClaims" CpimIssuerTechnicalProfileReferenceId="JwtIssuerWithEmail" />
    </OrchestrationSteps>
</UserJourney>
  1. 添加初始化和递增计数的技术配置:
<!-- 初始化重试计数 -->
<TechnicalProfile Id="InitializeRetryCount">
    <DisplayName>Initialize Retry Count</DisplayName>
    <Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.ClaimsTransformationProtocolProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
    <OutputClaims>
        <OutputClaim ClaimTypeReferenceId="retryCount" DefaultValue="0" />
    </OutputClaims>
</TechnicalProfile>

<!-- 递增重试计数 -->
<TechnicalProfile Id="IncrementRetryCount">
    <DisplayName>Increment Retry Count</DisplayName>
    <Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.ClaimsTransformationProtocolProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
    <InputClaims>
        <InputClaim ClaimTypeReferenceId="retryCount" />
    </InputClaims>
    <OutputClaims>
        <OutputClaim ClaimTypeReferenceId="retryCount" TransformationClaimType="outputClaim" />
    </OutputClaims>
    <ClaimsTransformations>
        <ClaimsTransformation ReferenceId="IncrementRetryCount" />
    </ClaimsTransformations>
</TechnicalProfile>

<!-- 声明转换:递增计数 -->
<ClaimsTransformation Id="IncrementRetryCount" TransformationMethod="AddIntegerClaim">
    <InputClaims>
        <InputClaim ClaimTypeReferenceId="retryCount" TransformationClaimType="inputClaim" />
    </InputClaims>
    <InputParameters>
        <InputParameter Id="add" DataType="int" Value="1" />
    </InputParameters>
    <OutputClaims>
        <OutputClaim ClaimTypeReferenceId="retryCount" TransformationClaimType="outputClaim" />
    </OutputClaims>
</ClaimsTransformation>

这种方式减少了重复步骤,通过计数和跳转实现循环重试,维护性更强。

内容的提问来源于stack exchange,提问作者Jassica

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 03:17:02