1. What is an OAuth grant type?
OAuth授权类型决定了OAuth过程中涉及的步骤的确切顺序。授权类型还影响客户端应用程序在每个阶段与OAuth服务通信的方式,包括访问令牌本身的发送方式。因此,授予类型通常被称为“OAuth flows”。
在客户端应用程序可以启动相应的流之前,必须将OAuth服务配置为支持特定的授权类型。客户端应用程序指定它希望在发送给OAuth服务的初始授权请求中使用的授权类型。
有几种不同的授权类型,每种类型都有不同的复杂性和安全性考虑。我们将重点讨论“授权码”和“隐式”授权类型,因为它们是迄今为止最常见的。
2. OAuth scopes
对于任何OAuth授权类型,客户端应用程序必须指定它想要访问的数据和它想要执行的操作类型。向OAuth服务发起请求时,通过使用scope参数来实现。
对于基本的OAuth来说,OAuth服务为每一个客户端提供的scope都是唯一的。因为scope的名字是一串任意字符,客户端提供的scope可能格式有很大不同。有些甚至使用完整的url做为scope名,类似于restful API接口。例如,当请求读取用户的联系人列表时,scope可能采取以下任何一种形式,这取决于正在使用的OAuth服务:
scope=contacts
scope=contacts.read
scope=contact-list-r
scope=https://oauth-authorization-server.com/auth/scopes/user/contacts.readonly
然而,当使用OAuth进行身份验证时,通常使用标准化的OpenID Connect scopes代替。例如,scope openid 属性将授予客户端应用程序对关于用户的一组预定义的基本信息的读访问权,例如他们的电子邮件地址、用户名等。稍后我们将更多地讨论OpenID Connect。
3. Authorization code grant type
授权码模式刚开始接触感觉很复杂,但是了解他的一些基础知识后,实际上比你想的要简单。
简而言之,客户端应用程序和OAuth服务首先使用重定向来交换启动流的一系列基于浏览器的HTTP请求。询问用户是否同意请求的访问。如果他们接受,客户端应用程序将被授予一个“授权代码”。客户端应用程序拿着授权码去OAuth服务,换取“访问令牌”,可以使用该令牌进行API调用以获取相关的用户数据。
从代码/令牌交换开始发生的所有通信都通过一个安全的、预先配置的后端通道发送到服务器,因此,对最终用户是不可见的。这个安全通道是在客户端应用程序首次向OAuth服务注册时建立的。此时,还会生成client_secret,客户端应用程序在发送这些服务器到服务器请求时必须使用它对自身进行身份验证。
由于最敏感的数据(访问令牌和用户数据)不是通过浏览器发送的,因此这种授权类型可以说是最安全的。如果可能,服务器端应用程序应该始终使用这种授权类型。
3.1 Authorization request
客户端应用程序向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
该请求包含以下值得注意的参数,通常在查询字符串中提供:
client_id, 必选参数,包含客户端应用程序的唯一标识符。这个值是在客户端应用程序向OAuth服务注册时生成的。
redirect_uri, 当向客户端应用程序发送授权代码时,用户的浏览器应该重定向到该URI。这也称为“回调URI”或“回调端点”。许多OAuth攻击都是基于利用这个参数验证中的缺陷。
response_type, 确定客户端应用程序期望的响应类型,从而确定它希望启动的流。对于授权代码grant类型,值应该是code。
scope, 用于指定客户机应用程序想要访问的用户数据的哪个子集。请注意,这些范围可能是OAuth提供者设置的自定义范围,也可能是OpenID Connect规范定义的标准化范围。稍后我们将更详细地介绍OpenID Connect。
state, 存储客户端应用程序上绑定到当前会话的惟一的、不可猜测的值。OAuth服务应该在响应中返回这个准确的值,以及授权代码。通过确保对其/回调端点的请求来自发起OAuth流的同一个人,此参数可作为客户机应用程序的CSRF令牌的一种形式。
3.2 User login and consent
当授权服务器接收到初始请求时,它将把用户重定向到一个登录页面,在那里将提示用户使用OAuth提供者登录到他们的帐户。例如,这通常是他们的社交媒体账号。
然后将向用户显示客户端应用程序想要访问的数据列表。这是基于授权请求中定义的范围。用户可以选择是否同意这种访问。
重要的是要注意,一旦用户批准了客户端应用程序的给定范围,只要用户仍然拥有与OAuth服务的有效会话,这个步骤将自动完成。换句话说,当用户第一次选择“Log In with social media”时,他们需要手动登录并表示同意,但如果他们之后再次访问客户端应用程序,他们通常只需点击一下就可以重新登录。
3.3 Authorization code grant
如果用户同意了请求,他们的浏览器将被重定向到授权请求的redirect_uri参数中指定的/callback 端点。得到的GET请求将包含授权代码作为查询参数。根据配置的不同,它还可以发送与授权请求中相同的state参数值。
GET /callback?code=a1b2c3d4e5f6g7h8&state=ae13d489bd00e3c24 HTTP/1.1
Host: client-app.com
3.4 Access token request
一旦客户端应用程序接收到授权代码,它需要将其交换为访问令牌。为此,它向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
除了client_id和授权代码之外,您还会注意到以下新参数:
client_secret, 客户端应用程序必须验证client_secret,该参数是客户端向OAuth服务注册时分配的密钥。
grant_type,用于确保新端点知道客户端应用程序想要使用的授权类型。在本例中,应该将其设置为authorization_code。
3.5 Access token grant
OAuth服务将验证获取access token的请求。如果验证通过,服务器通过授予客户端应用程序一个具有请求范围的access token来响应。
{
"access_token": "z0y9x8w7v6u5",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "openid profile",
…
}
3.6 API call
现在客户端应用程序有了access code,它最终可以从资源服务器获取用户数据。为此,它对OAuth服务的/userinfo 端点进行了一个API调用。access token 在 Authorization: bearing 报头中提交,以证明客户端应用程序有访问此数据的权限。
GET /userinfo HTTP/1.1
Host: oauth-resource-server.com
Authorization: Bearer z0y9x8w7v6u5
3.7 Resource grant
资源服务器应该验证令牌是否有效,以及它是否属于当前客户端应用程序。如果是,它将响应发送请求的资源,即基于访问令牌的范围的用户数据。
{
"username":"carlos",
"email":"carlos@carlos-montoya.net",
…
}
客户端应用程序最终可以将此数据用于其预期目的。在OAuth身份验证的情况下,它通常被用作ID来授予用户身份验证后的会话,从而有效地让用户登录。
4. Implicit grant type
隐式授权类型要简单得多。客户端应用程序在用户同意后立即接收access token,而不是首先获取access code,然后将其交换为access token。
您可能想知道为什么客户端应用程序不都使用隐式授权类型。答案相对简单——它远没有那么安全。当使用隐式授权类型时,所有通信都通过浏览器重定向进行—不存在像授权代码流中那样的安全后台通道。这意味着敏感访问令牌和用户数据更容易受到潜在的攻击。
隐式授权类型更适合单页应用程序和本机桌面应用程序,它们不能轻松地将client_secret存储在后端,因此不能使用access code方式以体现他的优势。
4.1 Authorization request
隐式流的启动方式与授权代码流非常相似。唯一的主要区别是必须将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
4.2 User login and consent
用户登录并决定是否同意请求的权限。此过程与授权代码流完全相同。
4.3 Access token grant
如果用户同意请求的访问,这就是事情开始不同的地方。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
由于访问令牌是在URL中发送的,所以它不会直接发送到客户端应用程序。相反,客户端应用程序必须使用合适的脚本来提取片段并存储它。
4.4 API call
一旦客户端应用程序成功地从URL片段中提取了access token,它就可以使用它对OAuth服务的/userinfo 端点进行API调用。与授权代码流不同,这也是通过浏览器进行的。
GET /userinfo HTTP/1.1
Host: oauth-resource-server.com
Authorization: Bearer z0y9x8w7v6u5
4.5 Resource grant
资源服务器应该验证令牌是否有效,以及它是否属于当前客户端应用程序。如果是,它将响应发送请求的资源,即基于与访问令牌关联的范围的用户数据。
{
"username":"carlos",
"email":"carlos@carlos-montoya.net"
}
客户端应用程序最终可以将此数据用于其预期目的。在OAuth身份验证的情况下,它通常被用作ID来授予用户身份验证后的会话,从而有效地让用户登录。