Microsoft 标识平台和 OAuth 2.0 隐式授权流
Microsoft 标识平台支持 OAuth 2.0 规范中所述的 OAuth 2.0 隐式授权流。 隐式授权的定义特征是令牌(ID 令牌或访问令牌)直接从 /authorize 终结点返回,而不是从 /token 终结点返回。 这通常在所谓的“混合流”中用作授权代码流的一部分 - 在 /authorize 请求中检索 ID 令牌以及授权代码。
本文介绍如何直接针对应用程序中的协议进行编程,以从 Microsoft Entra ID 请求令牌。 如果可能,建议你改用受支持的 Microsoft 身份验证库 (MSAL) 来获取令牌并调用受保护的 Web API。 有关使用 MSAL 的代码示例列表,请参阅 Microsoft 标识平台代码示例。
警告
Microsoft 建议不要使用隐式授权流。 在大多数情况下,可以使用我们建议的更安全的替代方案。 此流的某些配置需要对应用程序具有非常高的信任度,并携带其他流中不存在的风险。 仅当不能使用其他更安全的流时,才应使用此流。 有关更多信息,请参阅隐式授权流的安全问题。
协议图
下图显示了整个隐式登录流的样子,后续各部分详细介绍了每个步骤。
OAuth2 隐式授权的适用方案
只有对于登录流的初始交互式部分(其中缺少第三方 Cookie 不会影响你的应用程序),隐式授权才是可靠的。 此限制意味着,你只应将其用作混合流的一部分,在混合流中,你的应用程序从授权终结点请求代码和令牌。 在混合流中,应用程序会收到可兑换为刷新令牌的代码,从而确保应用的登录会话在一段时间内保持有效。
首选授权代码流
随着一些浏览器取消对第三方 cookie 的支持,隐式授权流不再是一种合适的身份验证方法。 如果没有第三方 Cookie,隐式流的无提示单一登录 (SSO) 功能将不起作用,导致应用程序在尝试获取新令牌时中断。 强烈建议你让所有新的应用程序都使用现在替代隐式流来支持单页应用的授权代码流。 现有单页应用也应该迁移到授权代码流。
隐式授权流的安全问题
隐式授权流适用于服务器可控制安全处理 POST 数据的传统 Web 应用程序。 使用隐式授权流传递令牌的主要方式有两种:一种是将 response_mode
作为 URL 片段返回,另一种是作为查询参数(使用 form POST
和 GET
)返回。 在 response_mode=form_post
的隐式流中,令牌通过 HTML 表单 POST 安全地传送到客户端的重定向 URI。 此方法可确保令牌不会在 URL 片段中公开,从而避免通过浏览器历史记录或引荐者标头泄露令牌的风险。
使用 response_mode=fragment
传递令牌时,会出现隐式流的安全问题。 URL 片段是 URL 中 #
符号后面的部分,当浏览器请求新页面时不会发送到服务器,但可供浏览器中运行的 JavaScript 使用。 这意味着令牌会暴露给页面上运行的任何 JavaScript,如果该页面包含第三方脚本,就可能存在安全风险。 SPA 中令牌的这种安全问题也不适用于 form POST
的隐式流。
当使用隐式授权或混合流进行请求时,应何时允许颁发访问令牌或 ID 令牌?
隐式授权和混合流不如其他 OAuth 流安全。 除非绝对必要,否则不应允许在应用注册中使用隐式授权或混合流进行请求时发出访问令牌或 ID 令牌。 如果你(或你的开发人员)在应用程序中使用 MSAL 来实现身份验证和授权,则两个字段都不需要启用。
但是,如果你(或你的开发人员)没有在应用程序中使用 MSAL,则下表概述了应该何时启用访问令牌或 ID 令牌。
要生成的应用程序类型 | 应在应用注册中启用的令牌 |
---|---|
一个不使用带 PKCE 的授权代码流的 SPA(单页应用程序) | 访问令牌和 ID 令牌 |
一个使用隐式流通过 JavaScript 调用 Web API 的 Web 或 SPA 应用程序 | 访问令牌和 ID 令牌 |
ASP.NET Core Web 应用和其他使用混合身份验证的 Web 应用 | ID 令牌 |
发送登录请求
最初将用户登录到应用时,可以发送 OpenID Connect 身份验证请求,并从 Microsoft 标识平台获取 id_token
。
重要
若要成功请求 ID 令牌和/或访问令牌,必须通过在“隐式授权和混合流”部分选择“ID 令牌”和“访问令牌”,为 Microsoft Entra 管理中心 - 应用注册页中的应用注册启用相应的隐式授权流。 如果未启用,将返回 unsupported_response
错误:
The provided value for the input parameter 'response_type' is not allowed for this client. Expected value is 'code'
// Line breaks for legibility only
https://login.partner.microsoftonline.cn/{tenant}/oauth2/v2.0/authorize?
client_id=00001111-aaaa-2222-bbbb-3333cccc4444
&response_type=id_token
&redirect_uri=http%3A%2F%2Flocalhost%2Fmyapp%2F
&scope=openid
&response_mode=fragment
&state=12345
&nonce=678910
参数 | 类型 | 描述 |
---|---|---|
tenant |
必需 | 请求路径中的 {tenant} 值可用于控制哪些用户可以登录应用程序。 可以使用的值包括 common 、organizations 、consumers 和租户标识符。 有关更多详细信息,请参阅协议基础知识。 至关重要的是,对于用户从一个租户登录到另一个租户的来宾场景,必须提供租户标识符才能让其正确登录到资源租户。 |
client_id |
必答 | Microsoft Entra 管理中心 - 应用注册页分配给应用的“应用程序(客户端) ID”。 |
response_type |
(必需) | 必须包含 OpenID Connect 登录的 id_token 。 也可以包含 response_type 、token 。 此处使用 token ,让应用能够立即从 /authorize 终结点接收访问令牌,而无需向 /authorize 终结点发出第二次请求。 如果使用 token response_type,scope 参数必须包含范围,以指出要对哪个资源(例如,Microsoft Graph 上的 user.read )发出令牌。 它还可以包含可提供授权代码的 code (取代 token ),该授权代码在授权代码流中使用。 此 id_token +code 响应有时称为混合流。 |
redirect_uri |
建议 | 应用的重定向 URI,可通过此 URI 在应用中发送和接收身份验证响应。 其必须完全符合在 Microsoft Entra 管理中心中注册的其中一个重定向 URI,否则必须是编码的 URL。 |
scope |
必答 | 范围的空格分隔列表。 针对 OpenID Connect (id_tokens ),即必须包含范围 openid ,其在同意 UI 中转换为“你将登录”权限。 或者,也可以包含 email 和 profile 范围,以获取对其他用户数据的访问权限。 也可以在此请求中包含其他范围,以请求对各种资源的许可(如果请求了访问令牌)。 |
response_mode |
建议 | 指定将生成的令牌送回到应用程序时应该使用的方法。 默认值 query 仅限访问令牌,但如果请求包括 id_token,则为 fragment 。 出于安全原因,建议对隐式流使用 form_post ,以确保令牌不会在 URL 片段中公开。 |
state |
建议 | 也会在令牌响应返回的请求中包含的值。 可以是想要的任何内容的字符串。 随机生成的唯一值通常用于 防止跨站点请求伪造攻击。 该 state 也用于在身份验证请求出现之前,于应用中编码用户的状态信息,例如之前所在的网页或视图。 |
nonce |
必答 | 由应用生成且包含在请求中的值,以声明方式包含在生成的 ID 令牌中。 应用随后便可确认此值,以减少令牌重新执行攻击。 该值通常是随机的唯一字符串,可用于标识请求的来源。 只有请求 id_token 时才是必需的。 |
prompt |
可选 | 表示需要的用户交互类型。 此时唯一有效的值为 login 、none 、select_account 和 consent 。 prompt=login 强制用户在该请求上输入其凭据,从而使单一登录无效。 prompt=none 则相反,它会确保用户不会看到任何交互式提示。 如果无法在无提示的情况下通过 SSO 完成请求,Microsoft 标识平台会返回一个错误。 prompt=select_account 将用户发送到一个帐户选取器,其中显示在会话中记住的所有帐户。 prompt=consent 会在用户登录之后触发 OAuth 同意对话框,要求用户向应用授予权限。 |
login_hint |
可选 | 如果事先知道用户名,可使用此参数为用户预先填充登录页面的用户名和电子邮件地址字段。 通常,应用在已经从前次登录提取 login_hint 可选声明后,会在重新身份验证时使用此参数。 |
domain_hint |
可选 | 如果包含,它会跳过用户在登录页面上经历的基于电子邮件的发现过程,导致稍微更加流畅的用户体验。 此参数通常用于在单个租户中运行的业务线应用,它们会提供给定租户中的域名,将用户转发给该租户的联合身份验证提供程序。 此提示会阻止来宾登录到此应用程序,并限制 FIDO 等云凭据的使用。 |
此时,系统会要求用户输入其凭据并完成身份验证。 Microsoft 标识平台确保用户已同意 scope
查询参数中指定的权限。 如果用户未曾同意这些权限的任何一项,则请求用户同意请求的权限。 有关更多信息,请参阅权限、同意和多租户应用。
用户经过身份验证并授予同意后,Microsoft 标识平台将使用 response_mode
参数中指定的方法,将响应返回到位于所指示的 redirect_uri
的应用。
成功的响应
使用 response_mode=fragment
和 response_type=id_token+code
的成功响应如下所示(为方便阅读,包含了换行符):
GET https://localhost/myapp/#
code=0.AgAAktYV-sfpYESnQynylW_UKZmH-C9y_G1A
&id_token=eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6Ik5HVEZ2ZEstZnl0aEV1Q...
&state=12345
参数 | 说明 |
---|---|
code |
如果 response_type 包含 code ,则包含该参数。 它是适合在授权代码流中使用的授权代码。 |
access_token |
如果 response_type 包含 token ,则包含该参数。 应用请求的访问令牌。 访问令牌不得进行解码或检查,应当作为不透明字符串对待。 |
token_type |
如果 response_type 包含 token ,则包含该参数。 此项始终为 Bearer 。 |
expires_in |
如果 response_type 包含 token ,则包含该参数。 表示令牌有效的秒数(针对缓存目的)。 |
scope |
如果 response_type 包含 token ,则包含该参数。 指示 access_token 有效的一个或多个范围。 可能不包括所有请求的范围(如果它们不适用于用户)。 |
id_token |
有符号 JSON Web 令牌 (JWT)。 应用可以解码此令牌的段,以请求有关登录用户的信息。 应用可以缓存并显示这些值,但不应依赖它们来获取任何授权或安全边界。 有关 ID 令牌的详细信息,请参阅 id_token reference 。 注意: 仅当请求了 openid 作用域且 response_type 包括 id_tokens 时才提供。 |
state |
如果请求中包含 state 参数,响应中就应该出现相同的值。 应用应该验证请求和响应中的 state 值是否完全相同。 |
警告
请勿尝试在代码中验证或读取你未拥有的任何 API 的令牌,包括此示例中的令牌。 Microsoft 服务的令牌可以使用将不会作为 JWT 进行验证的特殊格式,还可能会针对使用者(Microsoft 帐户)用户进行加密。 虽然可以通过读取令牌的操作进行调试和学习,但请不要在代码中依赖此操作,也不要假定不是你控制的 API 的令牌的相关具体信息。
错误响应
错误响应可能也发送到 redirect_uri
,让应用可以适当地处理:
GET https://localhost/myapp/#
error=access_denied
&error_description=the+user+canceled+the+authentication
参数 | 说明 |
---|---|
error |
可用于分类发生的错误类型与响应错误的错误码字符串。 |
error_description |
帮助开发人员识别身份验证错误根本原因的特定错误消息。 |
以无提示方式获取访问令牌
现在,用户已登录到单页应用,可以通过无提示方式获取访问令牌以调用受到 Microsoft 标识平台保护的 Web API,例如 Microsoft Graph。 即使已使用 token
response_type 收到令牌,仍然可以使用此方法获取其他资源的令牌,而无需再次将用户重定向到登录页。
重要
隐式流的这一部分不太可能适用于你的应用程序,因为默认情况下删除了第三方 Cookie,所以它用于各种浏览器中。 虽然目前这仍然适用于不在 Incognito 中的基于 Chromium 的浏览器,但开发人员应该重新考虑这部分流的使用。 在不支持第三方 Cookie 的浏览器中,你会收到一条错误消息,指出没有用户登录,因为浏览器删除了登录页面的会话 Cookie。
在正常的 OpenID Connect/OAuth 流中,可以通过对 Microsoft 标识平台 /token
终结点进行请求来实现此目的。 你可以在隐藏的 iframe 中进行请求,以获取其他 Web API 的新令牌:
// Line breaks for legibility only
https://login.partner.microsoftonline.cn/{tenant}/oauth2/v2.0/authorize?
client_id=00001111-aaaa-2222-bbbb-3333cccc4444&response_type=token
&redirect_uri=http%3A%2F%2Flocalhost%2Fmyapp%2F
&scope=https%3A%2F%2Fmicrosoftgraph.chinacloudapi.cn%2Fuser.read
&response_mode=fragment
&state=12345
&nonce=678910
&prompt=none
&login_hint=myuser@mycompany.com
有关 URL 中的查询参数的详细信息,请参阅发送登录请求。
提示
尝试使用来自应用注册的真实 client_id
和 username
将以下请求复制并粘贴到浏览器选项卡中。 这样便可查看无提示令牌请求的实际操作。
https://login.partner.microsoftonline.cn/common/oauth2/v2.0/authorize?client_id={your-client-id}&response_type=token&redirect_uri=http%3A%2F%2Flocalhost%2Fmyapp%2F&scope=https%3A%2F%2Fmicrosoftgraph.chinacloudapi.cn%2Fuser.read&response_mode=fragment&state=12345&nonce=678910&prompt=none&login_hint={username}
请注意,即使是在不支持第三方 Cookie 的浏览器中,也可以这样做,因为你要将其直接输入到浏览器栏中,而不是在 iframe 中打开它。
借助 prompt=none
参数,此请求立即成功或立即失败,并返回到应用程序。 响应会通过 response_mode
参数中指定的方法发送到位于所指示的 redirect_uri
的应用。
成功的响应
使用 response_mode=fragment
的成功响应如下所示:
GET https://localhost/myapp/#
access_token=eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6Ik5HVEZ2ZEstZnl0aEV1Q...
&state=12345
&token_type=Bearer
&expires_in=3599
&scope=https%3A%2F%2Fmicrosoftgraph.chinacloudapi.cn%2Fdirectory.read
参数 | 说明 |
---|---|
access_token |
如果 response_type 包含 token ,则包含该参数。 应用请求的访问令牌,在本例中为 Microsoft Graph 的访问令牌。 访问令牌不得进行解码或检查,应当作为不透明字符串对待。 |
token_type |
此项始终为 Bearer 。 |
expires_in |
表示令牌有效的秒数(针对缓存目的)。 |
scope |
指示访问令牌有效的一个或多个范围。 可能不包括所有请求的范围(如果它们不适用于用户)。 |
id_token |
有符号 JSON Web 令牌 (JWT)。 如果 response_type 包含 id_token ,则包含该参数。 应用可以解码此令牌的段,以请求有关登录用户的信息。 应用可以缓存并显示这些值,但不应依赖它们来获取任何授权或安全边界。 有关 id_tokens 的详细信息,请参阅id_token 参考。 注意: 仅当已请求 openid 作用域时提供。 |
state |
如果请求中包含 state 参数,响应中就应该出现相同的值。 应用应该验证请求和响应中的 state 值是否完全相同。 |
错误响应
错误响应还可能发送到 redirect_uri
,因此应用可以适当地对其进行处理。 如果 prompt=none
,则预期错误为:
GET https://localhost/myapp/#
error=user_authentication_required
&error_description=the+request+could+not+be+completed+silently
参数 | 说明 |
---|---|
error |
可用于分类发生的错误类型与响应错误的错误码字符串。 |
error_description |
帮助开发人员识别身份验证错误根本原因的特定错误消息。 |
如果在 iframe 请求中收到此错误,用户必须再次以交互方式登录以检索新令牌。 可以选择对应用程序合理的任何方式处理这种情况。
刷新令牌
隐式授权不提供刷新令牌。 ID 令牌和访问令牌很快就会过期,因此应用必须准备好定期刷新这些令牌。 要刷新任一类型的令牌,可以通过使用 prompt=none
参数控制 Microsoft 标识平台的行为,执行前面概述的同一隐藏的 iframe 请求。 要接收新的 ID 令牌,请务必使用 response_type
和 scope=openid
中的 id_token
,以及 nonce
参数。
在不支持第三方 Cookie 的浏览器中,这会导致错误,指出没有用户登录。
发送注销请求
OpenID Connect end_session_endpoint
允许应用向 Microsoft 标识平台发送请求以结束用户的会话,并清除 Microsoft 标识平台设置的 Cookie。 要完全将用户从 Web 应用程序注销,应用应结束自己与用户的会话(通常通过清除令牌缓存或删除 Cookie 实现),然后将浏览器重定向到:
https://login.partner.microsoftonline.cn/{tenant}/oauth2/v2.0/logout?post_logout_redirect_uri=https://localhost/myapp/
参数 | 类型 | 描述 |
---|---|---|
tenant |
必需 | 请求路径中的 {tenant} 值可用于控制哪些用户可以登录应用程序。 可以使用的值包括 common 、organizations 、consumers 和租户标识符。 有关详细信息,请参阅协议基础知识。 |
post_logout_redirect_uri |
建议 | 注销完成后用户应返回到的 URL。 该值必须与为应用程序注册的重定向 URI 之一匹配。 如果未包含,Microsoft 标识平台会向用户显示一条常规消息。 |
另请参阅
- 重温 MSAL JS 示例以开始编码。
- 复习授权代码流这种较新的、可取代隐式授权的授权方式。