1. 引言
javascript
若是你开车去酒店赴宴,你常常会苦于找不到停车位而耽误不少时间。是否有好办法能够避免这个问题呢?有的,据说有一些豪车的车主就不担忧这个问题。豪车通常配备两种钥匙:主钥匙和泊车钥匙。当你到酒店后,只须要将泊车钥匙交给服务生,停车的事情就由服务生去处理。与主钥匙相比,这种泊车钥匙的使用功能是受限制的:它只能启动发动机并让车行驶一段有限的距离,能够锁车,但没法打开后备箱,没法使用车内其余设备。这里就体现了一种简单的“开放受权”思想:经过一把泊车钥匙,车主便能将汽车的部分使用功能(如启动发动机、行驶一段有限的距离)受权给服务生。java
受权是一个古老的概念,它是一个多用户系统必须支持的功能特性。好比,Alice和Bob都是Google的用户,那么Alice应该能够将本身的照片受权给Bob访问。但请注意到,这种受权是一种封闭受权,它只支持系统内部用户之间的相互受权,而不能支持与其余外部系统或用户之间的受权。好比说,Alice想使用“网易印像服务”将她的部分照片冲印出来,她怎么能作到呢?算法
确定有人会说,Alice能够将本身的Google用户名和密码告诉网易印像服务,事情不就解决了吗?是的,但只有绝不关注安全和隐私的同窗才会出此“绝招”。那么咱们就来想想,这一“绝招”存在哪些问题?(1) 网易印像服务可能会缓存Alice的用户名和密码,并且可能没有加密保护。它一旦遭到攻击,Alice就会躺着中枪。(2) 网易印像服务能够访问Alice在Google上的全部资源,Alice没法对他们进行最小的权限控制,好比只容许访问某一张照片,1小时内访问有效。(3) Alice没法撤消她的单个受权,除非Alice更新密码。
浏览器
在以Web服务为核心的云计算时代,像用户Alice的这种受权需求变得日益迫切与兴盛,“开放受权(Open Authorization)”也正所以而生,意在帮助Alice将她的资源受权给第三方应用,支持细粒度的权限控制,而且不会泄漏Alice的密码或其它认证凭据。
缓存
根据应用场景的不一样,目前实现开放受权的方法分为两种:一种是使用OAuth协议[1];另外一种是使用IAM服务[2]。OAuth协议主要适用于针对我的用户对资源的开放受权,好比Google的用户Alice。OAuth的特色是“现场受权”或“在线受权”:客户端主要经过浏览器去访问资源,受权时须要认证Alice的资源全部者身份,而且须要Alice现场审批。OAuth通常在SNS服务中普遍使用,如微博。IAM服务则不一样,它的特色是“预先受权”或“离线受权”:客户端主要经过REST API方式去访问资源,资源全部者能够预先知道第三方应用所须要的资源请求,一次受权以后,不多会变动。IAM服务通常在云计算服务中使用,如AWS服务、阿里云计算服务。
安全
本文主要介绍OAuth开放受权。关于以IAM服务提供的开放受权,我将在另外一篇博文中介绍。下面我来介绍OAuth 2.0协议、协议的实例化描述、安全性分析。
服务器
2. OAuth 2.0 协议
OAuth 2.0 是目前比较流行的作法,它率先被Google, Yahoo, Microsoft, Facebook等使用。之因此标注为 2.0,是由于最初有一个1.0协议,但这个1.0协议被弄得太复杂,易用性差,因此没有获得普及。2.0是一个新的设计,协议简单清晰,但它并不兼容1.0,能够说与1.0没什么关系。因此,我就只介绍2.0。
2.1 协议的参与者
网络
从引言部分的描述咱们能够看出,OAuth的参与实体至少有以下三个:
数据结构
· RO (resource owner): 资源全部者,对资源具备受权能力的人。如上文中的用户Alice。
app
· RS (resource server): 资源服务器,它存储资源,并处理对资源的访问请求。如Google资源服务器,它所保管的资源就是用户Alice的照片。
· Client: 第三方应用,它得到RO的受权后即可以去访问RO的资源。如网易印像服务。
此外,为了支持开放受权功能以及更好地描述开放受权协议,OAuth引入了第四个参与实体:
· AS (authorization server): 受权服务器,它认证RO的身份,为RO提供受权审批流程,并最终颁发受权令牌(Access Token)。读者请注意,为了便于协议的描述,这里只是在逻辑上把AS与RS区分开来;在物理上,AS与RS的功能能够由同一个服务器来提供服务。
2.2 受权类型
在开放受权中,第三方应用(Client)多是一个Web站点,也多是在浏览器中运行的一段JavaScript代码,还多是安装在本地的一个应用程序。这些第三方应用都有各自的安全特性。对于Web站点来讲,它与RO浏览器是分离的,它能够本身保存协议中的敏感数据,这些密钥能够不暴露给RO;对于javascript代码和本地安全的应用程序来讲,它原本就运行在RO的浏览器中,RO是能够访问到Client在协议中的敏感数据。
OAuth为了支持这些不一样类型的第三方应用,提出了多种受权类型,如受权码 (Authorization Code Grant)、隐式受权 (Implicit Grant)、RO凭证受权 (Resource Owner Password Credentials Grant)、Client凭证受权 (Client Credentials Grant)。因为本文旨在帮助用户理解OAuth协议,因此我将先介绍这些受权类型的基本思路,而后选择其中最核心、最难理解、也是最普遍使用的一种受权类型——“受权码”,进行深刻的介绍。
2.3 OAuth协议 - 基本思路
[Figure 1: Abstract Protocol Flow]
如图1所示,协议的基本流程以下:
(1) Client请求RO的受权,请求中通常包含:要访问的资源路径,操做类型,Client的身份等信息。
(2) RO批准受权,并将“受权证据”发送给Client。至于RO如何批准,这个是协议以外的事情。典型的作法是,AS提供受权审批界面,让RO显式批准。这个能够参考下一节实例化分析中的描述。
(3) Client向AS请求“访问令牌(Access Token)”。此时,Client需向AS提供RO的“受权证据”,以及Client本身身份的凭证。
(4) AS验证经过后,向Client返回“访问令牌”。访问令牌也有多种类型,若为bearer类型,那么谁持有访问令牌,谁就能访问资源。
(5) Client携带“访问令牌”访问RS上的资源。在令牌的有效期内,Client能够屡次携带令牌去访问资源。
(6) RS验证令牌的有效性,好比是否伪造、是否越权、是否过时,验证经过后,才能提供服务。
2.4 受权码类型的开放受权
[Figure 2: Authorization Code Flow]
如图2所示,受权码类型的开放受权协议流程描述以下:
(1) Client初始化协议的执行流程。首先经过HTTP 302来重定向RO用户代理到AS。Client在redirect_uri中应包含以下参数:client_id, scope (描述被访问的资源), redirect_uri (即Client的URI), state (用于抵制CSRF攻击). 此外,请求中还能够包含access_type和approval_prompt参数。当approval_prompt=force时,AS将提供交互页面,要求RO必须显式地批准(或拒绝)Client的这次请求。若是没有approval_prompt参数,则默认为RO批准这次请求。当access_type=offline时,AS将在颁发access_token时,同时还会颁发一个refresh_token。由于access_token的有效期较短(如3600秒),为了优化协议执行流程,offline方式将容许Client直接持refresh_token来换取一个新的access_token。
(2) AS认证RO身份,并提供页面供RO决定是否批准或拒绝Client的这次请求(当approval_prompt=force时)。
(3) 若请求被批准,AS使用步骤(1)中Client提供的redirect_uri重定向RO用户代理到Client。redirect_uri须包含authorization_code,以及步骤1中Client提供的state。若请求被拒绝,AS将经过redirect_uri返回相应的错误信息。
(4) Client拿authorization_code去访问AS以交换所需的access_token。Client请求信息中应包含用于认证Client身份所需的认证数据,以及上一步请求authorization_code时所用的redirect_uri。
(5) AS在收到authorization_code时须要验证Client的身份,并验证收到的redirect_uri与第3步请求authorization_code时所使用的redirect_uri相匹配。若是验证经过,AS将返回access_token,以及refresh_token(若access_type=offline)。
若是读者对这个流程的细节不甚清楚,那么能够先看第3节的一个实例化描述,而后再回来看这部份内容。
3. OAuth协议实例化描述
下面我以实例化方式来帮助读者理解受权码类型的受权协议的运行过程。假设:
(1) Alice有一个有效的Google账号;
(2) Facebook.com已经在Google Authorization Server上注册了Client身份,已经得到(client_id, client_secret),注意client_secret是Client与AS之间的一个共享密钥。
(3) Alice想受权Facebook.com查看她的联系人列表(https://www.google.com/m8/feeds)。
图3展现了Alice、Facebook.com、Google资源服务器、以及Google OAuth受权服务器之间的协议运行过程。
[Figure 3: An Instance of Authorization Code Flow]
//若字体没法看清,请单击右键->选择查看原图
协议所涉及到的细节都已经在图3上了,因此不打算再作详细介绍了。若看懂了此图,OAuth2.0就理解了。
读者请注意,在步骤(4)中,Client须要拿“受权码”去换“受权令牌”时,Client须要向AS证实本身的身份,即证实本身就是步骤(2)中Alice批准受权时的Grantee。这个身份证实的方法主要有两种(图3中使用了第1种):
(1) 经过https直接将client_secret发送给AS,由于client_secret是由Client与AS所共享,因此只要传送client_secret的信道安全便可。
(2) 经过消息认证码来认证Client身份,典型的算法有HMAC-SHA1。在这种方式下,Client无需传送client_secret,只需发送消息请求的signature便可。因为不须要向AS传递敏感数据,因此它只须要使用http便可。
此外, 在步骤(2)中,Google受权服务器须要认证Alice的RO身份,并提供受权界面给Alice进行受权审批。今天Google提供的实例如图四、图5所示,仅供读者理解OAuth这种“现场受权”或"在线受权"的含义。
[Figure 4: RO's Identity Authentication]
[Figure 5: RO's Authorization Decision]
4. OAuth设计上的安全性考虑
4.1 为什么引入authorization_code?
协议设计中,为何要使用authorization_code来交换access_token?这是读者容易想到的一个问题。也就是说,在协议的第3步,为何不直接将access_token经过重定向方式返回给Client呢?好比:
HTTP/1.1 302
Location:
https://www.facebook.com/?access_token=ya29.AHES6ZSXVKYTW2VAGZtnMjD&token_type=Bearer&expires_in=3600
若是直接返回access_token,协议将变得更加简洁,并且少一次Client与AS之间的交互,性能也更优。那为什么不这么设计呢?协议文档[1]中并无给出这样设计的理由,但也不难分析:
(1) 浏览器的redirect_uri是一个不安全信道,此方式不适合于传递敏感数据(如access_token)。由于uri可能经过HTTP referrer被传递给其它恶意站点,也可能存在于浏览器cacher或log文件中,这就给攻击者盗取access_token带来了不少机会。另外,此协议也不该该假设RO用户代理的行为是可信赖的,由于RO的浏览器可能早已被攻击者植入了跨站脚本用来监听access_token。所以,access_token经过RO的用户代理传递给Client,会显著扩大access_token被泄露的风险。 但authorization_code能够经过redirect_uri方式来传递,是由于authorization_code并不像access_token同样敏感。即便authorization_code被泄露,攻击者也没法直接拿到access_token,由于拿authorization_code去交换access_token是须要验证Client的真实身份。也就是说,除了Client以外,其余人拿authorization_code是没有用的。 此外,access_token应该只颁发给Client使用,其余任何主体(包括RO)都不该该获取access_token。协议的设计应能保证Client是惟一有能力获取access_token的主体。引入authorization_code以后,即可以保证Client是access_token的惟一持有人。固然,Client也是惟一的有义务须要保护access_token不被泄露。
(2) 引入authorization_code还会带来以下的好处。因为协议须要验证Client的身份,若是不引入authorization_code,这个Client的身份认证只能经过第1步的redirect_uri来传递。一样因为redirect_uri是一个不安全信道,这就额外要求Client必须使用数字签名技术来进行身份认证,而不能用简单的密码或口令认证方式。引入authorization_code以后,AS能够直接对Client进行身份认证(见步骤4和5),并且能够支持任意的Client认证方式(好比,简单地直接将Client端密钥发送给AS)。
在咱们理解了上述安全性考虑以后,读者也许会有豁然开朗的感受,懂得了引入authorization_code的妙处。那么,是否是必定要引入authorization_code才能解决这些安全问题呢?固然不是。笔者将会在另外一篇博文给出一个直接返回access_token的扩展受权类型解决方案,它在知足相同安全性的条件下,使协议更简洁,交互次数更少。
4.2 基于Web安全的考虑
OAuth协议设计不一样于简单的网络安全协议的设计,由于OAuth须要考虑各类Web攻击,好比CSRF (Cross-Site Request Forgery), XSS (Cross Site Script), Clickjacking。要理解这些攻击原理,读者须要对浏览器安全(eg, Same Origin Policy, 同源策略)有基本理解。好比,在redirect_uri中引入state参数就是从浏览器安全角度考虑的,有了它就能够抵制CSRF攻击。若是没有这个参数,攻击者即可以在redirect_uri中注入攻击者提供的authorization_code或access_token,结果可能致使Client访问错误的资源(好比,将款项汇到一个错误的账号)。
基于Web安全的考虑,OAuth协议文档中已经有了比较全面的阐述,因此我不打算在此文中进行展开,有兴趣的读者请参考[1]。
5. 结语
本文对OAuth 2.0 开放受权协议及其设计上的安全性考虑作了一个基本的介绍,但愿能给参与安全协议设计和开发的同窗起到一点帮助。
参考文献:
[1] Hammer-Lahav, E., Recordon, D., and D. Hardt, "The OAuth 2.0 Authorization Framework", draft-ietf-oauth-v2-31 (work in progress), June 2012.
[2] http://aws.amazon.com/iam/