Azure AD B2C自定义策略令牌未生成及重试逻辑优化咨询
Azure AD B2C自定义策略问题排查与优化
背景
开发Azure AD B2C自定义策略,目标是仅通过邮箱验证(含验证码)+REST端点校验邮箱有效性后生成令牌,无需密码。当前流程执行到最后一步,仅返回带code参数的跳转URL,未生成预期的JWT令牌。
核心问题
- 流程已到最后一步但未生成令牌,遗漏了什么?哪里出错了?
- 在步骤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声明的状态是否正确传递,避免令牌生成步骤被意外跳过。
修复步骤
- 修改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>
- 确保
email声明的ClaimType定义标记为用户默认身份:
<ClaimType Id="email"> <DisplayName>Email Address</DisplayName> <DataType>string</DataType> <UserInputType>TextBox</UserInputType> <UserDefaultClaim>true</UserDefaultClaim> </ClaimType>
- 检查用户旅程步骤9的预条件(若有),确保仅在验证成功时执行令牌生成。
2. 多轮邮箱验证尝试的优化方案
当前重复步骤的方式冗余且维护成本高,建议通过声明计数+流程跳转实现更简洁的循环重试逻辑:
优化思路
- 新增
retryCount声明记录重试次数,初始化值为0。 - 用单个步骤执行邮箱校验,失败后递增计数,达到阈值则跳转至失败页面,否则循环重试。
具体实现
- 添加
retryCount声明类型:
<ClaimType Id="retryCount"> <DisplayName>Retry Count</DisplayName> <DataType>int</DataType> <UserInputType>Hidden</UserInputType> </ClaimType>
- 修改用户旅程步骤,合并重试逻辑:
<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>
- 添加初始化和递增计数的技术配置:
<!-- 初始化重试计数 --> <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
相关产品推荐
相关产品推荐

