从密码到token, 一个授权的故事 auth2.0


1

美好的旧时光

 

我经常怀念三十年前那美好的旧时光, 工作很轻松, 生活很悠闲。

 

上班的时候偶尔有些HTTP的请求发到我这里, 我简单的看一下, 取出相对应的html文档,图片,发回去就可以了, 然后就可以继续喝茶聊天。

 

我的创造者们对我很好, 他们制定的一个简单HTTP协议, 就是请求加响应,  尤其是我不用记住是谁刚刚发了HTTP请求,   每个请求对我来说都是全新的!

 

邮件服务器很羡慕我, 他说:老弟,你的生活太惬意了,  哪像我, 每次有人从客户端访问邮箱, 我都得专门给他建立一个会话, 来处理他发的消息, 你倒好, 完全不用管理会话。

 

这是由应用的特性决定的, 如果邮件服务器不管理会话, 那多个人之间的邮件消息就会完全混到一起了, 乱作一团了。

 

而30年前的Web 基本上就是文档的浏览而已, 既然是浏览,我作为一个服务器, 为什么要记住谁在一段时间里都浏览了什么文档呢?

 

2

Session

 

但是好日子没持续多久, 很快大家就不满足于静态的Html 文档了, 交互式的Web应用开始兴起, 尤其是论坛, 在线购物等网站。

 

我马上就遇到了和邮件服务器一样的问题, 那就是必须管理会话,必须记住哪些人登录系统,  哪些人往自己的购物车中放了商品,  也就是说我必须把每个人区分开。

 

这对我来说是个不小的挑战, 由于HTTP协议的无状态特性, 我必须加点小手段,才能完成会话管理。

 

我想出的办法就是给大家发一个会话标识(session id), 说白了就是一个随机的字符串,每个人收到的都不一样,  每次大家向我发起HTTP请求的时候,把这个字符串给一并捎过来, 这样我就能区分开谁是谁了。

 

3

沉重的负担

 

大家都很高兴, 可是我就不爽了。

 

每个人只需要保存自己的session id,而我需要保存所有人的session id !  如果访问我的人多了, 就得由成千上万,甚至几十万个。

 

这对我来说是一个巨大的开销 , 严重的限制了我的扩展能力, 比如说我用两个机器组成了一个集群, 小F通过机器A登录了系统,  那session id会保存在机器A上,  假设小F的下一次请求被转发到机器B怎么办?  机器B可没有小F的 session id啊。

 

有时候我会采用一点小伎俩: session sticky , 就是让小F的请求一直粘连在机器A上, 但是这也不管用, 要是机器A挂掉了, 还得转到机器B去。

 

那我只好做session 的复制了, 把session id  在两个机器之间搬来搬去, 快累死了。

 

后来有个叫Memcached的给我支了招: 把session id 集中存储到一个地方, 所有的机器都来访问这个地方的数据, 这样一来,就不用复制了, 但是增加了单点失败的可能性, 要是那个负责session 的机器挂了,  所有人都得重新登录一遍, 估计得被人骂死。

 

我也尝试把这个单点的机器也搞出集群,增加可靠性, 但不管如何, 这小小的session 对我来说是一个沉重的负担。

 

4

时间换空间

 

这几天的晚上我一直在思考, 我为什么要保存这可恶的session呢, 只让每个客户端去保存该多好?

 

可是如果我不保存这些session id ,  我怎么验证客户端发给我的session id 的确是我生成的呢?  如果我不去验证,我都不知道他们是不是合法登录的用户, 那些不怀好意的家伙们就可以伪造session id , 为所欲为了。

 

嗯,对了,关键点就是验证 !

 

比如说, 小F已经登录了系统, 我给他发一个令牌(token), 里边包含了小F的 user id, 下一次小F 再次通过Http 请求访问我的时候, 把这个token 通过Http header 带过来不就可以了。

 

不过这和session id没有本质区别啊, 任何人都可以可以伪造,  所以我得想点儿办法, 让别人伪造不了。

 

那就对数据做一个签名吧, 比如说我用HMAC-SHA256 算法,加上一个只有我才知道的密钥,  对数据做一个签名, 把这个签名和数据一起作为token ,   由于密钥别人不知道, 就无法伪造token了。

 

 

这个token 我不保存,  当小F把这个token 给我发过来的时候,我再用同样的HMAC-SHA256 算法和同样的密钥,对数据再计算一次签名, 和token 中的签名做个比较, 如果相同, 我就知道小F已经登录过了,并且可以直接取到小F的user id ,  如果不相同, 数据部分肯定被人篡改过, 我就告诉发送者: 对不起,没有认证。

 

 

Token 中的数据是明文保存的(虽然我会用Base64做下编码, 但那不是加密), 还是可以被别人看到的, 所以我不能在其中保存像密码这样的敏感信息。

 

当然, 如果一个人的token 被别人偷走了, 那我也没办法, 我也会认为小偷就是合法用户, 这其实和一个人的session id 被别人偷走是一样的。

 

这样一来, 我就不保存session id 了, 我只是生成token , 然后验证token ,  我用我的CPU计算时间获取了我的session 存储空间 !

 

解除了session id这个负担,  可以说是无事一身轻, 我的机器集群现在可以轻松地做水平扩展, 用户访问量增大, 直接加机器就行。   这种无状态的感觉实在是太好了!

 

 


 

1. 我把密码献给你

 

小梁开发了一个“信用卡管家”的程序 , 可以自动从邮箱中读取信用卡相关邮件,分析、汇总,形成一个报表。 

 

小梁找到信用卡达人张大胖试用 : “你的信用卡那么多,看看我这个程序吧, 保准你会爱死它。”

 

张大胖尝试了几下说: “咦,你这个程序要读取我的网易邮箱啊,那需要用户名/密码吧”

 

“是啊 , 你把密码告诉输入程序不就行了, 我的程序替你加密保存,保证不会泄露。”

 

“得了吧你, 我可不会告诉你我的密码, 为了方便记忆, 我的密码都是通用的, 万一泄露了就完蛋了”

 

小梁说:“这样吧,我不保存,我就访问邮箱的时候使用一次, 用完就扔!”

 

“你以为你是阿里巴巴啊, 有信用背书, 你只是个小网站, 我把密码献给你,总是觉得不安全。就是我信任你,别人能信任你吗?”

 

小梁想想也是, 这是一个巨大的心理障碍, 每个人都要誓死捍卫自己的密码啊。

 

2. Token

 

过了一周, 小梁兴致勃勃地把张大胖拉来看“信用卡管家”的升级版。

 

“升级为2.0了啊, 这次不用问你要网易邮箱的用户名和密码了”

 

“那你怎么访问我的邮箱?”

 

“很简单,我提供了一个新的入口,使用网易账号登录, 你点了以后,其实就会重定向到网易的认证系统去登录,  网易的认证系统会让你输入用户名和密码,并且询问你是否允许信用卡管家访问网易邮箱, 你确认了以后,就再次重定向到我的‘信用卡管家’网站, 同时捎带一个‘token’ 过来, 我用这个token 就可以通过API来访问网易邮箱了。 在这个过程中, 我根本不会接触到你的用户名和密码,怎么样, 这下满意了吧?”

 

“你说得轻松, 你这个信用卡管家是个小网站,还没有什么名气, 网易怎么会相信你这个网站呢?”

 

“我当然要先在网易注册一下啊, 他们会给我发个app_id 和app_secret,  我重定向到网易的时候需要把这个东西发过去, 这样网易就知道是‘信用卡管家’这个应用在申请授权了。”

 

 

(点击看大图)

 

张大胖说: “ 你这重定向来重定向去的, 实际上不就是为了拿到一个token 吗?”

 

“对啊,因为你不信任我的信用卡管家, 不让它保存你的密码,只好用token的方法了 , 它是网易认证中心颁发的,实际上就代表了你对信用卡管家访问邮箱的授权,所以有了这个token 就可以访问你的邮箱了”

 

"对了"  张大胖问题, “你为什么用Javascript的方式来读取token啊”

 

“这样我的后端服务器就不用参与了,工作都在前端搞定, 你注意到那个URL中的#号了吗? www.a.com/callback#token=<网易返回的token>”

 

张大胖说: “我知道啊,这个东西叫做hash fragment,  只会停留在浏览器端, 只有Javascript 能访问它,并且它不会再次通过http request 发到别的服务器器, 我想这是为了提高安全性吧。”

 

小梁说: “没错, 那个token非常非常重要,得妥善保存,不能泄露!”

 

“可是在第6步通过重定向,这个token 以明文的方式发送给了我的浏览器, 虽然是https ,不会被别人窃取,可是浏览器的历史记录或者访问日志中就能找到, 岂不暴露了?”

 

小梁说: “这个.... , 我说你这个家伙,安全意识很强烈嘛, 让我想想,有没有更安全的方式。"

 

3. Authorization  Code + Token

 

又过了一周,小梁成功地把信用卡管家升级为3.0.

 

他对张大胖说: “这次我成功地把那个非常重要的、表示授权的token 给隐藏起来了, 你要不要看看?”

 

“你先说说你是怎么隐藏的?”

 

“其实整体思路和之前的类似,只是我引入了一个叫做Authorization  Code 的中间层。 当你用网易账号登录的时候, 网易认证中心这一次不给我直接发token,而是发一个授权码(authorization code) ,   我的信用卡管家服务器端取到这个code以后,在后台再次访问网易认证中心, 这一次他才发给我真正的token 。 还是直接上图吧:”

 

(点击看大图)

 

张大胖说: “还比较容易理解, 本质上就是你拿着这个返回的授权码在服务器后台‘偷偷地’完成申请token 的过程, 所以token 浏览器端根本就接触不到,对吧?”

 

“什么叫偷偷地申请token ? 这是我信用卡管家服务器和网易之间的正常交流, 只是你看不到而已。”

 

“开个玩笑了, 你虽然隐藏了token,但是这个授权码确是暴露了啊,你看第7步,我在浏览器中都能明文看到,  要是被谁取到, 不也是照样能取到token吗?”

 

小梁说: “我们肯定有防御措施, 比如这个授权码和我的信用卡管家申请的app_id,app_secret关联, 只有信用卡管家发出的token请求, 网易认证中心才认为合法; 还可以让授权码有时间限制,比如5分钟失效,还有可以让授权码只能换一次token, 第二次就不行了。 ”

 

“听起来似乎不错, 好吧, 这次我可以放心地使用了!”

 

4. 后记

 

本文讲的其实就是就是OAuth 中的三种认证方式,依次是:

1. Resource Owner Password Credentials Grant(资源所有者密码凭据许可)

2. Implicit Grant(隐式许可)

3. Authorization Code Grant(授权码许可)

 

还有一种叫做Client credentials ,用的较少,文章没有涉及。

 

这些名称有些古怪, 但是本质没那么复杂。 在OAuth中,还有几个术语大家可以理解下:

资源所有者 :  就是我们上文的张大胖

资源服务器 : 即网易邮箱

客户端: 就是上文的信用卡管家

授权服务器 : 即上文的网易认证中心

 

想查看完整的OAuth 2.0协议, 可以点击阅读原文

 https://mp.weixin.qq.com/s?__biz=MzAxOTc0NzExNg==&mid=2665513566&idx=1&sn=a2688cadbe9c8042ff1abbdf04a8bd5e&chksm=80d67a1db7a1f30b28b93ed2ab29edfbf982b780433e4bfd178e3cc52cb1f9100cc8f923db4f&scene=21#wechat_redirect

posted @ 2018-01-23 11:25  Bigben  阅读(9294)  评论(1编辑  收藏  举报