安全系列之二:OAuth2.0 开放授权协议
本文提取出OAuth2.0规范RFC6749的主要内容,部分内容从文档复制出来,给大家讲讲第三方授权背后的故事。
先是举个知乎的QQ登录授权的例子,然后讲四种授权方式,两种令牌,接着是看看协议流程,分析知乎的QQ登录授权请求响应报文解释OAuth2.0协议,最后简单看看QQ提供第三方授权的API加深理解。
先打个预防针,在讲解四种授权,两种令牌时大家可能会有点不懂,但是跟随着协议流程走就懂了。
如果觉得排版不好,可以访问我的博客。TAT
http://blog.bensonlin.me/post/oauth2
如果觉得写得不错,欢迎推荐,关注我和follow我的github(blog.bensonlin.me上有显示)。
2016/06/18发现有人未经同意复制我的文章到网站上,这里表示抗议,网站如下:
http://www.07net01.com/2016/06/1578637.html
http://www.w2bc.com/article/150992
下面是正文
什么是 OAuth
Open Authorization的缩写,即开放授权协议
OAuth 的授权使应用无需涉及另一方应用的帐号信息(如用户名与密码),只需要通过授权就可以另一方应用的信息,保证了安全性,不会泄露用户名密码。
而整个流程要怎么做才能保证安全呢,该协议定义了规则。
更多的定义可以自己查看OAuth的维基百科
举个栗子
第一步:当然是上知乎官网登录,点击QQ头像作为QQ登录
图1
第二步:登录QQ并授权,可以看到右边是QQ授权给知乎的内容,获取我们的昵称,头像和性别等信息。输入用户名密码登录,可以看到按钮显示的是登录并授权,也就是是点击后就已经说明我们同意让知乎获取我们的这些个人信息。
图2
因为我已经登录了,所以直接点上面的QQ头像(头像就是多啦A梦)直接授权。
图3
第三步:登录授权成功,可以在知乎的首页看到你的信息(还需要补充知乎的个人信息作为新用户注册)
图4
注意:有的网站授权,是先登录后,进入另一个页面然后再点击授权的。
PS:记住图的编号,后面分析用到
下面将进入正题,首先是上面涉及的几个角色,然后是协议流程等
几个角色
前提:知乎要用QQ登录,使用QQ的头像和昵称:
- 资源所有者(Resource Owner):能够许可受保护资源访问权限的实体。也就是拥有QQ资源(头像/昵称)的人,也就是我们。
- 资源服务器(Resource Server):托管受保护资源的服务器,能够接收和响应使用访问令牌对受保护资源的请求。也就是某个放置头像信息的QQ服务器
- 客户端(Client):使用资源所有者的授权代表资源所有者发起对受保护资源的请求的应用程序。也就是正在使用的知乎。
- 授权服务器(Authorization Server):在成功验证资源所有者且获得授权后颁发访问令牌给客户端的服务器。可以和资源服务器是同一台服务器,也可以是分离的个体。这里假设也是QQ服务器本身。
还有一个角色是资源所有者的用户代理(User Agent),一般就是我们的Web浏览器,我们需要浏览器作为代理才能够进行操作
四种授权方式
本规范定义了四种许可类型——授权码、隐式许可、资源所有者密码凭据和客户端凭据——以及用于定义其他类型的可扩展性机制。其中授权码是我们最常用的,因此单独讲授权码方式,其它类型详情可以自行下载RFC6749文档查看,下载地址在这里,搜索自己想要的文档,然后右键另存为下载即可
- 授权码:授权码通过使用授权服务器作为客户端与资源所有者的中介而获得。客户端不是直接从资源所有者请求授权,而是引导资源所有者至授权服务器授权服务器之后引导资源所有者带着授权码回到客户端。在引导资源所有者携带授权码返回客户端前,授权服务器会鉴定资源所有者身份并获得其授权。由于资源所有者只与授权服务器进行身份验证,所以资源所有者的凭据不需要与客户端分享。授权码提供了一些重要的安全益处,例如验证客户端身份的能力,以及向客户端直接的访问令牌的传输而非通过资源所有者的用户代理来传送它而潜在暴露给他人(包括资源所有者)。
- 隐式许可:在隐式许可流程中,不再给客户端颁发授权码,取而代之的是客户端直接被颁发一个访问令牌(作为资源所 有者的授权),授权服务器不对客户端进行身份验证。这种许可类型是隐式的,因为没有中间凭据(如授权码)被颁发(之后用于获取访问令牌)。
- 资源所有者密码凭据:资源所有者密码凭据(即用户名和密码),可以直接作为获取访问令牌的授权许可。这种凭据只能应该 当资源所有者和客户端之间具有高度信任时(例如,客户端是设备的操作系统的一部分,或者是一个高度特权应用程序),以及当其他授权许可类型(例如授权码)不可用时被使用。
- 客户端凭据:当授权范围限于客户端控制下的受保护资源或事先与授权服务器商定的受保护资源时客户端凭据可以被 用作为一种授权许可。典型的当客户端代表自己(客户端也是资源所有者)或者基于与授权服务器事先商定的授权请求对受保护资源的访问权限时,客户端凭据被用作为授权许可。
可以看到,授权码模式(authorization code)是功能最完整、流程最严密的授权模式。
两个令牌
- 访问令牌(access token):访问令牌是用于访问受保护资源的凭据(如知乎拿访问令牌访问受保护的我们的昵称和头像等)。访问令牌是一个代表授权服务器向客户端颁发的授权的字符串。该字符串通常对于客户端是不透明的。令牌代表了访问权限的由资源所有者许可并由资源服务器和授权服务器实施的具体范围和期限。(不透明的意思都知道吧,就是客户端(知乎服务器)能够得到访问令牌,因为它要根据这个令牌去获取QQ的个人信息。)
- 刷新令牌由授权服务器颁发给客户端,用于在当前访问令牌失效或过期时,获取一个新的访问令牌,或者获得相等或更窄范围的额外的访问令牌(访问令牌可能具有比资源所有者所授权的更短的生命周期和更少的权限)。颁发刷新令牌是可选的,由授权服务器决定。对客户端也是不透明的
(授权码许可)协议流程
+----------+
| Resource |
| Owner |
| |
+----------+
^
|
(B)
+----|-----+ Client Identifier +---------------+
| -+----(A)-- & Redirection URI ----> | |
| User- | | Authorization |
| Agent -+----(B)-- User authenticates ---> | Server |
| | | |
| -+----(C)-- Authorization Code ---< | |
+-|----|---+ +---------------+
| | ^ v
(A) (C) | |
| | | |
^ v | |
+---------+ | |
| |>---(D)-- Authorization Code ----------' |
| Client | & Redirection URI |
| | |
| |<---(E)----- Access Token --------------------'
+---------+ (w/ Optional Refresh Token)
(A)客户端通过向授权端点引导资源所有者的用户代理开始流程。客户端包括它的客户端标识、请求范围、本地状态和重定向 URI,一旦访问被许可(或拒绝)授权服务器将传送用户代理回到该URI。
(B)授权服务器验证资源拥有者的身份(通过用户代理),并确定资源所有者是否授予或拒绝客户端的访问请求。
(C)假设资源所有者许可访问,授权服务器使用之前(在请求时或客户端注册时)提供的重定向URI重定向用户代理回到客户端。重定向 URI 包括授权码和之前客户端提供的任何本地状态。
(D)客户端通过包含上一步中收到的授权码从授权服务器的令牌端点请求访问令牌。当发起请求时,客户端与授权服务器进行身份验证。客户端包含用于获得授权码的重定向 URI 来用于验证。
(E)授权服务器对客户端进行身份验证,验证授权代码,并确保接收的重定向 URI 与在步骤(C)中用于重定向客户端的 URI 相匹配。如果通过,授权服务器响应返回访问令牌与可选的刷新令牌(如果原来的访问令牌过期了,而授权服务器又允许发送新的令牌,就会携带过去)
其他的协议流程可以查看RFC文档
解释知乎的QQ授权
注意结合上面的协议流程看分析:
- 首先,用户点击QQ登录时,知乎服务器会帮我们重定向到QQ的第三方授权服务器上页面上(图3)请求授权(可以看到,浏览器的地址是qq的地址,也就是说不会泄露QQ的密码给知乎,需要注意的是,这个链接需要使用https协议,因为涉及用户登录的密码),同时,跟随一个重定向URL指向知乎服务器的某个地址,我们点击授权后(图3),授权成功,重定向到知乎指定的URL并返回授权码(可以在URL看到)
- 接着知乎服务器发送同一个重定向URL和授权码请求授权服务器的访问令牌,最后如果QQ服务器校验没有问题,返回访问令牌,最后知乎服务器调用QQ提供的API,附带访问令牌获取用户的信息
为什么要分成两点呢?因为第一点我们可以容易的抓取请求响应报文得到,而获取访问令牌的过程对资源所有者(我们用户)是透明的,也就是说我们不会看到访问令牌,只有知乎服务器得到了访问令牌;其实看不到也不应该让资源所有者看到,否则就不安全了,因为谁都可以用访问令牌到访问我们的信息
那到底整个过程的细节是怎样的呢?咱们开始分析请求响应包
请求响应包和参数分析
这里使用Fiddler抓包工具,去除了某些头部,注意结合上面讲的内容;为了方便看报文,对其进行了换行处理;
重点关注:请求地址,响应状态值,Location;请求和响应过程中的参数下一小节分析
第一步:(知乎主页点击QQ登录)
请求报文:直接请求到QQ服务器,知乎希望得到的信息放到URL中。 GET请求中的关键参数有scope, state, redirect_uri, client_id, response_type
GET https://graph.qq.com/oauth2.0/authorize? scope=get_user_info%2Cget_info%2Cadd_t%2Cadd_pic_t%2Cget_other_info%2Cget_fanslist%2Cget_idollist%2Cadd_idol%2Cadd_share &state=8821ffbf09c3ff9c401fe404aa7fcaf7 &redirect_uri=https%3A%2F%2Fwww.zhihu.com%2Foauth%2Fcallback%2Fqqconn &response_type=code &client_id=100490701 HTTP/1.1 Host: graph.qq.com Referer: https://www.zhihu.com/
响应报文:响应302重定向到Location所在URL,也就是图3的页面,第二步的授权请求就是这里的Location值,当点击授权后表示用户允许QQ将部分个人信息交给知乎,因此知乎将真正得到这些权限。
HTTP/1.1 302 Moved Temporarily Location: https://graph.qq.com/oauth/show?which=Login &display=pc &scope=get_user_info%2Cget_info%2Cadd_t%2Cadd_pic_t%2Cget_other_info%2Cget_fanslist%2Cget_idollist%2Cadd_idol%2Cadd_share &state=8821ffbf09c3ff9c401fe404aa7fcaf7 &redirect_uri=https%3A%2F%2Fwww.zhihu.com%2Foauth%2Fcallback%2Fqqconn &response_type=code &client_id=100490701
第二步:授权请求和授权响应(点击同意登录并授权)
授权请求
关键参数有scope, state, redirect_uri, client_id, response_type
请求报文
POST https://graph.qq.com/oauth2.0/authorize HTTP/1.1 Host: graph.qq.com Referer: https://graph.qq.com/oauth/show?which=Login &display=pc &scope=get_user_info%2Cget_info%2Cadd_t%2Cadd_pic_t%2Cget_other_info%2Cget_fanslist%2Cget_idollist%2Cadd_idol%2Cadd_share &state=8821ffbf09c3ff9c401fe404aa7fcaf7 &redirect_uri=https%3A%2F%2Fwww.zhihu.com%2Foauth%2Fcallback%2Fqqconn &response_type=code &client_id=100490701
- response_type:必选,固定是”code”
- client_id:必选,是授权服务器颁发给已注册客户端客户端标识,也就是说知乎开发人员到QQ处申请表示希望通过QQ授权接入,QQ同意后会给知乎(client)一个唯一的标识,这样以后授权登录时,QQ方(授权服务器)就知道哪个应用正在请求接入
- scope:可选,表示知乎希望从QQ上得到用户的什么权限。为什么第一步重定向要附带scope,因为为了防止我们点击授权时,有人恶意修改了URL,导致授权的内容变化了,有了第一步,QQ服务器能先知道需要什么权限(参数内容是get_user_info之类的内容,可以看到图2中打钩的选项其实就是根据scope得到的),保存在QQ服务器中,而如果点击授权后,传到服务器的scope内容变了,那么肯定是有人修改了scope,授权将失败,保证了安全性。
- redirect_uri:可选,知乎希望跳转的URL,指向自己网站的位置,同样的,需要和第一步中的redirct_url相同,理由与scope相同
- state:推荐的(应认为是必需的),客户端用于维护请求和回调之间的状态的不透明的值。当重定向用户代理回到客户端时,授权服务器包含此值。该参数应该用于防止如跨站点请求伪造CSRF
授权响应
响应报文:重定向到Location上(redirect_url),也就是回到知乎,关键参数是code, state
HTTP/1.1 302 Moved Temporarily Location: https://www.zhihu.com/oauth/callback/qqconn? code=D38858BD4FE6058A48E461663ECB3CC3 &state=8821ffbf09c3ff9c401fe404aa7fcaf7
- code:必需的。授权服务器生成的授权码。授权码必须在颁发后很快过期以减小泄露风险。推荐的最长的授权码生命周期是 10 分钟。客户端不能使用授权码超过一次。如果一个授权码被使用一次以上,授权服务器必须拒绝该请求并应该撤销(如可能)先前发出的基于该授权码的所有令牌。授权码与客户端标识和重定向 URI 绑定。
- state:必需的,若“state”参数在客户端授权请求中提交。则返回同一个值。
第三步:访问令牌请求和响应(知乎程序内部请求获取令牌)
请求获取token,最后访问用户的头像,昵称等信息
这一步对我们是透明的,我们看不到,其实就是简单的调用API调用获取,关键参数有grant_type, client_id, client_secret, code, redirect_uri
访问令牌请求
下一小节有QQ API
- grant_type:必需的。值必须被设置为“authorization_code”。
- code:从授权服务器收到的授权码。
- redirect_uri:必需的,必须和第一步请求时的redirect_uri相同。
- client_id:必需的
授权服务器必须:
- 要求机密客户端或任何被颁发了客户端凭据(或有其他身份验证要求)的客户端进行客户端身份验证,
- 若包括了客户端身份验证,验证客户端身份,
- 确保授权码颁发给了通过身份验证的机密客户端,或者如果客户端是公开的,确保代码颁发给了请求 中的“client_id”,
- 验证授权码是有效的,并
- 确保给出了“redirect_uri”参数,若“redirect_uri”参数如 4.1.1 所述包含在初始授权请求中,且若包含,确保它们的值是相同的。
访问令牌响应
响应:关键参数有access_token, refresh_token
如果访问令牌请求是有效的且被授权,授权服务器颁发访问令牌以及可选的刷新令牌。如果请求客户端身份验证失败或无效,授权服务器返回错误响应。
QQ API加深理解
QQ接入API
state 的理解
攻击方式
假设 Alice 访问 知乎网站(Client)请求 QQ 登录授权(图1), 知乎 请求 QQ(授权服务器) 授权以获取 Alice 的信息,然后被重定向到 QQ服务器上(图2),此时知乎绑定了当前是Alice用户要进行QQ登录授权(假设用cookie绑定,是可以伪造的),接着 Alice 需要输入用户名和密码认证第三方;
但是 Alice 不这样做,没有输入用户名和密码,而是保存了这个 URL,而是让 Bob 以某种方式去访问这个URL,此时如果 Bob 用他的用户名密码登录到QQ授权服务器认证,此时,QQ 用他的身份产生了一个 授权码,重定向到知乎,此时相当于重复了一次请求到知乎,如果将cookie改为Alice自己的发回给知乎,那么 Alice 在客户端的账号就被认证成功,而进行认证的身份却是 Bob,能够获取Bob的信息;
如何防止
客户端应当基于当前需要认证的用户以某种方式产生一个值,保证其唯一性,保存到服务端中,这个唯一值由客户端发送给授权服务器,输入用户名密码授权后,授权服务器将这个值原封不动的进行返回给客户端,客户端比较授权服务器返回的值和自己保存的值,如果和原来发送给授权服务器的不一致,就会拒绝,不再获取 access_token
现在比如 Alice 保存了这个 URL,让 Bob 去登录,此时客户端会产生两个state, 分别是stateA,stateB,知乎用 Alice 的身份stateA发送给QQ,而Bob登录认证后,同样是stateA被返回到 知乎,知乎比较Bob本身的stateB和返回的state是否相同,此时 知乎 比较将会失败,拒绝下一步操作
state的唯一性防止了伪造攻击
转载请附上原地址:http://blog.bensonlin.me/blogs/oauth2