Microsoft 标识平台和 OAuth 2.0 客户端凭据流

OAuth 2.0 客户端凭据授权流允许 Web 服务(机密客户端)在调用其他 Web 服务时使用它自己的凭据(而不是模拟用户)进行身份验证。 RFC 6749 中指定的授予(有时称为 两条腿的 OAuth)可用于使用应用程序的标识以访问 Web 托管的资源。 此类型通常用于必须在后台运行的服务器到服务器交互,无需与用户立即交互,通常称为 守护程序服务帐户

在客户端凭据流中,由管理员向应用程序本身直接授予权限。 当应用程序向资源出示令牌时,资源强制应用本身具有执行操作的授权,因为没有用户参与身份验证。 本文介绍了以下内容所需的两个步骤:

本文介绍如何在应用程序中直接针对协议进行编程。 如果可能,建议你改用受支持的 Microsoft 身份验证库 (MSAL) 来获取令牌并调用受保护的 Web API。 还可以参考 使用 MSAL 的示例应用。 顺便提一下,永远不会向此流授予刷新令牌,因为可改为使用 client_idclient_secret(获取刷新令牌所必需的)获取访问令牌。

为了进行更高级别的保证,Microsoft 标识平台还允许调用服务使用证书或联合凭据(而不是共享机密)进行身份验证。 由于正在使用应用程序自己的凭据,因此这些凭据必须保持安全。 不要 在源代码中发布该凭据、将其嵌入网页或用于广泛分布的本机应用程序中。

协议图

整个客户端凭据流类似于下图。 本文稍后介绍每个步骤。

Diagram showing the client credentials flow

获取直接授权

应用往往通过以下两种方式之一接收直接授权来访问资源:

这两种方法在 Microsoft Entra ID 中很常用,建议用于执行客户端凭据流的客户端和资源。 资源也可选择以其他方式向其客户端授权。 每个资源服务器可选择最适合其应用程序的方法。

访问控制列表

资源提供程序可根据它所知并对其授予特定级别访问权限的应用程序(客户端)ID 列表,强制实施授权检查。 资源从 Microsoft 标识平台接收令牌时,可将此令牌解码,并从 appidiss 声明中提取客户端的应用程序 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=535fb089-9ff3-47b6-9bfb-4f1264799865
&state=12345
&redirect_uri=http://localhost/myapp/permissions

专业提示:请尝试将以下请求粘贴到浏览器中。

https://login.microsoftonline.com/common/adminconsent?client_id=535fb089-9ff3-47b6-9bfb-4f1264799865&state=12345&redirect_uri=http://localhost/myapp/permissions
参数 条件 描述
tenant 必需 要向其请求权限的目录租户。 此参数可采用 GUID 或友好名称格式。 如果不知道用户属于哪个租户并想让他们登录到任一租户,请使用 common
client_id 必须 Microsoft Entra 管理中心 - 应用注册体验分配给应用的“应用程序(客户端) ID”。
redirect_uri 必须 要向其发送响应,供应用处理的重定向 URI。 其必须与门户中注册的其中一个重定向 URI 完全匹配,否则必须经过 URL 编码并可包含其他路径段。
state 建议 同时随令牌响应返回的请求中所包含的值。 可以是所需的任何内容的字符串。 该 state 用于在身份验证请求出现之前,于应用中编码用户的状态信息,例如之前所在的页面或视图。

此时,Microsoft Entra ID 强制要求只有租户管理员可以登录来完成请求。 系统会要求管理员批准在应用注册门户中针对应用请求的所有直接应用程序权限。

成功的响应

如果管理员批准了应用程序的权限,成功响应如下所示:

GET http://localhost/myapp/permissions?tenant=a8990e1f-ff32-408a-9f8e-78d3b9139b95&state=state=12345&admin_consent=True
参数 说明
tenant 向应用程序授予所请求权限的目录租户(采用 GUID 格式)。
state 同样随令牌响应返回的请求中所包含的值。 可以是所需的任何内容的字符串。 该 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=535fb089-9ff3-47b6-9bfb-4f1264799865
&scope=https%3A%2F%2Fgraph.microsoft.com%2F.default
&client_secret=sampleCredentials
&grant_type=client_credentials
# Replace {tenant} with your tenant!
curl -X POST -H "Content-Type: application/x-www-form-urlencoded" -d 'client_id=535fb089-9ff3-47b6-9bfb-4f1264799865&scope=https%3A%2F%2Fgraph.microsoft.com%2F.default&client_secret=qWgdYAmab0YSkuL1qKv5bPX&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=97e0a5b7-d745-40b6-94fe-5f77d35c6e05
&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_typeclient_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=97e0a5b7-d745-40b6-94fe-5f77d35c6e05
&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: 255d1aef-8c98-452f-ac51-23d051240864\r\nCorrelation ID: fb3d2015-bc17-4bb9-bb85-30c5cf1aaaa7\r\nTimestamp: 2016-01-09 02:02:12Z",
  "error_codes": [
    70011
  ],
  "timestamp": "YYYY-MM-DD HH:MM:SSZ",
  "trace_id": "255d1aef-8c98-452f-ac51-23d051240864",
  "correlation_id": "fb3d2015-bc17-4bb9-bb85-30c5cf1aaaa7"
}
参数 说明
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 eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6Ik5HVEZ2ZEstZnl0aEV1Q...

在终端中尝试以下命令,确保将令牌替换为自己的令牌。

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 以显示租户的用户