Microsoft标识平台和 OAuth 2.0 客户端凭据流
OAuth 2.0 客户端凭据授予流允许 Web 服务(机密客户端)使用自己的凭据,而不是模拟用户,以便在调用另一个 Web 服务时进行身份验证。 RFC 6749 中指定的授予(有时称为“两条腿的 OAuth”)可用于使用应用程序的标识以访问 Web 托管的资源。 此类型通常用于必须在后台运行的服务器到服务器交互,而无需与用户立即交互,通常称为 守护程序 或 服务帐户。
在客户端凭据流中,权限由管理员直接授予应用程序。 当应用向资源提供令牌时,该资源会强制应用本身具有执行操作的授权,因为身份验证中没有涉及任何用户。 本文介绍以下两个步骤:
- 授权应用程序调用 API
- 如何获取调用该 API所需的令牌。
本文介绍如何直接针对应用程序中的协议进行编程。 如果可能,建议改为使用受支持的 Microsoft 身份验证库 (MSAL) 来获取令牌并调用受保护的 Web API。 还可以参考使用 MSAL的 client_id
和 client_secret
(获取刷新令牌所必需的)获取访问令牌。
为了获得更高级别的保证,Microsoft标识平台还允许调用服务使用 证书 或联合凭据(而不是共享机密)进行身份验证。 由于正在使用应用程序自己的凭据,因此这些凭据必须保持安全。 不要在源代码中发布该凭据、将其嵌入网页或用于广泛分布的本机应用程序中。
协议关系图
整个客户端凭据流类似于下图。 本文稍后将介绍每个步骤。
获取直接授权
应用通常通过以下两种方式之一接收直接授权以访问资源:
这两种方法是Microsoft Entra ID 中最常见的方法,我们建议它们用于执行客户端凭据流的客户端和资源。 资源还可以选择以其他方式授权其客户端。 每个资源服务器都可以选择最适合其应用程序的方法。
访问控制列表
资源提供程序可能会根据它知道的应用程序(客户端)ID 列表强制执行授权检查,并授予特定级别的访问权限。 当资源从Microsoft标识平台接收令牌时,它可以解码令牌并从 appid
和 iss
声明中提取客户端的应用程序 ID。 然后,它将应用程序与它维护的访问控制列表(ACL)进行比较。 ACL 的粒度和方法在资源之间可能有很大差异。
常见用例是使用 ACL 为 Web 应用程序或 Web API 运行测试。 Web API 只能向特定客户端授予一部分完全权限。 若要在 API 上运行端到端测试,可以创建一个测试客户端,用于从Microsoft标识平台获取令牌,然后将其发送到 API。 然后,API 会检查测试客户端的应用程序 ID 的 ACL,以便完全访问 API 的整个功能。 如果使用此类 ACL,请务必不仅验证调用方 appid
值,还要验证令牌的 iss
值是否受信任。
对于需要访问拥有个人 Microsoft 账户的普通用户数据的守护程序和服务帐户而言,此类授权非常常见。 对于组织拥有的数据,建议通过应用程序权限获得必要的授权。
控制没有 roles
声明的令牌
若要启用此基于 ACL 的授权模式,Microsoft Entra ID 不要求应用程序获得授权以获取另一个应用程序的令牌。 因此,可以在没有 roles
声明的情况下颁发仅限应用的令牌。 公开 API 的应用程序必须实现权限检查才能接受令牌。
如果要阻止应用程序获取应用程序的无角色仅限应用的访问 令牌,请确保为应用启用分配要求。 这会阻止没有分配角色的用户和应用程序获取此应用程序的令牌。
应用程序权限
可以使用 API 来公开一组 应用程序权限,而不是使用 ACL。 这些内容由组织的管理员授予应用程序,并且只能用于访问该组织及其员工拥有的数据。 例如,Microsoft Graph 公开多个应用程序权限以执行以下操作:
- 读取所有邮箱中的邮件
- 在所有邮箱中读取和写入邮件
- 以任何用户身份发送邮件
- 读取目录数据
若要将应用角色(应用程序权限)用于自己的 API(与 Microsoft Graph 相反),必须首先在 Microsoft Entra 管理中心中 API 的应用注册中公开应用角色。 然后,通过在客户端应用程序的应用注册中选择这些权限来配置所需的应用角色。 如果尚未在 API 的应用注册中公开任何应用角色,则无法在 Microsoft Entra 管理中心的客户端应用程序的应用注册中指定对该 API 的应用程序权限。
在以应用程序身份(而不是用户)进行身份验证时,不能使用 委托权限,因为应用没有用户可以代表其执行操作。 必须使用由管理员或 API 所有者授予的应用程序权限(也称为应用角色)。
有关应用程序权限的详细信息,请参阅 权限和同意。
建议:将管理员登录到应用以分配应用角色
通常,当你生成使用应用程序权限的应用程序时,应用需要一个页面或视图,管理员会批准应用的权限。 此页可以是应用登录流的一部分、应用设置的一部分,或是专用的“连接”流。 通常合理的结果是应用只在用户使用工作或学校 Microsoft 帐户登录后才显示此“连接”视图。
如果你将用户登录到应用,可以在要求用户批准应用程序权限之前确定用户所属的组织。 尽管并非严格必要,但它可以帮助你为用户创建更直观的体验。 要登录用户,请按照 Microsoft 身份验证平台协议教程操作。
从目录管理员请求权限
当您准备好向组织的管理员请求权限时,可以将用户重定向到 Microsoft 标识平台 管理员同意终结点。
// Line breaks are for legibility only.
GET https://login.microsoftonline.com/{tenant}/adminconsent?
client_id=00001111-aaaa-2222-bbbb-3333cccc4444
&state=12345
&redirect_uri=http://localhost/myapp/permissions
专业提示:尝试在浏览器中粘贴以下请求。
https://login.microsoftonline.com/common/adminconsent?client_id=00001111-aaaa-2222-bbbb-3333cccc4444&state=12345&redirect_uri=http://localhost/myapp/permissions
参数 | 条件 | 描述 |
---|---|---|
tenant |
必需 | 要向其请求权限的目录租户。 此参数可采用 GUID 或友好名称格式。 如果不知道用户属于哪个租户并想让他们登录到任一租户,请使用 common 。 |
client_id |
必需 | Microsoft Entra 管理中心 - 应用注册体验分配给应用的应用程序(客户端)ID。 |
redirect_uri |
必需 | 要向其发送响应以供应用处理的重定向 URI。 它必须与在门户中注册的其中一个重定向 URI 完全匹配,只不过它必须经过 URL 编码,并且可以具有其他路径段。 |
state |
建议 | 同时随令牌响应返回的请求中所包含的值。 它可以是用户想要的任何内容的字符串。 该状态用于在身份验证请求发生前对应用中用户状态的信息进行编码,例如他们打开的页面或视图。 |
此时,Microsoft Entra ID 强制只有租户管理员可以登录以完成请求。 系统会要求管理员批准你在应用注册门户中为应用请求的所有直接应用程序权限。
成功的响应
如果管理员批准应用程序的权限,成功的响应如下所示:
GET http://localhost/myapp/permissions?tenant=aaaabbbb-0000-cccc-1111-dddd2222eeee&state=state=12345&admin_consent=True
参数 | 描述 |
---|---|
tenant |
向应用程序授予所请求权限的目录租户(采用 GUID 格式)。 |
state |
同样随令牌响应返回的请求中所包含的值。 它可以是您想要的任何内容组成的字符串。 该状态用于在身份验证请求发生前对应用中用户状态的信息进行编码,例如他们打开的页面或视图。 |
admin_consent |
设置为 True。 |
错误响应
如果管理员未批准应用程序的权限,失败的响应如下所示:
GET http://localhost/myapp/permissions?error=permission_denied&error_description=The+admin+canceled+the+request
参数 | 描述 |
---|---|
error |
可用于对错误类型进行分类的错误代码字符串,以及可用于对错误做出反应的字符串。 |
error_description |
一条特定的错误消息,可帮助你识别错误的根本原因。 |
从应用预配终结点收到成功响应后,应用获得了它请求的直接应用程序权限。 现在便可为所需资源请求令牌。
获取令牌
获取应用程序所需的授权后,继续获取 API 的访问令牌。 若要通过使用客户端凭据授权获取令牌,请将 POST 请求发送到 /token
Microsoft身份验证平台。 有几个不同的情况:
第一种情况:使用共享密钥访问令牌请求
POST /{tenant}/oauth2/v2.0/token HTTP/1.1 //Line breaks for clarity
Host: login.microsoftonline.com:443
Content-Type: application/x-www-form-urlencoded
client_id=00001111-aaaa-2222-bbbb-3333cccc4444
&scope=https%3A%2F%2Fgraph.microsoft.com%2F.default
&client_secret=qWgdYAmab0YSkuL1qKv5bPX
&grant_type=client_credentials
# Replace {tenant} with your tenant!
curl -X POST -H "Content-Type: application/x-www-form-urlencoded" -d 'client_id=00001111-aaaa-2222-bbbb-3333cccc4444&scope=https%3A%2F%2Fgraph.microsoft.com%2F.default&client_secret=A1bC2dE3f...&grant_type=client_credentials' 'https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token'
参数 | 条件 | 描述 |
---|---|---|
tenant |
必需 | 应用程序计划对其进行操作的目录租户,采用 GUID 或域名格式。 |
client_id |
必需 | 分配给应用的应用程序 ID。 可以在注册应用的门户中找到此信息。 |
scope |
必需 | 为此请求中 scope 参数传递的值应该是所需的资源的资源标识符(应用程序 ID URI),后缀为 .default 。 包括的所有范围都必须用于单个资源。 包括多个资源的范围将会导致错误。 对于 Microsoft Graph 示例,该值是 https://graph.microsoft.com/.default 。 此值告知 Microsoft 标识平台:在为应用配置的所有直接应用程序权限中,终结点应为与要使用的资源关联的权限颁发令牌。 若要详细了解 /.default 范围,请参阅 同意文档。 |
client_secret |
必需 | 在应用注册门户中为应用生成的客户端密码。 在发送之前,客户端密码必须经过 URL 编码。 还支持根据 RFC 6749 在授权标头中提供凭据的基本身份验证模式。 |
grant_type |
必需 | 必须设置为 client_credentials 。 |
第二种情况:使用证书访问令牌请求
POST /{tenant}/oauth2/v2.0/token HTTP/1.1 // Line breaks for clarity
Host: login.microsoftonline.com:443
Content-Type: application/x-www-form-urlencoded
scope=https%3A%2F%2Fgraph.microsoft.com%2F.default
&client_id=11112222-bbbb-3333-cccc-4444dddd5555
&client_assertion_type=urn%3Aietf%3Aparams%3Aoauth%3Aclient-assertion-type%3Ajwt-bearer
&client_assertion=eyJhbGciOiJSUzI1NiIsIng1dCI6Imd4OHRHeXN5amNScUtqRlBuZDdSRnd2d1pJMCJ9.eyJ{a lot of characters here}M8U3bSUKKJDEg
&grant_type=client_credentials
参数 | 条件 | 描述 |
---|---|---|
tenant |
必需 | 应用程序计划对其进行操作的目录租户,采用 GUID 或域名格式。 |
client_id |
必需 | 分配给应用的应用程序(客户端)ID。 |
scope |
必需 | 为此请求中 scope 参数传递的值应该是所需的资源的资源标识符(应用程序 ID URI),后缀为 .default 。 包括的所有范围都必须用于单个资源。 包括多个资源的范围将会导致错误。 对于 Microsoft Graph 示例,该值是 https://graph.microsoft.com/.default 。 此值告知 Microsoft 标识平台:在为应用配置的所有直接应用程序权限中,终结点应为与要使用的资源关联的权限颁发令牌。 若要详细了解 /.default 范围,请参阅 同意文档。 |
client_assertion_type |
必需 | 该值必须设置为 urn:ietf:params:oauth:client-assertion-type:jwt-bearer 。 |
client_assertion |
必需 | 断言(JSON Web 令牌),需使用作为凭据向应用程序注册的证书进行创建和签名。 有关如何注册证书以及断言的格式,请阅读证书凭据的相关信息。 |
grant_type |
必需 | 必须设置为 client_credentials 。 |
基于证书的请求的参数与基于共享机密的请求只有一种方式不同:client_secret
参数由 client_assertion_type
和 client_assertion
参数替换。
第三种情况:使用联合凭据的访问令牌请求
POST /{tenant}/oauth2/v2.0/token HTTP/1.1 // Line breaks for clarity
Host: login.microsoftonline.com:443
Content-Type: application/x-www-form-urlencoded
scope=https%3A%2F%2Fgraph.microsoft.com%2F.default
&client_id=11112222-bbbb-3333-cccc-4444dddd5555&client_assertion_type=urn%3Aietf%3Aparams%3Aoauth%3Aclient-assertion-type%3Ajwt-bearer
&client_assertion=eyJhbGciOiJSUzI1NiIsIng1dCI6Imd4OHRHeXN5amNScUtqRlBuZDdSRnd2d1pJMCJ9.eyJ{a lot of characters here}M8U3bSUKKJDEg
&grant_type=client_credentials
参数 | 条件 | 描述 |
---|---|---|
client_assertion |
必需 | 应用程序从 Microsoft 标识平台之外的另一个标识提供者(如 Kubernetes)获取的断言(JWT 或 JSON Web 令牌)。 必须在应用程序上将此 JWT 的详细信息注册为联合标识凭据。 阅读有关工作负载标识联合身份验证的信息,了解如何设置和使用从其他标识提供者生成的断言。 |
请求中的所有内容都与基于证书的流程相同,唯一区别在于client_assertion
的来源。 在此流中,应用程序不会创建 JWT 断言本身。 相反,你的应用使用由另一个标识提供者创建的 JWT。 这称为工作负载标识联合身份验证,其中另一个标识平台中的应用标识用于获取 Microsoft 标识平台中的令牌。 这最适合跨云方案,例如在 Azure 外部托管计算资源,但访问受 Microsoft 标识平台保护的 API。 有关由其他标识提供者创建的 JWT 所需格式的信息,请阅读断言格式。
成功的响应
来自任何方法的成功响应如下所示:
{
"token_type": "Bearer",
"expires_in": 3599,
"access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6Ik1uQ19WWmNBVGZNNXBP..."
}
参数 | 描述 |
---|---|
access_token |
请求的访问令牌。 应用可以使用此令牌向安全资源(例如 Web API)进行身份验证。 |
token_type |
指示标记类型值。 Microsoft标识平台支持的唯一类型是 bearer 。 |
expires_in |
访问令牌有效的时间量(以秒为单位)。 |
警告
请勿尝试在代码中验证或读取你未拥有的任何 API 的令牌,包括此示例中的令牌。 Microsoft 服务的令牌可以使用将不会作为 JWT 进行验证的特殊格式,还可能会针对使用者(Microsoft 帐户)用户进行加密。 虽然读取令牌是一种有用的调试和学习工具,但不要在代码中依赖它,也不要对不属于你控制的 API 的令牌做出具体假设。
错误响应
错误响应(400 错误请求)如下所示:
{
"error": "invalid_scope",
"error_description": "AADSTS70011: The provided value for the input parameter 'scope' is not valid. The scope https://foo.microsoft.com/.default is not valid.\r\nTrace ID: 0000aaaa-11bb-cccc-dd22-eeeeee333333\r\nCorrelation ID: aaaa0000-bb11-2222-33cc-444444dddddd\r\nTimestamp: 2016-01-09 02:02:12Z",
"error_codes": [
70011
],
"timestamp": "YYYY-MM-DD HH:MM:SSZ",
"trace_id": "0000aaaa-11bb-cccc-dd22-eeeeee333333",
"correlation_id": "aaaa0000-bb11-2222-33cc-444444dddddd"
}
参数 | 描述 |
---|---|
error |
一个错误代码字符串,可用于对发生的错误类型进行分类,以及对错误做出反应。 |
error_description |
一条特定的错误消息,可帮助识别身份验证错误的根本原因。 |
error_codes |
特定于 STS 的错误代码列表,这些错误代码可能有助于诊断。 |
timestamp |
发生错误的时间。 |
trace_id |
可帮助进行诊断的请求的唯一标识符。 |
correlation_id |
此请求的唯一标识符,可帮助诊断跨组件的问题。 |
使用令牌
获取令牌后,请使用令牌向资源发出请求。 令牌过期时,对 /token
终结点重复请求以获取新的访问令牌。
GET /v1.0/users HTTP/1.1
Host: graph.microsoft.com:443
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbG...
在终端中尝试以下命令,确保将令牌替换为自己的令牌。
curl -X GET -H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbG..." 'https://graph.microsoft.com/v1.0/users'
代码示例和其他文档
阅读 Microsoft 身份验证库中的 客户端凭据概述文档
示例 | 平台 | 描述 |
---|---|---|
active-directory-dotnetcore-daemon-v2 | .NET 6.0+ | 一个 ASP.NET Core 应用程序,它使用应用程序的标识(而不是代表用户)查询 Microsoft Graph 以显示租户的用户。 此示例还演示了使用证书进行身份验证的变体。 |
active-directory-dotnet-daemon-v2 | ASP.NET MVC | 一个 Web 应用程序,该应用程序使用应用程序的标识来同步 Microsoft Graph 的数据,而不是代表用户来同步。 |
ms-identity-javascript-nodejs-console | Node.js 控制台 | Node.js 应用程序:使用应用程序的标识查询 Microsoft Graph 以显示租户的用户 |