如何在Azure B2C自定义策略中支持OIDC地址JSON声明并映射内置属性?
我来帮你搞定这两个Azure B2C自定义策略里的OIDC地址声明问题——这确实是自定义策略中容易卡壳的地方,毕竟默认的声明都是扁平结构,而OIDC标准要求address是嵌套的JSON对象。核心思路是用Azure B2C的ClaimsTransformation功能,把多个内置扁平属性(比如city、postalCode)组合成符合标准的嵌套JSON声明。
1. 核心原理
Azure B2C提供了两个关键的ClaimsTransformation方法来构建JSON:
CreateJson:创建一个空的JSON字符串声明AddJsonClaim:将指定的输入声明添加到已有的JSON字符串中,指定嵌套字段名
通过组合这两个方法,我们可以把多个扁平属性逐步组装成OIDC标准的address JSON对象。
2. 具体实现步骤
步骤1:声明所需的内置属性和address JSON声明
首先在<ClaimsSchema>里确保你需要用到的内置属性(比如city、postalCode)已存在,并新增一个address声明来存储最终的JSON字符串:
<ClaimsSchema> <!-- 这些内置属性通常已存在,确保DataType正确 --> <ClaimType Id="city"> <DisplayName>City</DisplayName> <DataType>string</DataType> </ClaimType> <ClaimType Id="postalCode"> <DisplayName>Postal Code</DisplayName> <DataType>string</DataType> </ClaimType> <ClaimType Id="country"> <DisplayName>Country</DisplayName> <DataType>string</DataType> </ClaimType> <ClaimType Id="streetAddress"> <DisplayName>Street Address</DisplayName> <DataType>string</DataType> </ClaimType> <!-- 定义存储JSON字符串的address声明 --> <ClaimType Id="address"> <DisplayName>Address</DisplayName> <DataType>string</DataType> </ClaimType> </ClaimsSchema>
步骤2:创建ClaimsTransformation来构建address JSON
在<ClaimsTransformations>节点中定义一系列转换,先创建空JSON,再逐个添加属性:
<ClaimsTransformations> <!-- 初始化空的address JSON对象 --> <ClaimsTransformation Id="createAddressJson" TransformationMethod="CreateJson"> <OutputClaims> <OutputClaim ClaimTypeReferenceId="address" TransformationClaimType="createdJson" /> </OutputClaims> </ClaimsTransformation> <!-- 将city映射到address.locality --> <ClaimsTransformation Id="addCityToAddress" TransformationMethod="AddJsonClaim"> <InputClaims> <InputClaim ClaimTypeReferenceId="city" TransformationClaimType="inputClaim" /> <InputClaim ClaimTypeReferenceId="address" TransformationClaimType="existingJson" /> </InputClaims> <InputParameters> <InputParameter Id="claimName" DataType="string" Value="locality" /> </InputParameters> <OutputClaims> <OutputClaim ClaimTypeReferenceId="address" TransformationClaimType="outputJson" /> </OutputClaims> </ClaimsTransformation> <!-- 将postalCode映射到address.postal_code --> <ClaimsTransformation Id="addPostalCodeToAddress" TransformationMethod="AddJsonClaim"> <InputClaims> <InputClaim ClaimTypeReferenceId="postalCode" TransformationClaimType="inputClaim" /> <InputClaim ClaimTypeReferenceId="address" TransformationClaimType="existingJson" /> </InputClaims> <InputParameters> <InputParameter Id="claimName" DataType="string" Value="postal_code" /> </InputParameters> <OutputClaims> <OutputClaim ClaimTypeReferenceId="address" TransformationClaimType="outputJson" /> </OutputClaims> </ClaimsTransformation> <!-- 将country映射到address.country --> <ClaimsTransformation Id="addCountryToAddress" TransformationMethod="AddJsonClaim"> <InputClaims> <InputClaim ClaimTypeReferenceId="country" TransformationClaimType="inputClaim" /> <InputClaim ClaimTypeReferenceId="address" TransformationClaimType="existingJson" /> </InputClaims> <InputParameters> <InputParameter Id="claimName" DataType="string" Value="country" /> </InputParameters> <OutputClaims> <OutputClaim ClaimTypeReferenceId="address" TransformationClaimType="outputJson" /> </OutputClaims> </ClaimsTransformation> <!-- 将streetAddress映射到address.street_address --> <ClaimsTransformation Id="addStreetToAddress" TransformationMethod="AddJsonClaim"> <InputClaims> <InputClaim ClaimTypeReferenceId="streetAddress" TransformationClaimType="inputClaim" /> <InputClaim ClaimTypeReferenceId="address" TransformationClaimType="existingJson" /> </InputClaims> <InputParameters> <InputParameter Id="claimName" DataType="string" Value="street_address" /> </InputParameters> <OutputClaims> <OutputClaim ClaimTypeReferenceId="address" TransformationClaimType="outputJson" /> </OutputClaims> </ClaimsTransformation> </ClaimsTransformations>
步骤3:在用户旅程中执行这些转换
创建一个专门的TechnicalProfile来执行上述转换,然后在用户旅程的合适步骤(比如登录/注册完成后,发行Token前)调用它:
<!-- 定义执行地址转换的TechnicalProfile --> <TechnicalProfile Id="BuildAddressClaim"> <DisplayName>Build Address JSON Claim</DisplayName> <Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.ClaimsTransformationProtocolProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" /> <OutputClaimsTransformations> <OutputClaimsTransformation ReferenceId="createAddressJson" /> <OutputClaimsTransformation ReferenceId="addCityToAddress" /> <OutputClaimsTransformation ReferenceId="addPostalCodeToAddress" /> <OutputClaimsTransformation ReferenceId="addCountryToAddress" /> <OutputClaimsTransformation ReferenceId="addStreetToAddress" /> </OutputClaimsTransformations> </TechnicalProfile> <!-- 在用户旅程中调用这个TechnicalProfile --> <UserJourney Id="SignUpOrSignIn"> <OrchestrationSteps> <!-- 省略其他步骤:登录、注册、验证等 --> <OrchestrationStep Order="7" Type="ClaimsExchange"> <ClaimsExchanges> <ClaimsExchange Id="BuildAddress" TechnicalProfileReferenceId="BuildAddressClaim" /> </ClaimsExchanges> </OrchestrationStep> <!-- 后续步骤:发行Token --> </OrchestrationSteps> </UserJourney>
步骤4:在RelyingParty中输出address声明
最后在<RelyingParty>节点的OutputClaims中添加address声明,这样它就会以JSON对象的形式出现在ID Token中:
<RelyingParty> <DefaultUserJourney ReferenceId="SignUpOrSignIn" /> <TechnicalProfile Id="PolicyProfile"> <DisplayName>PolicyProfile</DisplayName> <Protocol Name="OpenIdConnect" /> <OutputClaims> <OutputClaim ClaimTypeReferenceId="displayName" /> <OutputClaim ClaimTypeReferenceId="givenName" /> <OutputClaim ClaimTypeReferenceId="surname" /> <OutputClaim ClaimTypeReferenceId="email" /> <!-- 输出address声明 --> <OutputClaim ClaimTypeReferenceId="address" /> <OutputClaim ClaimTypeReferenceId="objectId" PartnerClaimType="sub" /> <OutputClaim ClaimTypeReferenceId="identityProvider" /> </OutputClaims> <SubjectNamingInfo ClaimType="sub" /> </TechnicalProfile> </RelyingParty>
3. 处理空属性的优化技巧
如果某些属性可能为空,你可以给ClaimsTransformation添加条件,只有当属性存在时才添加到JSON中,避免出现null值:
<ClaimsTransformation Id="addCityToAddress" TransformationMethod="AddJsonClaim"> <InputClaims> <InputClaim ClaimTypeReferenceId="city" TransformationClaimType="inputClaim" /> <InputClaim ClaimTypeReferenceId="address" TransformationClaimType="existingJson" /> </InputClaims> <InputParameters> <InputParameter Id="claimName" DataType="string" Value="locality" /> <InputParameter Id="ifExists" DataType="string" Value="DoNotOverride" /> </InputParameters> <OutputClaims> <OutputClaim ClaimTypeReferenceId="address" TransformationClaimType="outputJson" /> </OutputClaims> <!-- 只有当city声明存在时才执行此转换 --> <Conditions> <Condition Type="ClaimExists" ExecuteActionsIf="true"> <Value>city</Value> </Condition> </Conditions> </ClaimsTransformation>
完成以上配置后,ID Token中的address字段就会完全符合OIDC标准,格式如下:
"address": { "locality": "Seattle", "postal_code": "98101", "country": "US", "street_address": "123 Main St" }
内容的提问来源于stack exchange,提问作者Eli Pulsifer

