为企业应用程序定制在 JSON Web Token (JWT) 中发布的声明
Microsoft 标识平台支持在 Microsoft Entra 应用程序库中的大多数预集成应用程序和自定义应用程序上进行单一登录 (SSO)。 当用户使用 OIDC 协议通过 Microsoft 标识平台向应用程序进行身份验证时,系统会将令牌发送到该应用程序。 应用程序会验证并使用令牌登录用户,而不会提示输入用户名和密码。
OIDC 和 OAuth 应用程序使用的这些 JSON Web 令牌 (JWT) 包含名为声明的有关用户的信息片段。 “声明”是标识提供者在为某个用户颁发的令牌中陈述的有关该用户的信息。 在 OIDC 响应中,“声明”数据通常包含在标识提供者以 JWT 形式颁发的 ID 令牌中。
查看或编辑声明
提示
本文中的步骤可能因开始使用的门户而略有不同。
可选的 JWT 声明可以在原始应用程序注册中配置,但也可以在企业应用程序中配置。 要查看或编辑 JWT 中颁发给应用程序的声明,请执行以下操作:
- 至少以云应用程序管理员身份登录到 Microsoft Entra 管理中心。
- 浏览到“标识”>“应用程序”>“企业应用程序”>“所有应用程序”。
- 选择应用程序,在左侧菜单中选择“单一登录”,然后在“属性和声明”部分中选择“编辑”。
由于各种原因,应用程序可能需要声明自定义。 例如,当应用程序需要一组不同的声明 URI 或声明值时。 使用“属性和声明”部分,可以添加或移除应用程序的声明。 还可以根据用例创建特定于应用程序的自定义声明。
以下步骤介绍了如何分配常数值:
- 选择要修改的声明。
- 在“源属性”中按组织输入不带引号的常量值,然后选择“保存”。
属性概述显示常量值。
特定声明转换
可以使用以下特殊声明转换函数。
函数 | 说明 |
---|---|
ExtractMailPrefix() | 删除电子邮件地址或用户主体名称中的域后缀。 此功能仅提取用户名的第一部分。 例如,joe_smith ,而非 joe_smith@contoso.com 。 |
ToLower() | 将所选属性的字符转换为小写字符。 |
ToUpper() | 将所选属性的字符转换为大写字符。 |
添加特定于应用程序的声明
要添加特定于应用程序的声明:
- 在“用户属性和声明”中,选择“添加新声明”打开“管理用户声明”网页。
- 输入声明的“名称”。 该值不需要严格遵循 URI 模式。 如果需要 URI 模式,可以将其放入命名空间字段。
- 选择声明可检索其值的源。 可以从“源属性”下拉列表中选择一个用户属性,或者在用户属性作为声明发出之前对其应用转换。
声明转换
对用户属性应用转换:
- 在管理声明中,选择转换作为声明源,以打开管理转换页。
- 从“转换”下拉列表中选择函数。 根据选择的函数,提供参数和常量值以在转换中进行计算。
- 将源视为多值指示转换是应用于所有值还是仅应用于第一个值。 默认情况下,多值声明中的第一个元素将应用转换。 勾选此框时,可确保将其应用于所有值。 此复选框仅对多值属性启用。 例如
user.proxyaddresses
。 - 若要应用多个转换,请选择“添加转换”。 对一个声明最多可以应用两个转换。 例如,可以先提取
user.mail
的电子邮件前缀。 然后,将字符串设为大写。
可以使用以下功能转换声明。
函数 | 说明 |
---|---|
ExtractMailPrefix() | 删除电子邮件地址或用户主体名称中的域后缀。 此功能仅提取用户名的第一部分。 例如,joe_smith ,而非 joe_smith@contoso.com 。 |
Join() | 通过联接两个属性来创建新的值。 或者,可以在两个属性之间使用分隔符。 对于 NameID 声明转换,在转换输入具有域部分时,Join() 函数具有特定行为。 它会先从输入中删除域部分,然后再将输入与分隔符和所选参数联接起来。 例如,如果转换的输入为 joe_smith@contoso.com ,分隔符为 @ ,参数为 fabrikam.com ,则此输入组合最终会生成 joe_smith@fabrikam.com 。 |
ToLowercase() | 将所选属性的字符转换为小写字符。 |
ToUppercase() | 将所选属性的字符转换为大写字符。 |
Contains() | 如果输入与指定的值匹配,则输出一个属性或常量。 否则,如果没有匹配项,则可以指定其他输出。 例如,如果想要发出一个声明,其中的值为用户的电子邮件地址(如果包含域 @contoso.com ),否则就需要输出用户主体名称。 若要执行此函数,请配置以下值:参数 1(输入) :user.email 值:“@contoso.com” 参数 2(输出):user.email 参数 3(如果没有匹配项,则为输出):user.userprincipalname |
EndWith() | 如果输入以指定值结束,则输出一个属性或常量。 否则,如果没有匹配项,则可以指定其他输出。 例如,如果想要发出一个声明,其中的值为用户的员工 ID (如果员工 ID 以 000 结束),否则就需要输出一个扩展属性。 若要执行此函数,请配置以下值:参数 1(输入) :user.employeeid 值:000 参数 2(输出):user.employeeid 参数 3(如果没有匹配项,则为输出):user.extensionattribute1 |
StartWith() | 如果输入以指定值开始,则输出一个属性或常量。 否则,如果没有匹配项,则可以指定其他输出。 例如,如果想要发出一个声明,其中的值为用户的员工 ID(如果国家/地区以“ US ”开始),否则就需要输出一个扩展属性。 若要执行此函数,请配置以下值:参数 1(输入) :user.country 值:美国 参数 2(输出):user.employeeid 参数 3(如果没有匹配项,则为输出):user.extensionattribute1 |
Extract() - 匹配后 | 匹配指定值后返回的子字符串。 例如,如果输入的值为 Finance_BSimon ,匹配值为 Finance_ ,则声明的输出为 BSimon 。 |
Extract() - 匹配前 | 在匹配指定值前返回的子字符串。 例如,如果输入的值为 BSimon_US ,匹配值为 _US ,则声明的输出为 BSimon 。 |
Extract() - 匹配之间 | 在匹配指定值前返回的子字符串。 例如,如果输入的值为 Finance_BSimon_US ,第一个匹配值为 Finance_ ,第二个匹配值为 _US ,则声明的输出为 BSimon 。 |
ExtractAlpha() - 前缀 | 返回字符串的前缀字母部分。 例如,如果输入的值为 BSimon_123 ,则它将返回 BSimon 。 |
ExtractAlpha() - 后缀 | 返回字符串的后缀字母部分。 例如,如果输入的值为 123_Simon ,则它将返回 Simon 。 |
ExtractNumeric() - 前缀 | 返回字符串的前缀数字部分。 例如,如果输入的值为 123_BSimon ,则它将返回 123 。 |
ExtractNumeric() - 后缀 | 返回字符串的后缀数字部分。 例如,如果输入的值为 BSimon_123 ,则它将返回 123 。 |
IfEmpty() | 如果输入为 null 或为空,则输出一个属性或常量。 例如,如果希望在给定用户的员工 ID 为空时输出存储在 extension attribute 中的属性。 若要执行此函数,请配置以下值: 参数 1(输入):user.employeeid 参数 2(输出):user.extensionattribute1 参数 3(如果没有匹配项,则为输出):user.employeeid |
IfNotEmpty() | 如果输入不为 null 或空,则输出一个属性或常量。 例如,如果希望在给定用户的员工 ID 不为空时输出存储在 extension attribute 中的属性。 若要执行此函数,请配置以下值: 参数 1(输入):user.employeeid 参数 2(输出):user.extensionattribute1 |
Substring() - 固定长度 | 提取字符串声明类型的组成部分(从位于指定位置处的字符开始),并返回指定数目的字符。 SourceClaim - 应执行的转换的声明源。 StartIndex - 此实例中子字符串的起始字符位置(从零开始)。 Length - 子字符串的字符长度。 例如: sourceClaim - PleaseExtractThisNow StartIndex - 6 Length - 11 输出:ExtractThis |
Substring() - EndOfString | 从指定位置的字符开始,提取字符串声明类型的部分内容,并返回声明中从指定起始索引开始的剩余内容。 SourceClaim - 转换的声明源。 StartIndex - 此实例中子字符串的起始字符位置(从零开始)。 例如: sourceClaim - PleaseExtractThisNow StartIndex - 6 输出:ExtractThisNow |
RegexReplace() | RegexReplace() 转换接受以下内容作为输入参数: - 参数 1:用户属性作为正则表达式输入 - 信任源作为多值的选项 - 正则表达式模式 - 替换模式。 替换模式可能包含静态文本格式,以及指向正则表达式输出组和其他输入参数的引用。 |
如果需要其他转换,请在“SaaS 应用程序”类别下的 Microsoft Entra ID 反馈论坛中发布你的想法。
基于正则表达式的声明转换
下图显示了第一级转换的示例:
下表提供了有关第一级转换的信息。 表中列出的操作对应于上图中的标签。 选择“编辑”以打开声明转换边栏选项卡。
操作 | 字段 | 说明 |
---|---|---|
1 | Transformation |
从“转换”选项中选择“RegexReplace()”选项以使用基于正则表达式的声明转换方法进行声明转换。 |
2 | Parameter 1 |
正则表达式转换的输入。 例如,具有用户电子邮件地址(如 admin@fabrikam.com )的 user.mail。 |
3 | Treat source as multivalued |
某些输入用户属性可以是多值用户属性。 如果所选用户属性支持多个值,并且用户希望对转换使用多个值,则需要选中“将源视为多值”。 如果选中,则所有值都用于正则表达式匹配,否则仅使用第一个值。 |
4 | Regex pattern |
一个正则表达式,根据选为“参数 1”的用户属性的值计算。 例如,从用户的电子邮件地址中提取用户别名的正则表达式表示为 (?'domain'^.*?)(?i)(\@fabrikam\.com)$ 。 |
5 | Add additional parameter |
多个用户属性可用于转换。 然后,属性的值将与正则表达式转换输出合并。 最多支持另外五个参数。 |
6 | Replacement pattern |
替换模式是文本模板,其中包含正则表达式结果的占位符。 所有组名称都必须包在大括号内,例如 {group-name}。 比方说,管理部门想要将用户别名与其他域名(例如 xyz.com )一起使用,并将国家/地区名称与其合并。 在这种情况下,替换模式将是 {country}.{domain}@xyz.com ,其中 {country} 是输入参数的值,而 {domain} 是正则表达式计算的组输出。 在这种情况下,预期结果为 US.swmal@xyz.com 。 |
下图显示了第二级转换的示例:
下表提供了有关第二级转换的信息。 表中列出的操作对应于上图中的标签。
操作 | 字段 | 说明 |
---|---|---|
1 | Transformation |
基于正则表达式的声明转换不限于第一级转换,还可以用作第二级转换。 任何其他转换方法都可以用作第一级转换。 |
2 | Parameter 1 |
如果选择 RegexReplace() 作为第二级转换,则第一级转换的输出将用作二级转换的输入。 要应用转换,第二级正则表达式应与第一级转换的输出匹配。 |
3 | Regex pattern |
“正则表达式模式”是第二级转换的正则表达式。 |
4 | Parameter input |
第二级转换的用户属性输入。 |
5 | Parameter input |
如果管理员不再需要所选输入参数,则可以将其删除。 |
6 | Replacement pattern |
替换模式是文本模板,其中包含正则表达式结果组名称、输入参数组名称和静态文本值的占位符。 所有组名称都必须包在大括号内,例如 {group-name} 。 比方说,管理部门想要将用户别名与其他域名(例如 xyz.com )一起使用,并将国家/地区名称与其合并。 在这种情况下,替换模式将是 {country}.{domain}@xyz.com ,其中 {country} 是输入参数的值,而 {domain} 是正则表达式计算的组输出。 在这种情况下,预期结果为 US.swmal@xyz.com 。 |
7 | Test transformation |
仅当“参数 1”的选定用户属性的值与“正则表达式模式”文本框中提供的正则表达式匹配时,才会计算 RegexReplace() 转换。 如果它们不匹配,则默认声明值将添加到令牌中。 为了根据输入参数值验证正则表达式,可以在转换边栏选项卡中选择测试体验。 此测试体验仅对虚拟值进行操作。 如果使用更多的输入参数,参数的名称将添加到测试结果而不是实际值。 若要访问测试部分,请选择“测试转换”。 |
下图显示了测试转换的示例:
下表提供了有关转换的信息。 表中列出的操作对应于上图中的标签。
操作 | 字段 | 说明 |
---|---|---|
1 | Test transformation |
选择关闭或 (X) 按钮将隐藏测试部分,并在边栏选项卡上再次重新呈现“测试转换”按钮。 |
2 | Test regex input |
接受用于正则表达式测试计算的输入。 如果将基于正则表达式的声明转换配置为第二级转换,将会提供属于第一级转换预期输出的一个值。 |
3 | Run test |
提供测试正则表达式输入并配置“正则表达式模式”、“替换模式”和“输入参数”后,可以通过选择“运行测试”来计算表达式。 |
4 | Test transformation result |
如果计算成功,将会根据“测试转换结果”标签呈现测试转换的输出。 |
5 | Remove transformation |
可以通过选择“删除转换”来删除第二级转换。 |
6 | Specify output if no match |
如果针对“参数 1”配置的正则表达式输入值与“正则表达式”不匹配,则跳过转换。 在这种情况下,可以配置备用用户属性,通过选中“指定在没有匹配项的情况下的输出”即可将其添加到声明的令牌中。 |
7 | Parameter 3 |
如果在没有匹配项时需要返回备用用户属性,并且如果未选中“指定在没有匹配项的情况下的输出”,可以使用下拉列表选择备用用户属性。 此下拉列表对应于“参数 3(不匹配时输出)”。 |
8 | Summary |
在边栏选项卡底部,显示了格式的完整摘要,其中以简单的文本解释了转换的含义。 |
9 | Add |
验证转换的配置设置后,可以通过选择“添加”将其保存到声明策略。 在管理声明边栏选项卡上选择保存以保存更改。 |
RegexReplace() 转换也可用于组声明转换。
转换验证
选择添加或运行测试后出现以下情况时,消息将提供详细信息:
- 使用了具有重复用户属性的输入参数。
- 找到了未使用的输入参数。 定义的输入参数在替换模式文本中应具有相应的用法。
- 提供的测试正则表达式输入与提供的正则表达式不匹配。
- 找不到替换模式中组的源。
发出基于条件的声明
能够根据用户类型和用户所属的组来指定声明的源。
用户类型可以是:
- 任何 - 允许所有用户访问应用程序。
- 成员:本机租户的成员
- 所有来宾:从具有或没有 Microsoft Entra ID 的外部组织中移来的用户。
- Microsoft Entra 来宾:属于使用 Microsoft Entra ID 的另一个组织的来宾用户。
- 外部来宾:来宾用户属于没有 Microsoft Entra ID 的外部组织。
用户类型在以下场景中很有用:来宾和员工访问应用程序使用的声明源不同。 可做如下规定:如果用户是员工,则从 user.email 获取 NameID。 如果用户是来宾,则从 user.extensionattribute1 获取 NameID。
要添加声明条件:
- 在管理声明中,展开”声明条件”。
- 选择用户类型。
- 选择用户所属的组。 在某个给定应用程序的所有声明中,最多可以选择 50 个独一无二的组。
- 选择声明可检索其值的源。 可以从“源属性”下拉列表中选择一个用户属性,或者在用户属性作为声明发出之前对其应用转换。
添加条件的顺序很重要。 Microsoft Entra 首先使用源 Attribute
评估所有条件,然后使用源 Transformation
评估所有条件,以确定要在声明中发出哪个值。 Microsoft Entra ID 从上到下评估具有相同源的条件。 声明发出与声明中的表达式匹配的最后一个值。 转换(如 IsNotEmpty
和 Contains
)的作用类似于限制。
例如,Britta Simon 是 Contoso 租户中的来宾用户。 Britta 属于另一个也使用 Microsoft Entra ID 的组织。 基于以下对 Fabrikam 应用程序的配置,如果 Britta 尝试登录到 Fabrikam,Microsoft 标识平台将评估条件。
首先,Microsoft 标识平台会验证 Britta 的用户类型是否为“所有来宾”。 由于类型为“所有来宾”,因此 Microsoft 标识平台会将声明的源分配到 user.extensionattribute1
。 其次,Microsoft 标识平台会验证 Britta 的用户类型是否为“Microsoft Entra 来宾”。 由于类型为“所有来宾”,因此 Microsoft 标识平台会将声明的源分配到 user.mail
。 最后,使用值 user.mail
为 Britta 发出声明。
作为另一个示例,请考虑当 Britta Simon 尝试登录时,使用以下配置会发生什么情况。 Microsoft Entra 首先使用源 Attribute
评估所有条件。 当 Britta 的用户类型为 user.mail
Microsoft Entra 来宾时,声明的源为 。 接下来,Microsoft Entra ID 会评估转换。 由于 Britta 是来宾,因此 user.extensionattribute1
是声明的新源。 由于 Britta 是 Microsoft Entra 来宾,因此 user.othermail
是声明的新源。 最后,使用值 user.othermail
为 Britta 发出声明。
作为最后一个示例,请考虑如果 Britta 未配置 user.othermail
或其为空,会发生什么情况。 在这两种情况下,声明回退到 user.extensionattribute1
,忽略条件条目。
安全注意事项
接收令牌的应用程序依赖于这一声明,不能篡改。 当你通过声明自定义修改令牌内容时,这些假定可能不再正确。 应用程序必须明确承认令牌已被修改,以防自己被恶意行为者创建的自定义内容的影响。 采用以下方式之一防止不适当的自定义:
如果没有此项,Microsoft Entra ID 将返回 AADSTS50146 错误代码。
配置自定义签名密钥
对于多租户应用,应使用自定义签名密钥。 请勿在应用部件清单 (manifest) 中设置 acceptMappedClaims
。 在 Azure 门户中设置应用时,你将在租户中获得一个应用注册对象和一个服务主体。 该应用使用 Azure 全局登录密钥,该密钥不能用来自定义令牌中的声明。 若要获取令牌中的自定义声明,请从证书创建自定义登录密钥,并将其添加到服务主体。 对于测试,可以使用自签名的证书。 配置自定义签名密钥后,应用程序代码需要验证令牌签名密钥。
向服务主体添加以下信息:
从证书的 PFX 文件导出中提取 base-64 编码的私钥和公钥。 确保用于“签名”的 keyCredential
的 keyId
与 passwordCredential
的 keyId
相匹配。 可通过获取证书指纹的哈希来生成 customkeyIdentifier
。
请求
注意
首先从 Microsoft Entra 管理中心的“应用注册”边栏选项卡对新创建的应用禁用任何服务主体锁定配置,然后再尝试对服务主体执行 PATCH,这会导致显示“400 错误请求”。
下面的示例显示了用于向服务主体添加自定义签名密钥的 HTTP PATCH 请求的格式。 为便于阅读,keyCredentials
属性中的“密钥”值已缩短。 该值是 base-64 编码的。 对于私钥,属性用法为“Sign
”。 对于公钥,属性用法为“Verify
”。
PATCH https://graph.microsoft.com/v1.0/servicePrincipals/aaaaaaaa-bbbb-cccc-1111-222222222222
Content-type: servicePrincipals/json
Authorization: Bearer {token}
{
"keyCredentials":[
{
"customKeyIdentifier": "aB1cD2eF3gH4iJ5kL6-mN7oP8qR=",
"endDateTime": "2021-04-22T22:10:13Z",
"keyId": "aaaaaaaa-0b0b-1c1c-2d2d-333333333333",
"startDateTime": "2020-04-22T21:50:13Z",
"type": "X509CertAndPassword",
"usage": "Sign",
"key":"cD2eF3gH4iJ5kL6mN7-oP8qR9sT==",
"displayName": "CN=contoso"
},
{
"customKeyIdentifier": "aB1cD2eF3gH4iJ5kL6-mN7oP8qR=",
"endDateTime": "2021-04-22T22:10:13Z",
"keyId": "bbbbbbbb-1c1c-2d2d-3e3e-444444444444",
"startDateTime": "2020-04-22T21:50:13Z",
"type": "AsymmetricX509Cert",
"usage": "Verify",
"key": "cD2eF3gH4iJ5kL6mN7-oP8qR9sT==",
"displayName": "CN=contoso"
}
],
"passwordCredentials": [
{
"customKeyIdentifier": "aB1cD2eF3gH4iJ5kL6-mN7oP8qR=",
"keyId": "cccccccc-2d2d-3e3e-4f4f-555555555555",
"endDateTime": "2022-01-27T19:40:33Z",
"startDateTime": "2020-04-20T19:40:33Z",
"secretText": "mypassword"
}
]
}
使用 PowerShell 配置自定义签名密钥
使用 PowerShell 实例化 MSAL 公共客户端应用程序,并使用授权代码授予流获取 Microsoft Graph 的委托权限访问令牌。 使用访问令牌调用 Microsoft Graph,并为服务主体配置自定义签名密钥。 配置自定义签名密钥后,应用程序代码需要验证令牌签名密钥。
若要运行此脚本,你需要:
- 应用程序服务主体的对象 ID,位于 Azure 门户中企业应用程序内应用程序条目的“概述”边栏选项卡中。
- 应用注册,用于登录应用和获取用于调用 Microsoft Graph 的访问令牌。 在 Azure 门户的应用注册中应用程序条目的“概述”边栏选项卡中获取此应用的应用程序(客户端)ID。 应用注册应具有以下配置:
- “移动和桌面应用程序”平台配置中列出的“http://localhost"”的重定向 URI。
- 在“API 权限”中,Microsoft Graph 委托权限 Application.ReadWrite.All 和 User.Read(确保对这些权限授予管理员同意)。
- 登录以获取 Microsoft Graph 访问令牌的用户。 用户应是以下 Microsoft Entra 管理角色之一(更新服务主体所必需):
- 云应用管理员
- 应用程序管理员
- 要配置为应用程序的自定义签名密钥的证书。 可以创建自签名证书,也可以从你信任的证书颁发机构获取一个证书。 脚本中使用了以下证书组件:
- 公钥(通常为 .cer 文件)
- PKCS#12 格式的私钥(.pfx 文件)
- 私钥的密码(.pfx 文件)
重要
私钥必须采用 PKCS#12 格式,因为 Microsoft Entra ID 不支持其他格式类型。 使用 Microsoft Graph 通过包含证书信息的 keyCredentials
来“修补”服务主体时,使用错误格式可能会导致“证书无效: 密钥值是无效证书”错误。
##########################################################
# Replace the variables below with the appropriate values
$fqdn="yourDomainHere" # This is used for the 'issued to' and 'issued by' field of the certificate
$pwd="password" # password for exporting the certificate private key
$tenantId = "aaaabbbb-0000-cccc-1111-dddd2222eeee" # Replace with your Tenant ID
$appObjId = "aaaaaaaa-0000-1111-2222-bbbbbbbbbbbb" # Replace with the Object ID of the App Registration
##########################################################
# Create a self-signed cert
$cert = New-SelfSignedCertificate -certstorelocation cert:\currentuser\my -DnsName $fqdn
$pwdSecure = ConvertTo-SecureString -String $pwd -Force -AsPlainText
$path = 'cert:\currentuser\my\' + $cert.Thumbprint
$location="C:\\temp" # path to folder where both the pfx and cer file will be written to
$cerFile = $location + "\\" + $fqdn + ".cer"
$pfxFile = $location + "\\" + $fqdn + ".pfx"
# Export the public and private keys
Export-PfxCertificate -cert $path -FilePath $pfxFile -Password $pwdSecure
Export-Certificate -cert $path -FilePath $cerFile
$pfxpath = $pfxFile # path to pfx file
$cerpath = $cerFile # path to cer file
$password = $pwd # password for the pfx file
# Check PowerShell version (minimum 5.1) (.Net) or PowerShell Core (.Net Core) and read the certificate file accordingly
if ($PSVersionTable.PSVersion.Major -gt 5)
{
$core = $true
}
else
{
$core = $false
}
# this is for PowerShell Core
$Secure_String_Pwd = ConvertTo-SecureString $password -AsPlainText -Force
# reading certificate files and creating Certificate Object
if ($core)
{
$pfx_cert = get-content $pfxpath -AsByteStream -Raw
$cer_cert = get-content $cerpath -AsByteStream -Raw
$cert = Get-PfxCertificate -FilePath $pfxpath -Password $Secure_String_Pwd
}
else
{
$pfx_cert = get-content $pfxpath -Encoding Byte
$cer_cert = get-content $cerpath -Encoding Byte
# calling Get-PfxCertificate in PowerShell 5.1 prompts for password - using alternative method
$cert = [System.Security.Cryptography.X509Certificates.X509Certificate2]::new($pfxpath, $password)
}
# base 64 encode the private key and public key
$base64pfx = [System.Convert]::ToBase64String($pfx_cert)
$base64cer = [System.Convert]::ToBase64String($cer_cert)
# getting id for the keyCredential object
$guid1 = New-Guid
$guid2 = New-Guid
# get the custom key identifier from the certificate thumbprint:
$hasher = [System.Security.Cryptography.HashAlgorithm]::Create('sha256')
$hash = $hasher.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($cert.Thumbprint))
$customKeyIdentifier = [System.Convert]::ToBase64String($hash)
# get end date and start date for our keycredentials
$endDateTime = ($cert.NotAfter).ToUniversalTime().ToString( "yyyy-MM-ddTHH:mm:ssZ" )
$startDateTime = ($cert.NotBefore).ToUniversalTime().ToString( "yyyy-MM-ddTHH:mm:ssZ" )
# building our json payload
$object = [ordered]@{
keyCredentials = @(
[ordered]@{
customKeyIdentifier = $customKeyIdentifier
endDateTime = $endDateTime
keyId = $guid1
startDateTime = $startDateTime
type = "AsymmetricX509Cert"
usage = "Sign"
key = $base64pfx
displayName = "CN=$fqdn"
},
[ordered]@{
customKeyIdentifier = $customKeyIdentifier
endDateTime = $endDateTime
keyId = $guid2
startDateTime = $startDateTime
type = "AsymmetricX509Cert"
usage = "Verify"
key = $base64cer
displayName = "CN=$fqdn"
}
)
passwordCredentials = @(
[ordered]@{
customKeyIdentifier = $customKeyIdentifier
displayName = "CN=$fqdn"
keyId = $guid1
endDateTime = $endDateTime
startDateTime = $startDateTime
secretText = $password
hint = $null
}
)
}
Connect-MgGraph -tenantId $tenantId -Scopes Application.ReadWrite.All
$graphuri = "https://graph.microsoft.com/v1.0/applications/$appObjId"
Invoke-MgGraphRequest -Method PATCH -Uri $graphuri -Body $object
$json = $object | ConvertTo-Json -Depth 99
Write-Host "JSON Payload:"
Write-Output $json
验证令牌签名密钥
启用了声明映射的应用必须通过将 appid={client_id}
追加到其 OpenID Connect 元数据请求来验证令牌签名密钥。 以下示例显示应使用的 OpenID Connect 元数据文档格式:
https://login.microsoftonline.com/{tenant}/v2.0/.well-known/openid-configuration?appid={client-id}
更新应用程序清单
对于单租户应用,可以在应用程序清单中将 acceptMappedClaims
属性设置为 true
。 如 apiApplication
资源类型中所述。 设置该属性允许应用程序使用声明映射,而无需指定自定义的签名密钥。
警告
对于多租户应用,请勿将 acceptMappedClaims 属性设置为 true,这可能会允许恶意参与者为应用创建声明映射策略。
请求令牌的受众需要使用经过验证的 Microsoft Entra 租户域名,这意味着应设置 Application ID URI
(在应用程序清单中由 identifierUris
表示),例如将其设置为 https://contoso.com/my-api
或(仅使用默认租户名)https://contoso.onmicrosoft.com/my-api
。
如果未使用经过验证的域,Microsoft Entra ID 将返回 AADSTS501461
错误代码以及消息“AcceptMappedClaims 仅支持与应用程序 GUID 匹配的令牌受众或经过验证的租户域中的受众。 请更改资源标识符,或使用特定于应用程序的签名密钥。”
高级声明选项
配置 OIDC 应用程序的高级声明选项,以公开与 SAML 令牌相同的声明。 还适用于打算对 SAML2.0 和 OIDC 响应令牌使用相同声明的应用程序。
通过选中管理声明边栏选项卡中的高级声明选项下的框来配置高级声明选项。