最近在做第三方接入的,初步定下使用OAuth2协议,花了些时间对OAuth2的授权方式做了些了解。
我还记得一两年前,跟一位同事聊起互联网时,当时我说过一个想法:
目前不少较为稀有的资源,很多都是论坛提供下载的,论坛提供的下载往往要求一个论坛帐号,更有甚者,需回帖才可见,又或者下载需要消耗一定的虚拟货币,而这些货币可以用论坛活跃度而获得。假设现在我是一个普通用户,我要找某个资源。通过搜索引擎或者资料,我发现在某个论坛有这个资源下载,从其他地方获得这个资源代价比较高或者说根本就找不着。当我准备下载时,很可能就被提示需登录后才可下载,随机被跳转到注册页面。
为了这个资源注册一个帐号?我想,无论谁在99%的情况下都不乐意去注册一个只是用一次的帐号,偏偏有些论坛就是为了某些原因要求你必须提供一个帐号。好吧,像我这样的人,当然是瞎填点信息注册个帐号了事。至于注册了帐号需不需要金币或者多少声望才能回帖下载之类的,这里就不唠叨了。这个过程的关键点是:我为了一个临时性的需要,注册了一个永久性无关痛痒的帐号,这个帐号使用一次之后,基本上失去价值了。有无数无聊的用户花了N多的时间在M多的论坛里注册了N*M个无用帐号,这个过程除了对某些统计指标有利以外,对用户没有任何价值。
可不可以做一个平台,使任意用户可以在任意论坛注册一个帐号,随后这个帐号和密码自动登记到这个平台中作为公共帐号,之后,其他用户再访问这个论坛时,就无需再次注册帐号了,直接在这个平台上,自动地使用公共帐号去做该做的事。这样,随着用户数的增加,最终可以达到一个比较理想的情况:大部分论坛的临时性操作,用户都不用再去注册了,也不用担心自己的常用帐号密码等信息泄漏的问题。尽管对于一些有“经济系统”的论坛(需要通过活跃度/发帖数/现金等有偿获得虚拟货币,存在消费行为),这个平台可能不适合,但即使需求只被解决了一半,也是个有价值的产品。
当时只是大概聊了下,完全没有动手的打算,至今我还没发现类似的产品,不知是这个需求不够大众还是什么。那时我也大概看了下OpenID,跟我的设想不一样,OpenID是将一个用户在某个平台上的帐号,公开给其他网站使用,当然公开的只是帐号而不会包含密码。当时宣传的口号大概是这样的:“一次登录,到处使用”。当时我只在豌豆网注册了一个OpenID试着玩玩,感觉支持这个OpenID的资源网站太少了,那个帐号作用不大。
OAuth最近几年大行其道,很大程度得益于微博的推广。OAuth和OpenID是比较容易混淆的两个东西,比较“官方”的观点认为:OpenID设计目的是“身份校验”;OAuth的设计目的是“授权”。我也比较认同这个观点,但我觉得这种说法本身也挺容易混淆,有位同事说“身份校验”本身就是对“用户资源”权限的授予,所以OAuth包含了OpenID的作用。
在说明我的观点之前,不妨思考下,目前提供OAuth的网站有那些,他们的提供的服务是什么,为什么他们大多都提供OAuth却鲜有提及OpenID?(我不是暗指腾讯啦)。
OAuth与OpenID
先看看OpenID,前面多少也讲过了,下面以豌豆网为例:
服务提供方:豌豆网
提供的服务:用户身份识别,同一个用户有同一个OpenID描述,帐号密码验证功能由豌豆网提供
服务消费方:第三方
消费的目的:让豌豆网的用户来操作自己网站所拥有的资源
再来对比OAuth,用新浪微博为例吧:
服务提供方:新浪微博
提供的服务:读取/发送/查询微博,好友关系处理等,帐号密码资源都由新浪微博提供
服务消费方:第三方
消费的目的:为终端用户操作该用户在新浪微博的资源提供可能
一对比,区别就很明显了:OAuth和OpenID的区别主要是服务提供方是否提供有价值的资源。
作为一个拥有资源的服务提供方,当然希望自己管理自己的用户信息。假如新浪微博支持其他网站的OpenID登录,由于有不少的OpenID服务提供方,那么它需要如何管理自己的用户呢?例如,用户A通过网站X的OpenID登录新浪微博,跟用户A通过网站Y的OpenID登录新浪微博,最终的效果是一个帐号还是两个帐号呢?如果用户A在新浪微博本身有一个帐号的话,情况又更复杂了。要么所有帐号都按新帐号处理,要么提供多个帐号关联功能。前一种方案简单易行,但产生了大量非活跃帐号,用户体验也不见得好。后一种方案,想一想都觉得,维护是个灾难。于是,大多数的资源提供方都倾向与自己管理自己的用户信息,对于第三方的接入,开放一些授权给他们参与一些用户资源的访问就是了,于是便提供OAuth服务而不是提供OpenID接入,一些网站如腾讯还在 OAuth上提供了OpenID。写着写着,我自己都觉得OpenID的“接入”和”服务”很拗口。好吧,OpenID的接入是说使用其他网站所验证的帐号信息,OpenID的服务是指对外提供OpenID的身份校验服务。
把上面的情况循环循环再循环,最终,一方面,拥有有价值资源的网站,都做OAuth去了,他们在等待开发者和其他第三方网站的接入,壮大他们的平台;另一方面,提供OpenID服务的小网站,几乎没有大网站的接入支持,对用户的吸引力越来越小,典型的恶性循环。然后,大部分网站的OAuth服务虽然基本是按照官网规范做接口,但不少细节都做了个性化。例如部分网站的expires_in单位是秒,部分是用分做单位的。部分网站支持state作为状态传递,部分又不支持。最终这些非标准的东西,会惹恼苦逼的开发者(为什么我会很自然的想起IE?),相信很多开发者都会根据市场份额去选择几个流行的OAuth提供方进行兼容,其他的,见乔布斯去吧。而用户则会根据应用的数量去选择平台,又一个恶性循环。如果你的资源不够吸引开发者,就不会有人愿意为你的自定义标准买单。莫非这就是传说中的,合久必分分久必合?嗯,扯远了。
我并没有贬OpenID褒OAuth的意思,只是觉得在目前市场下,不太可能有大网站愿意放弃提供OAuth服务而使用OpenID接入外部帐号。其实我对OpenID了解不多,写着写着,没想到竟然写了一大坨,我真怀疑自己是不是话痨…… 有点晚,明晚继续,if有空的话。
OAuth授权流程
OAuth2是从OAuth发展而来的,虽然不向下兼容,但了解OAuth能更好的理解OAuth2的一些改变。
OAuth里存在三个主要角色:用户、服务提供方和服务消费方。不少文档会把服务消费方说成是客户端,对于SP来说,这个说法没什么问题,但我感觉这个说放容易引起混淆,所以我这里还是用服务消费方来描述。按流行的口号,服务提供方一般对外宣称自己是某某某开放平台,而服务消费方则是各种第三方应用。用户在平台上有一些已有资源,如好友关系,照片等。
几乎所有的OAuth平台都有类似的背景:他们原先积累了一大堆的真实用户,在互联网开放的趋势下,主动或被动的需要支持第三方应用的接入。第三方应用为了使其功能更加丰富完整,希望从平台能获取甚至操作当前用户的资源。用户很可能不希望第三方得知他原有的帐号和密码,原因很明显,安全考虑嘛。服务提供方也不希望第三方直接使用用户的帐号和密码登录平台操作用户数据,为啥?不便于数据统计和维护嘛,希望对 哪个第三方操作哪个用户数据 和 哪个用户操作自己的数据 两种处理流程有所区别。第三方很无辜,经常大喊“我觉不会使用任何途径存储用户的帐号!”。即使真有人相信这些誓言,但也很难确保第三方使用帐号敏感数据时,不被第四方所捕获,所以,认真你就输了。
为了解决上面的问题,准确的说是让三种角色互相信任,OAuth由此而生。在没有第三方的情况下,服务提供方和用户可以认为是互相信任的,因为用户用域名来确保自己访问的是一个受信的站点;服务提供方则要求用户登录,并且登录会话可以控制。
应为第三方一般是不知名的,用户很难区分第三方合不合法,所以用户需要通过服务提供方来证实第三方,例如位于服务提供方的OAuth授权页面会简单的介绍该应用的简单介绍,正是这些介绍使得用户可以相信,该应用是一个合法登记的第三方。
为了让服务提供方信任第三方应用,第三方应用在必要时需要向服务提供方提供身份凭据。最简单的办法就是第三方开发者去服务提供方那去注册个帐号,然后在需要时用这个帐号来证明自己的身份。这种第三方应用的帐号,下面统称应用帐号。由于第三方的请求不会有人工的干预,所以应用帐号的帐号密码一般由服务提供商提供,方便服务提供方管理,安全系数也较高,因为服务提供方可以制定规则,使密码更难以伪造或猜测。
按理说,第三方应用除了到SP处申请一个应用帐号外,也有其他办法证实自己的身份。
例如可以使用HTTPS连接,让“第四方”去证明。OAuth2使用的就是HTTPS连接,但也仅仅是服务端认证,客户端并不做保证。估计一个方面的原因是,应用的数量很多,一般都是中小规模开发商开发的,客户端也要认证的话,证书申请门槛较高,一个账号密码可以解决的问题有必要去申请证书吗?另一方面是,很多应用是没有服务端的,使用双向HTTPS认证无疑将这些应用拒之门外。
上面的方法是,用户通过服务提供方,去识别第三方是否合法。还有种方式是:服务提供方通过用户,去识别第三方是否合法。但OAuth里没有这种方式的体现,但OAuth2里有类似的方式,那就是提供用户的帐号密码换取AccessToken,名字应该叫“资源所有者密码凭据”。如果第三方应用只是开发者自娱自乐的小应用,这种方式是最简单的。
经过上面的注册和授权流程,用户和服务提供方都可以确认第三方应用的身份了,那第三方如何确认服务提供方和用户的身份?
第三方应用怎么确认服务提供方的身份呢?很简单,域名就是服务提供方的唯一标识,只要DNS不被劫持的话。第三方应用根据服务提供方的返回内容确认用户身份,载体是操作令牌AccessToken,为了方便后面统称ATOK,在OAuth里,ATOK的有效期是从用户授权成功,到用户取消授权,对第三方来说,几乎是永久的。至于用户授权之后取消授权,再授权的时候,两次ATOK是否一样,第三方能否处理好这种情况,OAuth里没有提及,看实现者的心情了。
把上面所说的综合在一起,可以得到一个OAuth的雏形版本:
第三方到服务提供方注册个应用帐号,当需要操作用户在服务提供方处的数据时,提供应用帐号密码申请授权,服务提供方将用户引导到授权页面,当授权成功时,服务提供方将对应该用户的ATOK发给应用,随后应用就使用这个ATOK来操作用户数据。
下面新浪微博OAuth的基本流程(其实各平台的流程都一样,贴这个是觉得这张图比较好看):
从图中可以看到OAuth的流程比原先设想的雏形多了不少东西,这些多出来的有什么作用呢?
四个步骤
OAuth授权分四步。
第一步,应用向服务提供方申请请求令牌(Request Token),服务提供方验证通过后将令牌返回。这个步骤由于涉及到应用帐号密码,在应用的服务端发起,所以这个步骤对用户透明。
第二步,应用使用请求令牌让浏览器重定向到服务提供方进行登录验证和授权。服务提供方校验请求令牌,将第三方的资料显示给用户,提示用户选择同意或拒绝此次授权。如果用户同意授权,发放已授权令牌并将用户引导到当前应用的注册地址。这个步骤从重定向开始到引导回注册地址之前,应用方并不参与用户身份校验和授权过程,确保第三方不可获得用户的真实帐号密码。
第三步,用已授权令牌向服务提供方换取ATOK。第三方应用需在服务端发起请求,用帐号密码和上一步的令牌换取ATOK,这个步骤对用户而言也是透明的。如果前两步分别是让服务提供方认证应用和用户,那这步就是用户和服务提供方再次认证第三方应用。因为用户浏览器将第二步的结果重定向到第三步,除非用户DNS被劫持,否则就能确保重定向到的是合法的地址。曾经我很困惑在用户授权之后为何不直接返回ATOK而需要再次换取,估计是出于对ATOK的安全考虑,用户浏览器一端存在太多的可能性让ATOK泄漏,最安全的办法还是让第三方服务端来获取和保管ATOK。
第四步,用ATOK作为令牌访问受保护资源。很多时候,权限是有多种类别的。ATOK包含了某个用户对某个应用的授权凭据,准确的说,ATOK对应用户授权时所赋予的一系列权限的集合。所以在这一步,除了校验ATOK的合法性之外,服务提供方还需对该ATOK是否拥有足够的权限执行被保护操作进行判断。
单次签名
在OAuth里,OAuth的相关请求都要做单次签名,目的是防止OAuth的请求被篡改和重放。签名当然是拿应用帐号的密码来做签名,其实就是对HTTP请求中所有OAuth相关的参数都连在一块,使用密码计算某种哈希值作为签名。OAuth规范里描述了签名的规则,那是相当的繁琐、复杂,足以吓跑一大堆未经世事的开发者。随便找一个OAuth开放平台的API文档,我相信在OAuth授权流程有接近一半会在描述怎么产生签名构造一个合法的HTTP请求。有一对文字图片描述还不够,各开放平台几乎无一例外地提供各种开发语言下的SDK,为求尽量降低技术门槛。即使如此,不少开发者依然觉得,OAuth的签名过程实在是太复杂了,而这些复杂也没有带来预期的好处。
重定向地址
为了防止有攻击者伪造重定向地址骗取用户授权,服务提供方应对授权时的重定向地址进行验证。所以注册时,第三方应提供重定向地址。服务提供方可以直接对重定向地址进行等值判断,但这样的话就没办法让第三方在授权过程中传递状态,只能借助Cookie/Session之类的方式了。服务提供方也可以判断重定向地址是否同一个域,这样的话应用方就可以在URI里传递少量状态。对于一些没有服务端的第三方Web应用,由于代码是公开的,将应用的帐号密码存在页面里并不合适。OAuth则建议不使用重定向地址,让用户在授权后,把授权码人工输入到应用中进行下一步。记得有段时间FaWave也是这么添加新帐号的。
安全漏洞
OAuth曾爆了一个安全漏洞,攻击者利用此漏洞可骗取用户信任获取非法的授权。
这个网页有该漏洞的详细说明,流程如下:
简单的说,这个漏洞主要的关键是:
1. 部分服务提供方并未对重定向地址进行合法性判断,或者部分第三方的重定向地址会根据URI的参数再次重定向从而被攻击者利用;
2. RequestToken从未授权到已授权的状态转变时没有变化,从而为攻击者暴力访问回调地址骗取ATOK提供可能;
对于第一点,攻击者伪造重定向地址,即可骗得用户对可靠第三方的授权,获得ATOK
对于第二点,假如第一点不成立,那攻击者可以用第一步的请求令牌构造一个合法的重定向请求,并在用户授权之后、浏览器重定向到合法重定向地址之前,进行同样操作执行这个重定向操作,此时就看攻击者和正常授权流程就存在竞争关系。如果第三方先处理攻击者的请求,攻击者就获得了最终的ATOK。
为了解决上述安全漏洞,OAuth更新了1.0a版本,主要改变就是第一步增加对重定向地址的签名,和第二步与第三步之间增加一个随机校验码,使之与未授权的RequestToken有所区分。
目前大部分的平台都转到了OAuth2。OAuth2虽并不兼容OAuth1,但基本原理是一样的。
OAuth2的改变
OAuth2对比OAuth1,主要改变有下面几点:
1. 取消繁琐的签名,全部改用HTTPS。
2. ATOK从原来的永久令牌变为临时令牌,增加RefreshToken
3. 取消获取RequestToken的步骤
4. 提供了多种场景的授权流程
HTTPS
OAuth原有的签名算法实在是太繁琐了,吓跑了不少开发者。对于服务提供方,也很不好实现,特别是单次签名的实现,由于服务提供方要确保每次由客户端生成的随机码不被重复利用,必须存储每次请求发来的随机码,无论是对存储还是校验都是一个难题。通常的做法是,存储一段时间的随机码,这个时间需比RequestToken的过期时间要长。这样即使到时还有重放攻击,RequestToken也已经失效。
OAuth2取消了签名,改用HTTPS来加密,确保通信内容不被第三方窃取。这个改变毫无疑问是降低了门槛,授权的流程被简化了。虽有少量人有异议,但OAuth2最大的异议是临时的ATOK。
ATOK与RefreshToken
由于第三方应用往往不重视ATOK的安全性,开发者为图方便经常把ATOK从后端发给前端页面或者存在cookie中。由于OAuth1中ATOK几乎是永久性的,即使发现ATOK被盗用,也只能让用户取消授权,这可能会造成一些其他的问题。OAuth2将ATOK改为临时令牌,当ATOK过期后,需要使用RefreshToken重新获取新的ATOK,让开发者郁闷的是,RefreshToken也不是永久性的,不同的服务提供方有不同的过期时间,相同的是,过期时间都不会太长,顶多也就几个月。
这个改变对很多第三方应用是个坑爹的改变,原本他们几乎都是拿ATOK作为OpenID来使用的(所以才有了各种ATOK被盗用的隐患),而到了OAuth2,ATOK已经不能唯一标识一个用户了,他们要多做很多的东西才能维持用户的身份,在使用ATOK访问用户资源时,步骤也是异常繁琐。
虽然临时ATOK这个改变很合理,但对开发者很不友好,今后会不会继续改变,可以拭目以待。我个人觉得,这个问题其实是另外一个问题,那就是开发者对用户帐号信息的安全意识太单薄,后面讲OAuth的问题时再详细讨论。
授权流程
OAuth1流程比较复杂,尽管规范里有对多种场景的授权流程进行不同的建议,但很多应用和开放平台最终都使用了同一种授权流程,结果产生了安全隐患(例如上面重定向地址的问题吗)。OAuth2描述了四种授权场景,为这些场景下的授权流程提供指导。我只简单说些要点和差异,详细的说明还是看官方文档和各开放平台的文档稳妥些。
(待续)