本文希望以应用场景的角度出发,帮助大家快随了解OAuth协议流程,更为清楚明白的介绍在各种情况使用什么授权模式更为合适。
OAuth2 官网
原文地址
本系列相关文章:
OpenID Connect 协议入门指南
SAML2.0入门指南
1. 协议中各种角色:应用、API和用户
第三方应用:客户端
客户端,即尝试去获得用户账号信息的应用,用户需要先对此操作授权。
API: 资源服务器
即资源服务器,提供用来获得用户信息的API。
授权服务器
授权服务器用来提供接口,让用户同意或者拒接访问请求。在某些情况下,授权服务器和API资源服务器会是同一个,但是在大多数情况下,二者是独立的。
用户:资源的拥有者
资源的拥有者,当前的请求正在尝试获得他们账户的部分信息。
2. 创建App
在开始OAuth流程之前,你首选需要注册一个新应用到服务器。通常在注册的过程中,需要提供应用的基本信息,比如应用名称、网站信息、logo等等。此外,你还需要注册一个重定向URI,用以将用户通过浏览器或者移动客户端重定向回Web服务器,这个URI很重要,在后面的我们会具体讲到。
2.1 重定向URI
服务器只会把用户重定向回注册过的URI以避免一些安全攻击。任何HTTP的从定向请求都必须使用TLS保护,所以要求使用HTTPS。这样的要求是为了防止token在传输的过程中被截获。对于原生APP,重定向的URI可以被注册为一个自定义的URL scheme,比如
2.2 Client ID & Secret
当注册完毕之后,一般会返回给用户一个客户端ID(Client ID)和一个客户端密钥(Client Secret)。这个ID一般是公开的信息,用来构造登录URL,或者被包含在页面的JS代码中。这个密钥(Secret) 必须是保密的。如果一个已经部署的应用不能保护密钥,比如一个简单的JS网页,则不应该使用客户端密钥,同时理论上服务器也不应该向这类应用颁发密钥。
3. 授权:得到授权码
OAuth 2流程的第一步是获得用户的授权。对于基于浏览器应用或是移动应用,这一步通常是在服务器显示给用户的接口上完成。
OAuth 2提供了多种授权模式(grant types),根据不同的情况而使用。
- 授权码模式:适用于Web应用、浏览器应用或是移动APP;
- 口令模式:适用于使用用户名和口令登录的模式;
- 应用访问模式:适用于应用访问;
- 默认模式:之前被推荐在没有Secret情况下使用,现在被没有客户端密钥的授权码模式取代。
每一个模式的使用细节将在下面的章节中说明。
3.1 Web服务
Web服务是最常见的应用类型。用户所使用的Web App程序运行在服务器端,其源码不会公开暴露,这就代表着这类应用在授权的过程中可以使用客户端密钥(Client Secret),以避免某些攻击手段。
3.1.1 授权
创建登录链接,将用户重定向到授权服务器:
https://oauth2server.com/auth?response_type=code&
client_id=CLIENT_ID&redirect_uri=REDIRECT_URI&scope=photos&state=1234zyx
- code: 代表服务器希望收到授权码;
- client_id: 注册应用时颁发的ID;
- redirect_uri: 指明当用户授权完成之后返回的地址;
- scope: 一个或多个值,用来指明希望获得用户账户哪部分权限;
- state:由应用生成的一个随机字符串,会在稍后的过程中去验证。
通过以上的链接,用户将会看到如下的界面
当用户点击“Allow”之后,授权服务器会把用户重定向回应用网站,并在链接中带上授权码:
https://oauth2client.com/cb?code=AUTH_CODE_HERE&state=1234zyx
- code: 服务器返回的授权码;
- state: 返回请求授权码过程中发送的state;
当应有接收到这个请求时,要首先验证state是否就是应用刚才发送的值。在发送state之后,可以把state保存到Session以便于后续的比较。这样做的目的是防止应用接受任意伪造的授权码。
3.1.2 换取Token
然后使用授权码到资源服务器换取Token:
POST https://api.oauth2server.com/token
grant_type=authorization_code&
code=AUTH_CODE_HERE&
redirect_uri=REDIRECT_URI&
client_id=CLIENT_ID&
client_secret=CLIENT_SECRET
- grant_type=authorization_code - 代表授权模式为验证码;
- code=AUTH_CODE_HERE - 验证码;
- redirect_uri=REDIRECT_URI - 重定向URI;
- **client_id=CLIENT_ID **- 注册应用时颁发的ID;
- client_secret=CLIENT_SECRET - 客户端密钥,因为当前请求时服务器之间传输的,并没有暴露给用户。
API服务器会返回授权码和其有效期:
{
"access_token":"RsT5OjbzRn430zqMLgV3Ia",
"expires_in":3600
}
或者是授权失败的提示:
{
"error":"invalid_request"
}
安全性提示:服务器要求应用必须提前注册从定向URI
3.2 纯页面应用
纯页面应用,也称为浏览器应用,其所有运行代码都会加载到本地的浏览器上。因此其代码会暴露给使用者的浏览器,不能保护客户端密钥的安全,所以客户端密钥不能在这种情况下使用,除此之外其和Web应用没有太大区别。
以前,曾经推荐使用默认模式(implicit type),该种模式将会直接返回token,并不使用授权码来换取token。但是到了本文编写之时,业界已经开始转为推荐使用没有Scret的授权码流程,这样这样协议流程更为安全,具体内容请见引文: Redhat, Deutsche Telekom, Smart Health IT.
3.2.1 授权
创建登录链接,将用户发送至授权服务器:
https://oauth2server.com/auth?response_type=code&
client_id=CLIENT_ID&redirect_uri=REDIRECT_URI&scope=photos&state=1234zyx
- code - 指明使用授权码模式;
- client_id - 注册时颁发的client ID;
- redirect_uri - 重定向URI,代表当授权完成之后,返回的路径;
- scope - 一个或多个值,代表所希望访问当前用户的那部分信息;
-
state - 由于后续步骤验证实用的随机字符串。
之后,同样用户会显示授权页面:
当用户点击“Allow”,授权服务器依据重定向URI将用户返回到应用网站:
https://oauth2client.com/cb?code=AUTH_CODE_HERE&state=1234zyx
- code - 授权服务器返回的授权码;
- state - 登录请求中发出的随机码;
应有服务器收到该请求之后,应该先核对state的值,以避免接受伪造的授权码;
3.2.2 换取Token
POST https://api.oauth2server.com/token
grant_type=authorization_code&
code=AUTH_CODE_HERE&
redirect_uri=REDIRECT_URI&
client_id=CLIENT_ID
- grant_type=authorization_code - 代表授权模式为验证码;
- code=AUTH_CODE_HERE - 验证码;
- redirect_uri=REDIRECT_URI - 重定向URI;
- **client_id=CLIENT_ID **- 注册应用时颁发的ID;
3.3 移动APP
以前,曾经推荐使用默认模式(implicit type),该种模式将会直接返回token,并不使用授权码来换取token。但是到了本文编写之时,业界已经开始转为推荐使用没有Scret的授权码流程,这样这样协议流程更为安全,
类似于纯页面应用,移动应用不能包含客户端密钥,所以使用的是没有客户端密钥的Oauth流程。此外,对于移动APP,还有一些特别地方需要注意。
3.3.1 授权
创建一个登陆按钮,将用户跳转到服务器在手机上原生APP,或是通过手机的浏览器跳转到服务器Web页面、无论是IOS还是Android,都可以通过URL Scheme协议启动本地APP;
3.3.1.1 使用服务器的APP
如果用户安装了服务器的原生APP,比如微博APP或是微信APP,可以通过URL来直接启动APP:
fbauth2://authorize?response_type=code&client_id=CLIENT_ID
&redirect_uri=REDIRECT_URI&scope=email&state=1234zyx
- code - 指明使用授权码模式;
- client_id - 注册时颁发的client ID;
- redirect_uri - 重定向URI,代表当授权完成之后,返回的路径;
- scope - 一个或多个值,代表所希望访问当前用户的那部分信息;
- state - 由于后续步骤验证实用的随机字符串。
对于还要支持PKCE extension的服务器,还需要创建一个随机字符串("code verifier")保存本地,然后再URL中追加如下参数:
- code_challenge=XXXXXXX - 过base64编码后的"code verifier"的Hash值;
- code_challenge_method=S256 - 指明Hash算法种类,这里是sha256;
需要说明的是,这里的URL用的协议App提前向操作系统定义好的。
3.3.1.2 使用手机Web浏览器
如果服务器在手机上没有一个原生的APP,也可以通过手机浏览器上来走标准的Web认证流程。这里需要注意的是,不要使用应用内置的WebView,因为这样就不无法保证用户正在登陆网站是不是伪造的。
一般是使用移动端的原生浏览器,对于IOS 9+版本的用户,可以使用“SafariViewController”在应用中启动嵌入浏览器,这个API会显示地址栏让用户判断是否打开正确的网页,同时还提供和Safari浏览器共享cookie的功能。该API能保证应用被注入和修改,所以可以被认为是安全的。对于Android开发者,可以考虑使用Chrome Custom Tabs来提供类似的功能。
https://facebook.com/dialog/oauth?response_type=code&client_id=CLIENT_ID
&redirect_uri=REDIRECT_URI&scope=email&state=1234zyx
参数的意义和使用服务器原生APP的情况是一样的。比如用户登录FaceBook,就会在手机上看到如下界面:
3.3.2 换取Token
当用户点击"Approve"之后,用户将会被重定向到应用,同样可以使用URL Scheme:
fb00000000://authorize#code=AUTHORIZATION_CODE&state=1234zyx
应用收到该请求之后,要先比较state的值是否满足预期,然后用授权码来换取Token。
换取Token的过程和在Web中请求类似,只不过没有加入Client Secret。如果服务器需要支持PKCE,则还需要添加更多的参数:
POST https://api.oauth2server.com/token
grant_type=authorization_code&
code=AUTH_CODE_HERE&
redirect_uri=REDIRECT_URI&
client_id=CLIENT_ID&
code_verifier=VERIFIER_STRING
- grant_type=authorization_code - 标识这是授权码模式;
- code=AUTH_CODE_HERE - 授权码;
- redirect_uri=REDIRECT_URI -重定向URL;
- client_id=CLIENT_ID - Client ID;
- code_verifier=VERIFIER_STRING - 之前步骤中,生成的随机字符串
如果服务器支持PKCE,则授权服务器需要能“Code-Challenge”中识别code,也就是提供再次对code_verifier进行Hash,并且比较计算出的Hash值和之前的code_challenge是否一致,以此代替Client Secret,保证安全性。
3.4 其他授权模式
3.4.1 口令模式
这种模式直接使用用户名和口令来取回Token。很显然,这要求应用去收集用户的口令,所以只建议当前的APP是专门为服务器设计的情况下使用。比如Twitter的App使用该模式登录。
POST https://api.oauth2server.com/token
grant_type=password&
username=USERNAME&
password=PASSWORD&
client_id=CLIENT_ID
- grant_type=password - 指明使用口令模式;
- username=USERNAME - 用户名;
- password=PASSWORD - 口令;
- client_id=CLIENT_ID - client ID;
资源服务器返回的Token和其他模式是一样的。值得注意的是,因为口令模式多用于移动端和桌面应用,使用Secret并不合适。
3.4.2 应用访问模式
在一些情况下,是应用去访问服务区获取资源,而不是某个用户。比如应用去获取一些服务在应用配置信息,这些信息不属于某个用户的。在这种情形下,应用也需要被授权,得到Token去访问数据,这就是应用访问模式的意义。
OAuth提供了client_credentials模式来结局这个问题:
POST https://api.oauth2server.com/token
grant_type=client_credentials&
client_id=CLIENT_ID&
client_secret=CLIENT_SECRET
资源服务器返回形式和其他模式是相同的。
4. 创建已经授权的请求
最后就是使用Token获取资源,如何表面一个请求是已经呗授权?就是Http的头中加入Token:
curl -H "Authorization: Bearer RsT5OjbzRn430zqMLgV3Ia" \
https://api.oauth2server.com/1/me
另外还要确保是使用HTTPS通信,这样才能确保信息安全。
5. 与OAuth 1.0版本的差异
最后说明下两个版本的OAuth协议有什么不同点。
5.1 认证和签名
OAuth 1.0 流程中有大量密码学上的签名操作,以保证数据完整性;OAuth 2中有HTTPS来保护数据。
5.2 用户体验
和OAuth 1.0相比,OAuth 2在移动端的体验更好。
5.3 性能
和OAuth 1.0相比,OAuth 2性能更好,减少了流程的步骤。
其他参考
Learn more about creating OAuth 2.0 Servers
PKCE Extension
Recommendations for Native Apps
More information is available on OAuth.net
Some content adapted from hueniverse.com.