一、总述
二、环境配置
[三、业务servers管理]
[四、用户信息同步]
[五、token的拦截、生成、验证(服务端)]
[六、概念辨析:session、cookie、token的区别与token存储]
[七、token的分享(浏览器端)]
[八、安全性措施]
[九、用户体验]
[十、性能]
今天我要讲两个故事,一家高级商场的故事和一家普通商场的故事。
从前有个老板叫 C/S ,他开了这样一家高级商场叫 “TCP/IP Server" 。专门服务注册了VIP的顾客。他们对待注册了VIP的顾客的服务非常的高级。怎样高级?每一位VIP顾客进来购物,就会有一个服务员全程陪同。这些服务员提供所有的服务,非常的有才能。但就是都很没有记性,永远都记不住自己是在服务哪位顾客。这也怪不得服务员,谁叫每个VIP顾客都长得一模一样呢?于是老板就让服务员全程牵着顾客的手,并且手里拿着一张纸条,纸条上面写着顾客的姓名、性别、年龄、余额等等信息。这样他们就能叫出顾客的尊称,而且因为牵着手,所以不至于跟顾客走丢。真为这一群奇葩着急啊ヽ( ̄д ̄;)ノ!!!这些服务员这一群体被学术家们称为“progress”,而顾客这一群体被学术家们称为“client”
我很好奇高级商场卖的都是些什么东西。从商业角度来说,全程一对一的服务成本可真高啊。
2.普通商场的故事
后来,有一个老板叫 B/S ,他觉得 C/S 是个傻X,全程一对一的服务成本高,收益小。于是他开了一家普通的商场叫"HTTP Server"。跟"TCP/IP Server"商场一样,他的"HTTP Server"也是需要注册VIP的。但是他的服务员们并不会全程陪同顾客购物。总的来说,他们提供两样服务:1.询问 2.收银。每个服务员在处理完某个顾客的 询问服务 或者 收银服务 后,就可以为另一个顾客服务。 "B/S" 以为它的商场会比那个没头脑的 “C/S” 的商场收益更好!很显然嘛,一样多的服务员,他的店能服务更多的顾客。
天真的 B/S ,可怜的 B/S 。他差点就破产了~ 还不是那群让人着急的服务员!开张第一天,他们就惹来了满满的差评。他们总是记不住顾客买了什么东西,根本没有办法收银;而且顾客上前要询问他们“node.js的产品在哪个货架?”、“Python有多少种框架产品?”等问题的时候,这群服务员总是没有办法先叫出顾客的名字来给予顾客问候。顾客觉得他们都很没有礼貌~~~这也怪不得服务员,健忘是他们的天性,更何况每个顾客的相貌、穿着、声音又都完全一样,他们可是要一个人服务多个顾客的!
B/S决定关店整改!!!他给每个服务员配备了一个记录器。有一个服务员专门负责把进店的会员信息写进记录器,然后把一串唯一的ID写在一种叫做 cookie 的纸上,把纸贴在顾客额头上......没错,贴在额头上。其它服务员只要往记录器输入顾客头上的ID,就能查询到顾客的信息,这样服务员就不会因为叫不出顾客的名字被说没礼貌了;并且可以把顾客要买的东西, 临时 的记录在记录器里面。当然啦,记录器里的记录是整个商场共享的。所以,即便 顾客A 买《黑客与画家》的时候是 服务员A 给记录的,买《写给大家看的设计书》的时候是 服务员B 给记录的,结算的时候又是 服务员C ,他们搞错了。 B/S 非常自豪的把这种记录器机制叫做 session机制 ,真搞不懂他为什么要这么叫。就这样, B/S 的 “HTTP Server” 商场以更低的成本获得了更大的收益~ 他发迹了。
登录就是通过验证(密码验证、指纹验证、声音验证、人面验证、DNA验证...),向服务端表明你是谁?并且让服务端记住你是谁。对于C/S的架构来说,要记住你是谁太容易了~ 客户端跟服务端建立的TCP/IP连接就能很好的表明你谁,一个进程服务一个客户端,只要在进程空间里写个变量就OK了!但是对于B/S架构来说,因为是无状态的,服务一结束就断开(现在的长连接也还是会断),要记住你是谁真心太不容易了~ 所以只能由客户端来告诉服务端:“我是谁。”,也就是给客户端一个ID,然后服务端有个全局的空间(session)用于记录ID对于的信息,类似于Map<ID, infomations>。对于 每一次 的服务请求,服务端都要拿着客户端给的ID,去session查询这个ID是不是登录了,如果是则怎样怎样,如果不是就叫用户登录(假设每个页面的访问都要求用户登录)。拿着ID去session查询ID是不是登录了这个过程我们把它叫做 检验 ,这点先提前说明下,有点重要!登录成功后,就把这个ID做一个标记,表示登录。什么样的标记?你可以在这个ID的session空间里设置一个变量叫 mark
用来表示登录了,但是不会有人这么做的,至少也得设置个 username
的变量。不然你怎么知道这个用户是哪一个用户呢?有username就可以再去数据库查询更多的信息,当然也可以把更多的信息写到session里面,这里不做讨论。
很早期的公司,一家公司可能只有一个Server,慢慢的Server开始变多了。每个Server都要进行注册登录,退出的时候又要一个个退出。用户体验很不好!你可以想象一下,上豆瓣 要登录豆瓣FM、豆瓣读书、豆瓣电影、豆瓣日记......真的会让人崩溃的。我们想要另一种登录体验:一家企业下的服务只要一次注册,登录的时候只要一次登录,退出的时候只要一次退出。怎么做?
一次注册。 一次注册不难,想一下是不是只要Server之间同步用户信息就行了?可以,但这样描述不太完整,后续讲用户注册的时候详细说。实际上用户信息的管理才是SSO真正的难点,只是作为初学者,我们的难点在于实现SSO的技术!我们先讨论实现手段。
一次登录与一次退出。 回头看看 普通商场的故事 ,什么东西才是保持登录状态关键的东西?记录器(session)?那种叫做cookie的纸张?写在纸张上的ID? 是session里面 记录的信息 跟那个ID,cookie只不是记录ID的工具而已。客户端持有ID,服务端持有session,两者一起用来保持 登录状态 。客户端需要用ID来作为凭证,而服务端需要用session来验证ID的有效性(ID可能过期、可能根本就是伪造的找不到对于的信息、ID下对应的客户端还没有进行登录验证等)。但是session这东西一开始是每个server自己独有的,豆瓣FM有自己的session、豆瓣读书有自己的session,而记录ID的cookie又是不能跨域的。所以,我们要实现一次登录一次退出,只需要想办法让各个server的共用一个session的信息,让客户端在各个域名下都能持有这个ID就好了。再进一步讲,只要各个server拿到同一个ID,都能有办法检验出ID的有效性、并且能得到ID对应的用户信息就行了,也就是能 检验ID 。
以server群如何生成、验证ID的方式大致分为两种:
“共享Cookie”这个就是上面提到的共享session的方式,我倒觉得叫“共享session”来得好一点,本质上cookie只是存储session-id的介质,session-id也可以放在每一次请求的url里。据说这种方式不安全,我没去细究,哪位大神可以推荐下相关的资料,我后期补上。其实也是,毕竟session这项机制一开始就是一个server一个session的,把session拿出来让所有server共享确实有点奇怪。
SSO-Token方式因为共享session的方式不安全,所以我们不再以session-id作为身份的标识。我们另外生成一种标识,把它取名SSO-Token(或Ticket),这种标识是整个server群唯一的,并且所有server群都能验证这个token,同时能拿到token背后代表的用户的信息。我们要讨论的也是这种方式,一会上具体流程图。
单点登录还有非常关键的一步,这一步跟server端验证token的方式无关,用最早的“共享session”的方式还是现在的“token”方式,身份标识到了浏览器端都要面临这样的一个问题:用户登录成功拿到token(或者是session-id)后怎么让浏览器存储和分享到其它域名下?同域名很简单,把token存在cookie里,把cookie的路径设置成顶级域名下,这样所有子域都能读取cookie中的token。这就是共享cookie的方式(这才叫共享Cookie嘛,上面那个应该叫共享session)。比如:谷歌公司, google.com
是他的顶级域名,邮箱服务的 mail.google.com
和地图服务的 map.google.com
都是它的子域。但是,跨域的时候怎么办?谷歌公司还有一个域名, youtube.com
,提供视频服务。如何把 身份标识 分享给youtube.com这个域?简单的先提下,后续会细说。
利用带有src属性的HTML标签跨域设置cookie。例如:
<iframe width = 0 height=0 src="youtube.com/set_sso_token?ssotoken=iurk2i3f">
url跳转。比如:在google.com登录后,会跳转到youtube.com下种植cookie,再跳转会google.com。
Ajax设置cookie(目前还不了解,据说京东是这么做的。)
实际上,基本是用Token代替session的方式,也有非常多种实现方法。图片是我先去自己实验时的方案。
SSO-Server负责用户的登录(当然也有注册、修改基本用户信息、退出功能),它的作用有两点:
验证用户。就是验证账号密码。我们略过不关注
生成唯一的token
往其它业务Server同步token等信息,让各个业务server自己验证token有效性(如图); 或者,提供检验token有效性的API接口
token生成与验证方案:如上图。
token在浏览器的跨域分享方法,都尝试一遍。
安全性。暂时一律不考虑,我们先关注实现一个差劲的SSO,再来关注安全。因为安全是个大话题。太早引入,太拖节奏
实现语言:node.js(express4.x)。其实感觉SSO的实现是语言无关的,一个node.js实现的登录server完全可以为一个python实现的业务server服务。server之间的关系最后都演化成web接口的调用。