OAuth授权模式
翻译
原文:https://portswigger.net/web-security/oauth/grant-types
- name: 翻译
desc: 原文:https://portswigger.net/web-security/oauth/grant-types
bgColor: '#F0DFB1'
textColor: 'green'
2
3
4
# 1OAuth授权模式
在本节中,我们将介绍两种最常见 OAuth 授权模式的基础知识。如果你完全不熟悉 OAuth,我们建议你在完成 OAuth 身份验证 (opens new window)的实验之前,先阅读本章节。
# 2什么是OAuth授权模式?
OAuth 授权模式决定了 OAuth 流程中的确切步骤顺序。授权模式还会影响客户端应用程序在每个阶段如何与 OAuth 服务进行通信,包括如何发送访问令牌。因此,授权模式通常被称为 “OAuth 流”。
必须先将 OAuth 服务配置为支持特定的授权模式,然后客户端应用程序才能请求并启动相应的流。客户端应用程序在发送 OAuth 服务的初始授权请求中,指定要使用的授权模式。
有多种不同的授权模式,每种模式都有不同程度的复杂性 和 安全考虑因素。我们将重点关注 “授权码” 和 “隐式” 授权模式,因为它们是当下最常见的。
# 3OAuth作用域
对于任何 OAuth 授权模式,客户端应用程序都必须指定 它将要访问哪些数据,以及要执行什么类型的操作。它通过 OAuth 服务授权请求中的scope
参数来实现此目的。
对于基本 OAuth,客户端应用程序可以请求所访问的数据范围,这对于每个 OAuth 服务都是唯一的。由于作用域的名称只是一个任意的文本字符串,因此不同程序之间提供的格式可能会有很大差异。有些程序甚至使用完整的 URI 来作为作用域名称,类似于 REST API 端点。例如,当请求对用户联系人列表的读访问权限时,作用域名称可能采用以下任何形式,具体取决于所使用的 OAuth 服务:
scope=contacts
scope=contacts.read
scope=contact-list-r
scope=https://oauth-authorization-server.com/auth/scopes/user/contacts.readonly
2
3
4
但是,当使用 OAuth 进行身份验证时,一般会使用标准化的 OpenID Connect 作用域。例如,作用域openid profile
会将一组预定义的用户基本信息(例如其电子邮件地址、用户名等)的读访问权限授予给客户端应用程序。我们稍后将详细讨论 OpenID Connect (opens new window)。
# 4授权码模式
授权码模式一开始看起来很复杂,但当你熟悉了一些基础知识之后,它实际上比你想象的要简单。
简单来说,客户端应用程序和 OAuth 服务首先会使用重定向,来交换一系列基于浏览器的 HTTP 请求,这些请求将会启动 “流”。系统会询问用户是否同意该请求的访问。如果用户接受,则客户端应用程序会被授予 “授权码”。然后,客户端应用程序与 OAuth 服务交换此代码以接收 “访问令牌”,它们可以使用该令牌进行 API 调用来获取相关的用户数据。
从代码 / 令牌交换开始,期间发生的所有通信都经过安全的、预配置的反向通道,在服务器到服务器之间传输,因此对最终用户是不可见的。在客户端应用程序首次向 OAuth 服务注册时,将会建立此安全通道。同一时间,还会生成一个client_secret
,客户端应用程序在发送这些服务器到服务器请求时,必须使用这个client_secret
来验证自己的身份。
由于最敏感的数据(访问令牌和用户数据)不会通过浏览器发送,因此这种授权类型可以说是最安全的。如果可能的话,理想情况下,服务端应用程序应该始终使用这种授权模式。
# 4.11. 授权请求
客户端应用程序向 OAuth 服务的/authorization
端点发送请求,请求访问特定用户数据的权限。请注意,端点的映射可能因提供商而异 - 我们的实验室使用端点/auth
来实现此目的。但是,你应该根据请求中使用的参数来自行识别端点。
GET /authorization?client_id=12345&redirect_uri=https://client-app.com/callback&response_type=code&scope=openid%20profile&state=ae13d489bd00e3c24 HTTP/1.1
Host: oauth-authorization-server.com
2
此请求包含以下值得注意的参数,通常在查询字符串中提供:
client_id
1必需参数,包含客户端应用程序的唯一标识符。该值是在客户端应用程序向 OAuth 服务注册时生成的。
redirect_uri
1向客户端应用程序发送授权码时,用户的浏览器应重定向至该 URI。这也称为 “回调 URI” 或 “回调端点”。许多 OAuth 攻击都基于此参数的验证缺陷。
response_type
1客户端应用程序指定期望的响应类型,从而确定它要启动哪种流。对于授权码模式,该值应为
code
。scope
1客户端应用程序指定要访问的用户数据子集。请注意,这些数据范围可能是由 OAuth 提供程序所设置的自定义作用域,也可能是由 OpenID Connect 规范定义的标准化作用域。我们稍后将更详细地介绍 OpenID Connect (opens new window)。
state
1存储一个唯一的、不可猜测的值,该值与客户端应用程序上的当前会话绑定。OAuth 服务应该在响应中返回这个确切值以及授权码。此参数充当客户端应用程序的 CSRF 令牌,确保后续请求
/callback
端点的和发起 OAuth 流的是同一人。
# 4.22. 用户登录并同意
当授权服务器收到初始请求时,它会将用户重定向到登录页面,在该页面上,系统会提示用户使用 OAuth 提供程序登录其帐户。这通常是他们的社交媒体帐户。
然后,服务器会向用户展示 客户端应用程序 想要访问的数据列表。这基于授权请求中定义的作用域(数据范围)。用户可以选择是否同意此访问。
请务必注意,一旦用户批准了客户端应用程序的给定范围,只要用户仍与 OAuth 服务保持有效会话,此步骤就会自动完成。换句话说,用户第一次选择 “使用社交媒体登录” 时,他们需要手动登录并同意,但如果他们稍后重新访问客户端应用程序,他们通常只需单击一下即可重新登录。
# 4.33. 下发授权码
如果用户同意请求的访问,则用户的浏览器将被重定向至授权请求redirect_uri
参数中指定的/callback
端点。生成的GET
请求会将授权码作为查询参数。根据配置,该请求还可能携带授权请求中指定的state
参数。
GET /callback?code=a1b2c3d4e5f6g7h8&state=ae13d489bd00e3c24 HTTP/1.1
Host: client-app.com
2
# 4.44. 令牌访问请求
客户端应用程序收到授权码后,需要用其来交换访问令牌。为此,它会向 OAuth 服务的/token
端点发送服务器到服务器的POST
请求。从这时开始,所有通信都发生在安全的反向信道中,因此,攻击者通常无法进行观察或控制行为。
POST /token HTTP/1.1
Host: oauth-authorization-server.com
…
client_id=12345&client_secret=SECRET&redirect_uri=https://client-app.com/callback&grant_type=authorization_code&code=a1b2c3d4e5f6g7h8
2
3
4
除了client_id
和授权code
之外,你应该还会注意到以下新参数:
client_secret
1在向 OAuth 服务注册时会分配一个密钥,客户端应用程序必须包含该密钥,来对自身进行身份验证。
grant_type
1确保新端点知道 客户端应用程序 要使用的授权模式。在这种情况下,应该将其设置为
authorization_code
。
# 4.55. 下发访问令牌
OAuth 服务将会验证令牌访问请求。如果一切正常,服务器将会对客户端应用程序做出响应,为其下发预期作用域的访问令牌
{
"access_token": "z0y9x8w7v6u5",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "openid profile",
…
}
2
3
4
5
6
7
# 4.66. API调用
现在,客户端应用程序拥有了访问令牌,它终于可以从资源服务器获取用户的数据了。为此,它会向 OAuth 服务的/userinfo
端点发出 API 调用。访问令牌会在Authorization: Bearer
标头中提交,以证明客户端应用程序有权访问此数据。
GET /userinfo HTTP/1.1
Host: oauth-resource-server.com
Authorization: Bearer z0y9x8w7v6u5
2
3
# 4.77. 资源授予
资源服务器应验证令牌是否有效,以及它是否属于当前客户端应用程序。如果是,它会返回所请求的资源(基于访问令牌作用域的用户数据)。
{
"username":"carlos",
"email":"carlos@carlos-montoya.net",
…
}
2
3
4
5
最终,客户端应用程序可以将这些数据用于其预期目的。在 OAuth 身份验证的场景下,这通常被用作一个 ID ,为用户授予经过身份验证的会话,从而有效地登录其账户。
# 5隐式授权模式
隐式授权模式要简单得多。客户端应用程序不会获取授权码,然后用其交换访问令牌,而是在用户同意之后直接获得访问令牌。
你可能想知道,为什么客户端应用程序并不总是使用隐式授权模式。答案很简单——它的安全性要低得多。在使用隐式授权类型时,所有的通信都通过浏览器重定向来进行 - 没有像授权码模式那样的安全反向通道。这意味着,敏感的访问令牌和用户数据更容易受到潜在攻击。
隐式授权模式更适合单页应用程序 和 本机桌面应用程序,因为这些应用程序无法轻松地将client_secret
存储在后端,因此,这些程序在使用 授权码模式 时不会带来太大的好处。
# 5.11. 授权请求
隐式流的启动方式,与授权码流大致相同。唯一的区别是response_type
参数必须设置为token
。
GET /authorization?client_id=12345&redirect_uri=https://client-app.com/callback&response_type=token&scope=openid%20profile&state=ae13d489bd00e3c24 HTTP/1.1
Host: oauth-authorization-server.com
2
# 5.22. 用户登录并同意
用户登录并决定是否同意所请求的权限。此过程与授权码流完全相同。
# 5.33. 下发访问令牌
如果用户同意该请求的访问,这就是事情开始不同的地方。OAuth 服务会将用户的浏览器重定向至授权请求中指定的redirect_uri
。但是,它不会发送包含授权码的查询参数,而是直接将访问令牌和其他特定于令牌的数据作为 URL 片段发送。
GET /callback#access_token=z0y9x8w7v6u5&token_type=Bearer&expires_in=5000&scope=openid%20profile&state=ae13d489bd00e3c24 HTTP/1.1
Host: client-app.com
2
(译者加:URL 片段是指#
后面的锚点数据)
由于访问令牌是在 URL 片段中发送的,因此,令牌永远不会直接发送给客户端应用程序。相反,客户端应用程序必须使用合适的脚本来提取片段并存储它。
# 5.44. API调用
客户端应用程序成功从 URL 片段中提取访问令牌之后,可以对 OAuth 服务的/userinfo
端点发出 API 调用。与授权码流不同,这种调用也可以通过浏览器进行。
GET /userinfo HTTP/1.1
Host: oauth-resource-server.com
Authorization: Bearer z0y9x8w7v6u5
2
3
# 5.55. 资源授予
资源服务器应验证令牌是否有效,以及它是否属于当前客户端应用程序。如果是,它会返回所请求的资源(基于访问令牌作用域的用户数据)。
{
"username":"carlos",
"email":"carlos@carlos-montoya.net"
}
2
3
4
最终,客户端应用程序可以将这些数据用于其预期目的。在 OAuth 身份验证的场景下,这通常被用作一个 ID ,为用户授予经过身份验证的会话,从而有效地登录其账户。
学习更多
现在,你已经对不同流程的工作原理有了更多的了解,你应该能够阅读我们的学习材料,来了解如何利用基于 OAuth 的身份验证机制中的漏洞。