Web安全的问题其实很有意思,这些在互联网上使用的安全协议看似复杂,其实在现实生活中都有类似的准则被人们理所当然地广泛使用。比如这里要讲的授权,这是个所有人都很熟悉的概念。例如你要给张三1000元现金,你手里没现金,就想让张三直接去你银行账户里取,那你会怎么做呢?你可以这样:
- 把你的银行账号密码告诉张三让他去银行取钱
但显然傻瓜才会这样干,正确的做法是这样:
- 开一张1000元的支票给张三去银行支取
这张支票就是授权的凭证,代表你授权张三从你的银行账户里提取1000元。这样的事情发生在互联网上,那就是一个第三方App,想要获取你在某一网站上的一些私有信息(例如邮箱,名字,照片),需要得到你的授权。这个授权的过程原理和上面的支票类似,当然在具体实现上需要更复杂更严密的步骤,这就是OAuth协议做的事情。
有一个很常见的场景,就是在很多App上,往往可以使用你另一个平台上(微信,QQ等)的账号注册登录,例如简书:
你可以点击QQ登录,它会给你转到QQ授权登录简书的界面,从这里开始就进入了OAuth 2.0协议的执行流程了。
OAuth 2.0 协议 - 授权码模式
网上介绍OAuth 2.0协议的文章也挺多的,所以我们直奔主题,结合上面的例子,讲讲使用最广,最严密的一种实现方式 - 授权码模式。
这里直接贴一张 RFC 6749 里的图,是整个授权码实现过程的流程图。这里有几个名词需要解释一下:
- Resource Owner,就是资源的主人。
- User Agent,一般就是浏览器。
- Client,这里是指第三方App,例如简书,注意这里是指它的后台而不是前端。
- Authorization Server,授权服务器,例如QQ的授权服务器,它管理用户QQ信息的授权。
上面图中的 ABCDE 五个步骤:
(A)Client(即第三方App)将用户导向Authorization Server地址,并且提供一个重定向URL。
(B)授权页面询问用户,用户同意授权。
(C)Authorization Server产生一个授权码,并且将用户导向(A)中的重定向URL,带上授权码,
作为URL的参数。
(D)这个重定向URL实际上是Client域名下的一个地址,于是这个请求发送到了Client后台,它使用
授权码向授权服务器申请一个token。
(E)Client得到token,这个token就是一个令牌,可以用来获取用户授权的资源。
咋一看似乎还是难以理解它的具体流程和原理,所以我们还是直接看上面的例子。
步骤(A)
在简书的登录界面,点击QQ图标,就会被导入到QQ登录简书的授权界面,即进入步骤(A),注意这是一个QQ域名下的页面,现在暂时和简书没有关系了,对话只发生在用户和QQ之间,我们来看这个页面的Http请求:
URI:
https://graph.qq.com/oauth2.0/show?
参数:
which=Login
display=pc
client_id=100410602
redirect_uri=https://www.jianshu.com/users/auth/qq_connect/callback
response_type=code
这里最重要的参数是:
- client_id,代表了简书,是它向QQ授权服务器申请的唯一ID。
- redirect_uri,这是简书域名下的一个地址,等一会儿用户同意授权后,QQ会重新将浏览器导向这个地址。
- 有时还会带一个参数scope,这表示授权的范围,比如用户名字,qq号,好友列表之类的。
所以这个页面实际上是简书将用户导到了QQ,让QQ和用户达成协议,同意授权给简书。QQ将要求用户登录自己的QQ账号,验明身份,并向用户征求相应QQ信息的授权给简书。在这里没有scope参数,而是QQ让用户在页面上选择相应的授权范围,这其实是一样的。
步骤(B)
用户在QQ的授权页面上用QQ账号登录,并且选择同意授权给简书相应QQ信息。
步骤(C)
用户点击同意授权后,QQ将会生成一个授权码(Authorization Code),并且将用户导回之前(A)中的redirect_uri,它是这样的:
URI:
https://www.jianshu.com/users/auth/qq_connect/callback?
附上授权码:
code=1EE50EE39E4260EBCFA4892F72F84953
授权码实际上就是用户同意授权的凭证,这个凭证将随着这个URL发回给简书。
步骤(D)
现在又回到了简书的控制范围,上面的URL由用户浏览器发到简书后,现在开始是简书后台的工作了。简书将使用授权码,向QQ申请一个token令牌,这个请求是发生在简书后台和QQ之间的,具体长什么样我们当然不得而知,但它至少要包含以下信息:
- 简书的client_id
- 和这个client_id对应的secret,这也是简书在向QQ申请client_id时得到的,由简书保存,简书用它向QQ服务器证明,这是我本人。
- 授权码
步骤(E)
QQ服务器验证以上信息,向简书发送token,简书后面可以用这个token获取用户的相应信息。
之后的工作就完全由简书决定了,通常的做法是它获取了用户QQ信息,然后做一系列处理,比如用用户的qq号产生一个简书账号(或查找已经关联该qq号的简书账号),实现登录,再将用户302重定向到简书的主页面。
原理分析
上面五个步骤,你来我往似乎很复杂,我们整理一下实际上就是两个部分:
- (A)(B)(C)三步发生在用户和QQ之间,当然是由简书发起的。简书要求QQ向用户征求授权,于是QQ和用户开始对话,同意授权,以授权码为凭证,这个授权码包含的内容是:“用户xx同意向简书提供QQ信息”。就类似于用户在银行开出一张支票,支票上写着“用户xx同意向张三支付100元”。
- 然后(D)(E)两步,转到了简书后台和QQ之间,QQ重定向用户浏览器到简书之前提供的URL,附上那个授权码,类似于你拿到了支票,将支票转发给了张三。然后简书拿着这个授权码,加上能证明自己就是简书的相关证件,也就是简书的client_id和secret,向QQ换取一个token令牌,后面就能用这个token向QQ取得用户授权的相关信息。
可以看到这里的逻辑是完全符合我们的生活常识的,正好对应我们向张三开支票取钱的过程,只是步骤上更复杂更严密,因为互联网的世界有更多风险因素需要考虑。有个问题大家经常会问到:
- 为什么要分成两步,先拿授权码,再换取token?如果对比支票的例子,自始至终支票只有一张,凭票即可兑钱。但是OAuth 2.0协议却拆分成了两步,严格来讲授权码其实不是真正的支票,只是支票授权书,要获取真正的支票token,需要简书亲自去向QQ索取。这是因为ABC三步发生在用户和QQ服务器之间,这个对话信道是不可信任的,可能用户的浏览器已被劫持。所以在这个信道上不能发送真正的token,否则token一旦泄露就失去所有安全性了,而只能发送一个授权码作为凭证,这个授权码是和简书绑定的。简书拿到这个凭证之后,再带上自己的身份证client_id和secret,才能向QQ换取真正的token支票。其他人即使窃取了授权码,但因为他们提供不了简书的secret,QQ也会拒绝授予token。简书和QQ之间的对话是发生在后台的,可以认为这个信道的安全是有保障的。
后记
写这篇是因为看到前一阵Facebook的数据泄漏事件,不过从前因后果来看FB似乎也是被坑了一把,真正的始作俑者是那个叫Cambridge Analytica的公司,它获取FB的用户信息并且分析它们的喜好性格,来有针对性地向他们推送带有政治倾向的广告和宣传信息来影响选举。据说它为了收集用户数据,还在FB的游戏平台上发布小游戏。这种第三方App在用户玩之前,往往会告知用户将收集您的Facebook信息balabala,一般人常常看也不看就点同意了:
这样的霸王条款其实我们已经很习惯了,一个第三方的App,想要获取你在某些网站上的个人信息,就要用到Oauth协议,实现这种细粒度的授权。有些小游戏他要求的授权范围就很广,包括获取用户在FB上所有的Post和点赞信息之类的,这其实就比较危险了,因为他可以分析你的心理性格。还有些要求获取好友列表的,这也值得警惕,因为那些数据收集的公司就是这样由点到面类似爬虫一样获取大量FB用户的。FB的事情也是在提醒我们,以后看到这样的提示信息需要好好考虑一下,因为一旦点了同意,就相当于你把“支票”开出去了。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。