前言
OAuth2.0 在实际工作中会经常接触到,尤其是做帐号系统的开发对这块应该更加的熟悉。常见的场景是:第三方登录。当你想要登录某个站点,但是没有帐号,而这个站点接入了如 wechat,QQ,Sina 等登录功能,我们使用 QQ 等第三方登录的过程就是使用的 OAuth2.0 协议。我所在的公司帐号系统也有自己独立的一套授权登录系统,但是发现在实际对接过程中很多人没有真正理解 OAuth2.0,下面我们来了解下 OAuth 协议的基本原理
OAuth 是一个关于 授权(authorization) 网络开放标准,在全世界范围内广泛应用,目前的版本是 2.0 版,下面这段话摘选自百度百科
本文对 OAuth2.0 的设计思路和运行流程,做一个简明通俗的解释
应用场景
为了加深一下 OAuth 的适用场景,下面我们举一个例子看下。
有一个 云冲印 的网站,可以将用户存储在 Google 的照片,冲印出来。用户要想使用该服务,必须让 云冲印 读取自己存储在 Google 上的照片。
问题是只有得到用户的授权,Google 才会同意 云冲印 读取这些照片。那么,云冲印如何获取到用户的授权呢?
传统的方法是,用户将自己 Google 的帐号密码告诉 云冲印,后者就可以读取用户的照片了。但是这样的做法有几个严重的缺点。
- 帐号密码告诉云冲印,这样有可能会造成帐号密码泄露,很不安全
- 云冲印 拥有了获取用户存储在 Google 所有资料的权利,用户没办法限制 云冲印 获得授权的范围和有效期
- 用户只能通过修改密码,才能收回赋予 云冲印 的权利。但是这样做,会使得其他所有获得用户授权的第三方应用全部失效
- 只要有一个第三方应用程序被破解,就会导致用户密码泄露,以及所有被密码保护的数据泄露
OAuth 就是为了解决上面这些问题而诞生的
名词定义
在详细讲解 OAuth2.0 之前,需要了解几个专用名词。它对读懂后面的讲解,尤其是几张图,至关重要
- Third-party application:第三方应用程序,本文中又称 客户端,即上面例子中的 云冲印
- HTTP service:HTTP 服务提供商,本文中简称 服务提供商,即上面例子中的 Google
- Resource owner:资源所有者,本文中又称 用户
- User Agent:用户代理,本文中指浏览器
- Authorization server:认证服务器,即服务提供商专门用来处理认证的服务器
- Resource server:资源服务器,即服务提供商存放用户资源的服务器。它和认证服务器可以是同一台服务器,也可以是不同服务器
从上面的这些名词中,不难理解,OAuth 的作用就是让 客户端 安全可控地获取 用户 授权,与 服务提供商 进行互动
OAuth 的思路
从上面的名词中,我们发现在 客户端 与 服务提供商 之间,多了一层 授权层。客户端 不能直接登录 服务提供商,只能登录授权层,以此将 客户端 与 用户 区分开来。客户端 登录授权层所用的令牌(token),与用户的密码不同。用户可以在登录的时候指定授权层令牌的权限范围以及有效期
客户端 登录授权层以后,服务提供商 根据令牌的权限范围和有效期,向 客户端 开放用户存储的资源
运行流程
OAuth 2.0 的运行流程如下图,摘选自 RFC 6749
- (A) 用户打开客户端后,客户端要求用户给予授权
- (B) 用户同意授权给客户端
- (C) 客户端使用上一步获得的授权,向认证服务器申请令牌
- (D) 认证服务器对客户端进行认证后,确认无误,同意发放令牌
- (E) 客户端使用令牌,向资源服务器申请获取资源
- (F) 资源服务器确认令牌无误后,同意向客户端开放资源
从上面步骤中可以看出,B 是关键,即用户怎样才能给与客户端授权。有了这个授权后,客户端就可以向认证服务器申请令牌,获取令牌后进而凭借令牌获取资源服务器上的用户资源
下面我们来看下客户端获取授权的模式
客户端的授权模式
客户端必须得到用户的授权(authorization grant),才能进而获取令牌(token)。OAuth2.0 中定义了四种授权方式
- 授权码模式(authorization code)
- 简化模式(implicit)
- 密码模式(resource owner password credentials)
- 客户端模式(client credentials)
在上述四种模式中最常用的是 授权码模式(authorization code),我们熟悉的微博,QQ等都是这种模式,本篇文章中我们只介绍这种模式
授权码模式
授权码模式(authorization code)是功能最完整,流程最严密的授权模式。它的特点是通过客户端的后端服务器与 服务提供商 的认证服务器进行互动
图解
通过上面的图解,我们可以总结一下授权码模式的步骤:
- 用户访问第三方 APP,后者询问是否允许访问用户的第三方帐号,在得到用户允许后第三方 APP 向认证服务器发送一个授权请求
- 认证服务器校验第三方的请求合法后,认证服务器将用户导向第三方 APP 事先指定的 重定向URI ,同时附上一个授权码
- 第三方 APP 收到授权码之后,附上事前已经定好的 重定向URI,向认证服务器申请令牌。这一步是在第三方的后台服务器上完成的,对用户不可见
- 认证服务器在校验了授权码和重定向URI 的合法性,确认无误后,向客户端发送令牌(Access Token)
下面的参数是上面整个流程中所需要的参数:
response_type:表示授权类型,必选项,此处的固定值为 code
client_id:表示客户端的 ID,必选项
redirect_uri:表示重定向 URI,可选项
scope:表示申请的权限范围,可选项
state:可以指定任意值,认证服务器会原封不动的返回这个值
关于 state 值以后再起一篇文章讲解,该参数是用于 oauth 授权过程的安全问题,可以防止 CSRF
下面看一个简易的例子
GET /authorize?response_type=code&client_id=s6BhdRkqt3&state=xyz
&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb HTTP/1.1
Host: server.example.com
服务端返回给客户端的 URI 包含以下参数:
code:授权码,必选项。该授权码有一个有效期,可由服务方自己设定,且该授权码只能使用一次,否则会被服务器拒绝。
state:认证服务器会原样返回该参数
下面是一个例子:
HTTP/1.1 302 Found
Location: https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA
&state=xyz
在上面的步骤后第三方 APP 的后台服务器获取到了 code,开始向认证服务器发送申请令牌的 HTTP 请求,包含以下参数:
grant_type:表示使用的授权模式,必选项,此处的固定值为 authorization_code
code:表示上一步获得的授权码,必选项
redirect_uri:表示重定向 URI,必选项,且必须和上一步中的该参数表示一致
client_id:表示客户端 ID,必选项
下面是一个例子:
POST /token HTTP/1.1
Host: server.example.com
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code&code=SplxlOBeZQQYbYS6WxSbIA
&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb
认证服务器返回的数据中包含以下参数:
access_token:表示访问令牌,必选项
expires_in:表示过期时间,单位为秒
refresh_token:表示更新令牌,用来获取下一次的访问令牌,可选项
sope:表示权限范围,如果与客户端申请的范围一致,此项可省略
下面是一个例子:
HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
Cache-Control: no-store
Pragma: no-cache
{
"access_token":"2YotnFZFEjr1zCsicMWpAA",
"expires_in":3600,
"refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA",
"example_parameter":"example_value"
}
更新令牌
如果用户访问的时候,旧的令牌已经过期,则需要使用 更新令牌 申请一个新的访问令牌
第三方 APP 的后台服务器发出的 HTTP 请求,包含以下参数:
granttype:表示使用的授权模式,此处的值固定位 *refreshtoken* ,必选项
refresh_token:表示早期收到的更新令牌,必选项
scope:表示申请的权限范围,不可以超出上一次申请的范围,如果该参数省略,则表示与上一次保持一致
下面是一个例子:
POST /token HTTP/1.1
Host: server.example.com
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
Content-Type: application/x-www-form-urlencoded
grant_type=refresh_token&refresh_token=tGzv3JOkF0XG5Qx2TlKWIA