什么是三方授权?
第三方授权就是,委托第三方来对既定的用户进行鉴定,鉴定成功之后,下发信任凭证,信任凭证和用户挂钩,同时可以使用此凭证来去第三方平台,获得该用户开放的部分信息.
直白的说,就是将用户授权的工作交给第三方来做,而自己只维护信任凭证,并且获取用户信息。就像我们的QQ和微博一样,你在逛各大论坛的时候,总会有一种途径就是第三方的App登陆。显然这种验证是要在第三方做的,而论坛只需要维护一个token,并且可以获取你的个人信息,然而没什么软用。你如果想用部分功能,还是得注册,因为你相当于论坛只是一个有部分信息的游客,而没有成为它的用户,这种手段可以看成是提高自己访问量或转化率的一种手段吧。其实这种授权方案不一样只用在互联网,部分企业级的网站做资源的共享访问也是这么做的。比如两个网站合作,A站点账户可以登陆B站点,B站点账户可以登陆A站点,这样A站点就能拿到B站点相关资源,反过来也是。就做到了资源共享。
那么这种授权是怎么做的呢?到底安全不安全?那么可能就要引出OAuth协议了。
OAuth是怎么做的#####
首先OAuth只是一个授权协议,不是一个实现或是一个中间件。
OAuth1于2007年10月建立,之后又在2009年6月重新修订和发布 http://oauth.net/core/1.0a/
OAuth1.0授权流程是什么?
流程很复杂,因为OAuth需要保证授权码和Token在传输的时候不被截取和篡改,所以使用了很多签名反复的验证。
OAuth1.0的问题:
反复的签名,开放平台本来就面对开发者,但是反复的签名导致开发的流程太过于复杂。
没有引入回跳地址的判断,导致会跳可能会篡改,这样授权码和Token会被Hack掉(1.0a的时候修复了这个漏洞)。
授权流程过于单一化。
OAuth2.0的引入:
抽象验证流程
由于OAuth1过于复杂,之后对OAuth进行了改造引入了Oauth2.0。OAuth的授权过程如下:
(A) 客户端从资源拥有者那里请求授权。授权请求能够直接发送给资源拥有者,或者间接地通过授权服务器这样的中介,而后者更为可取。
(B) 客户端收到一个访问许可,它代表由资源服务器提供的授权。
(C) 客户端使用它自己的私有证书到授权服务器上验证,并出示访问许可,来请求一个访问令牌。
(D) 授权服务器验证客户端私有证书和访问许可的有效性,如果验证通过则分发一个访问令牌。
(E) 客户端通过出示访问令牌向资源服务器请求受保护资源。
(F) 资源服务器验证访问令牌的有效性,如果验证通过则响应这个资源请求。
上面只是一个抽象的验证流程,根据上面的抽象流程演化出四种验证模式:
授权码模式
授权码模式和上述的 抽象验证模式流程比较相似:
(A) web客户端通过将终端用户的user-agent重定向到授权服务器来发起这个流程。客户端传入它的客户端标识符、请求作用域、本地状态和一个重定向URI,在访问被许可(或被拒绝)后授权服务器会重新将终端用户引导回这个URI。
(B) 授权服务器验证终端用户(借助于user-agent),并确定终端用户是否许可客户端的访问请求。
(C) 假定终端用户许可了这次访问,授权服务器会将user-agent重定向到之前提供的重定向URI上去。授权服务器为客户端传回一个授权码做获取访问令牌之用。
(D) 客户端通过验证并传入上一步取得的授权码从授权服务器请求一个访问令牌。(需要带上ClientId和Secret,ClientId和Secret是通过平台授予)
(E) 授权服务器验证客户端私有证书和授权码的有效性并返回访问令牌。
授权码模式(implicit grant type)
User-Agent子态适用于客户端不能保存客户端私有证书的App(纯客户端App,无Server参与)。因为可纯客户端的程序不能保存密钥
(A) 客户端将user-agent引导到终端用户授权endpoint。客户端传入它的客户端标识符、请求作用域、本地状态和一个重定向URI,在访问被许可(或被拒绝)后授权服务器会重新将终端用户引导回这个URI。
(B) 授权服务器验证终端用户(通过user-agent)并确认终端用户是许可还是拒绝了客户端的访问请求。
(C) 如果终端用户许可了这次访问,那么授权服务器会将user-agent引导到之前提供的重定向URI。重定向URI会在URI片断{译者注:URI片断是指URI中#号之后的内容}中包含访问令牌。
(D) user-agent响应重定向指令,向web服务器发送不包含URI片断的请求。user-agent在本地保存URI片断。
(E) web服务器返回一个web页面(通常是嵌入了脚本的HTML网页),这个页面能够访问完整的重定向URI,它包含了由user-agent保存的URI片断,同时这个页面能够将包含在URI片断中的访问令牌(和其它参数)提取出来。
(F) user-agent在本地执行由web服务器提供的脚本,该脚本提取出访问令牌并将它传递给客户端。
密码模式
密码模式就是将密码托管给第三方App,但是必须要保证第三方App高度可信。
(A)用户向客户端提供用户名和密码。
(B)客户端将用户名和密码发给认证服务器,向后者请求令牌。
(C)认证服务器确认无误后,向客户端提供访问令牌。
客户端模式
这种模式不需要终端用户的参与,只是Client和Server端的交互。通常只用于Client状态的获取。
(A)客户端向认证服务器进行身份认证,并要求一个访问令牌。
(B)认证服务器确认无误后,向客户端提供访问令牌。
授权流程
这里以授权码方式流程说明,主要流程分为两步,获取授权码和通过授权码获取资源票据:
(A)用户访问客户端,后者将前者导向认证服务器。
(B)用户选择是否给予客户端授权。
(C)假设用户给予授权,认证服务器将用户导向客户端事先指定的"重定向URI"(redirection URI),同时附上一个授权码。
(D)客户端收到授权码,附上早先的"重定向URI",向认证服务器申请令牌。这一步是在客户端的后台的服务器上完成的,对用户不可见。
(E)认证服务器核对了授权码和重定向URI,确认无误后,向客户端发送访问令牌(access token)和更新令牌(refresh token)。
A步骤中,客户端申请认证的URI,包含以下参数:
response_type:表示授权类型,必选项,此处的值固定为"code"
client_id:表示客户端的ID,必选项
redirect_uri:表示重定向URI,可选项
scope:表示申请的权限范围,可选项
state:表示客户端的当前状态,可以指定任意值,认证服务器会原封不动地返回这个值。
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
C步骤中,服务器回应客户端的URI,包含以下参数:
code:表示授权码,必选项。该码的有效期应该很短,通常设为10分钟,客户端只能使用该码一次,否则会被授权服务器拒绝。该码与客户端ID和重定向URI,是一一对应关系。
state:如果客户端的请求中包含这个参数,认证服务器的回应也必须一模一样包含这个参数。
HTTP/1.1 302 Found
Location: https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA
&state=xyz
D步骤中,客户端向认证服务器申请令牌的HTTP请求,包含以下参数:
grant_type:表示使用的授权模式,必选项,此处的值固定为"authorization_code"。
code:表示上一步获得的授权码,必选项。
redirect_uri:表示重定向URI,必选项,且必须与A步骤中的该参数值保持一致。
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
E步骤中,认证服务器发送的HTTP回复,包含以下参数:
access_token:表示访问令牌,必选项。
token_type:表示令牌类型,该值大小写不敏感,必选项,可以是bearer类型或mac类型。
expires_in:表示过期时间,单位为秒。如果省略该参数,必须其他方式设置过期时间。
refresh_token:表示更新令牌,用来获取下一次的访问令牌,可选项。
scope:表示权限范围,如果与客户端申请的范围一致,此项可省略。
HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
Cache-Control: no-store
Pragma: no-cache
{
"access_token":"2YotnFZFEjr1zCsicMWpAA",
"token_type":"example",
"expires_in":3600,
"refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA",
"example_parameter":"example_value"
}
资源访问过程:
http://localhost:8082/oauth/server/resource/uname.do?access_token=233730a3d4cdc1af64f78fa80bc13c6e
此处主要由资源服务方实现,对于access_token进行校验。
token过期刷新过程:
客户端使用“refresh_token”访问许可类型和下列参数传入刷新令牌:
refresh_token:
必需参数。与待刷新的访问令牌相关联的刷新令牌。
grant_type=refresh_token&client_id=s6BhdRkqt3&client_secret=8eSEIpnqmM&refresh_token=n4E9O119d
授权服务器必须验证客户端私有证书(如果存在),验证刷新令牌是否有效,以及验证资源拥有者的授权是否仍然有效。如果请求有效,授权服务器则发布一个访问令牌响应,原来的Token失效。以后访问必须使用新Token。
一切只是看上去很美好,但其实很多坑####
OAuth2看上去很美好,但是细心观察其实还是有一些漏洞的。
对于授权码和access_token的篡改,在OAuth1中是反复的对Code和Token进行签名,来保证Token不会被篡改,但是OAuth2中却没有,因为OAuth2是基于Https的,所以如果没有Https的支持OAuth2可能还不如OAuth1.
对于redirect_uri的校验,OAuth1中没有提到redirect_uri的校验,那么OAuth2中要求进行redirect_uri的校验。但是如果校验规则过松,也会导致跳转的安全问题。 例如:校验的时候只校验根域名,或者二级域名,但是第三方App对自己的域名保护的不好,导致二级域名被hack那么此时授权码和Token会被窃取。 校验规则不严谨,例如www.baidu.com 但是redirect_uri为:www.a.com.\www.baidu.com,这样授权就被a.com钓走了。
对于CSRF攻击(跨站请求伪造):由于这个授权过程服务器和Client和用户之间有几次交互,但是在得到授权码的时候需要一次回跳,但是这次回跳是可以被阻塞的。
攻击者使用自己的账户申请第三方授权登陆
授权后服务端返回授权码,但是此时组织授权回跳,此时Client并没有接到授权码,也就是阻断了授权流程
攻击者将此跳转链接发给一个正在处于在Client登陆状态的账户
诱骗正常用户点击,那么此时攻击者第三方账户和被攻击账户进行绑定(相当于账户绑定了第三方的账户)
攻击者再次进行第三方授权登陆。这样就劫持了诱骗的账户。转账?删好友?等等就所以搞了。
解决办法:
在进行授权码申请或者是Token申请的时候带上state参数,服务器返回请求时要求携带state参数,在Client处理授权的时候校验此参数。参数可以是当前账户的SessionId,或Cookie的签名串。在Client申请accessToken会验证相关state和当前用户的关系,这样就防止了篡改。OAuth的校验流程为什么这么复杂,直接授权之后redirect回accessToken不就结了吗?为什么还要返回auth_code之后请求accessToken?
首先,redirect是不安全的,随时可以暂停回调而拿到accessToken,拿到了accessToken也就意味着拿到了授权,但是auth_code是和client相对应的,那么即使拿到了auth_code还需要再次申请accessToken,申请accessToken时需要校验Client和state。同时协议设计的原则就是只有Client能拿到accessToken而用户是拿不到的。
最后,切记HTTPS####
参考资料:
http://blog.csdn.net/seccloud/article/details/8192707
http://www.justwinit.cn/post/8138/
https://blog.yorkxin.org/2013/09/30/oauth2-1-introduction
人人网OAuth历程:http://www.infoq.com/cn/presentations/dx-renren-authentication-authorization/