SSO这一律念由来已久,网络上对应不一样场景的成熟SSO解决方案比比皆是,从简单到复杂,各式各样应有尽有!开源的有OpenSSO、CAS ,微软的AD SSO,及基于kerberos 的SSO等等……这些优秀的解决方案尽显开发及使用者的逼格,固然需求所致无谓好坏高低,知足实际之需才是王道!编程
本文并不讨论上述提到的方案的整合使用、或者复杂场景如:安全、防火墙、N 多个系统层叠调用这种"巨型项目"里SSO的实现与使用,也并不涉及 C/S 、C/S+B/S 的SSO解决方案,仅关注B/S 上的SSO实现。虽是如此,然而万变不离其宗,这里咱们将从一个简而小的登陆场景去接触SSO的本质,描述如何原生态地自实现一个轻量、微核的SSO(本文不提供源码)。跨域
文章将由浅入深地探讨SSO(单点登陆),涉及SSO的定义、表现、原理、实现细节等方面的阐述,借助你们熟知的淘宝、天猫登陆场景,经过对阿里登陆的模仿实现,创建一个简单模型,而后不断由该模型进行迭代并对每个迭代版本进行详细描述,最终获得一个支持跨域的SSO( 力求条理清晰,层层递进,简单但有深度!!!!开始部分本着让即便从未听过SSO的同窗也可以从抽象文字定义的概念印象过渡到具象的视觉认知这一宏(zhuang)伟(bi)理念入手,将会有不少浅显的描述,"老司机" 能够快速掠过)浏览器
SSO( Single Sign-On ),中文意即单点登陆,翻译得比较精简,我的以为 Wiki 上的解释更细腻点—— SSO, is a property of access control of multiple related, but independent software systems. With this property a user logs with a single ID and password to gain access to connected system or systems without using different usernames or passwords, or in some configurations seamlessly sign on at each system. ( 单点登陆是一种控制多个相关但彼此独立的系统的访问权限, 拥有这一权限的用户可使用单一的ID和密码访问某个或多个系统从而避免使用不一样的用户名或密码,或者经过某种配置无缝地登陆每一个系统 ). 注:系统,在本文特指WEB 应用或者WEB 服务;用户,下文也会称之为User;ID,用户标识;密码,本文也称其为口令,Password, Passcode 或者 Pin。缓存
OK,从上面的定义中咱们总结出 与 SSO 交互的2个元素:1. 用户,2. 系统,它的特色是:一次登陆,所有访问。上面提到SSO是访问控制的一种,控制用户可否登陆,即验证用户身份,并且是全部其它系统的身份验证都在它这里进行,那么咱们是否是能够认为SSO仍是一个验证中心。那么从整个系统层面来看SSO,它的核心就是这3个元素了:1. 用户,2. 系统,3. 验证中心。可能扯了那么多仍是不足以形象地描述咱们萌萌的SSO,呐,有图有真相:安全
既然SSO这么棒,应该如何实现呢?cookie
咱们暂不考虑细节,先从SSO须要解决的问题入手:使用一个帐户经过一次登陆,便可在多个相关的系统之间来回访问,为了更加形像咱们仍是上图:(多图预警)网络
登陆页面,网址:login.taobao.com ….. 我将在 login.taobao.com 所指的系统进行登陆数据结构
访问网站,第一张网址:buyertrade.taobao.com…. 访问 buyertrade.taobao.com所指的系统了;而后访问另外一张网页网址为:favoriate.taobao.com, 访问favoriate.taobao.com 所指系统,两个系统的 Domain 是相同的,请注意这点;架构
接下来我再分别访问淘宝(www.taobao.com)和天猫(www.tmall.com)的首页 ,图中显示我仍旧是登陆的( 注意:这里是不一样的Domain下,系统之间的来回访问)并发
能够看到,我除了在第一张网页图那里须要输入用户名(ID)和口令(password)进行登陆,再访问其它相关系统时,从图2-5 中全部的访问操做,不管域名相同仍是不一样我都不须要再登陆了,它们都知道我叫"望向明天"!对,没错,这就是SSO的做用:一次登陆,所有访问,读者也能够尝试下看看是否是如此;
好,通过我上面一大段废话,基本上对SSO要解决什么问题有一个清晰的认识。如今咱们自行脑(yi)补(yin)下SSO 的原理是什么样的。
嗯,问题解决了,没错,就这样。
由上面的猜测能够获得第1个解决方案,记为方案1。这里对这个猜测作一点小小的优化,猜测中第2点 "各个系统保存" 好让人闹心,同一份数据保存多份,太浪费,这里咱们把每一个已登陆的用户信息保存到公共缓存中。好,咱们再来描述下这个方案:
文字完了,接下来看看方案1的架构图和时序图:
嗯,图文并茂的样子,难道就这么大功告成了? 咱们先把方案1中完成的初版 SSO 记为SSO_V1,接下来咱们来好好地捋一捋。
SSO_V1 貌似解决了问题,可是深刻思考,细思极恐!由于这个设计有Bug:每次传 ID 给服务Ai,可是这个ID 每次怎么获取来呢?登陆SSO的时候,这倒没有问题,可让用户填!但第2次请求是发给Ai中的某一个 Aj 时,ID 要怎么来( 假设百度和新浪是相关但彼此独立的系统,登陆百度后,再访问新浪时怎么让新浪取到与登陆百度时同样的ID吧)?总不至于每次发请求时都要求用户填一遍ID 吧?
其实咱们把 猜测 中最值得思考的问题之一忽略掉了:
如何让SSO"告知"系统Ai,当前登陆的User 的ID和password?
这问题能够这样来描述:假设有W ( www.weidai.com )和 T( trade.weidai.com ) 两个系统,W和T 都经过S (login.weidai.com) 系统登陆,当由U访问W再转向S 完成登陆后,怎样作才能使 U 访问T 时不须要再一次经过 S 进行登陆验证?
对,若是你是WEB 开发的老司机,很天然你会想到用cookie ,即把用户信息( 本文也会称之为UserInfo )保存在cookie 当中,由于 不管W 、T 或者 S 它们的Domain是同样的——都是 weidai.com ——同一Domain,这有何用?用处就在于 W 、T 以及 S 能够共享此路径下的 cookie。这里,让咱们优化的心再一次燃烧起来——直接保存用户的 ID 和 口令 对于咱们这么有逼格,有追求的猿来讲有点太不讲究——为何呢?不太安全,cookie 中 最好保存一个 公共Session ID( 请和WEB 本身生成的Seesion ID进行区分 ) ,而咱们的公共缓存 Cache 中保存的 UserInfo 是一个由 公共Session ID为Key ,以包含用户标识和口令的数据结构为Value的Map。最后附上这一流程的时序图及简要说明:
嗯,又是图文并茂的样子,难道再一次大功告成? 咱们暂时把刚才的方案记为方案2,并把方案2中完成的升级版SSO记为SSO_V2,接下来咱们再来好好地捋一捋!
SSO_V2 可以在 Domain 相同的状况下"完美"解决问题,可是在Domain不一样的状况下怎么作到免登呢?如上面图示淘宝( www.taobao.com )和天猫( www.tmall.com )若采用SSO_V2 确定没法作到免登的,由于咱们知道当访问天猫时(Domain 为tmall.com ),淘宝( Domain 为 taobao.com )下的 cookie 是没法随访问请求一并传给与天猫相关的系统的。因此问题变成,怎么让不一样Domain下的系统也"知晓"用户已经登陆的实事?
在咱们提出SSO_V3前,咱们先看看SSO 本质是什么?经过这么多的文字描述、样图解释,咱们能够看到,要让用户"一次登陆,所有访问"无非就是让全部的系统共享"一份"(相同)已验证的、安全可靠的验证信息。因此问题就能够转化为:不一样Domain下的系统如何共享一份的验证信息?既然Domain没法作到交叉访问,那咱们可让不一样Domain下的WEB应用持有相同的验证信息,这在效果上不就是一份吗!因此最终要解决的问题就是:SSO系统如何使不一样的 Domain 拥有一份相同的cookie? —— 让SSO在用户进行登陆时再去访问其它域下的系统,并让各个系统保存同样的验证信息,这样不一样域下就会有同一份cookie。
如下是SSO_V3的时序图和文字说明,这里咱们假设 SSO 的Domain 为 SD,T 的 Domain 为 TD:
嗯,仍是图文并茂的样子,这下是否是能够完事了呢?咱们仍是把刚才的方案记为方案3,并把方案3中完成的升级版SSO记为SSO_V3,而后仍是来好好地捋一捋!
再细细的考虑下SSO_V3的实现方式,有没有感受它哪里有点不对劲( 思惟一直跟着我来走,是否是被绕晕了,想发现不对劲,怎么可能)? SSO_V3 使不一样 Domain 获取相同的cookie 拷贝时,表面是在U处主动发出向T的请求(实际上是被动), 但其实是 SSO 返回给 U 的页面自动完成的(经过 JS、经过页面自动跳转、iframe均可以实现)。因此方案SSO_V3要求SSO 预先知道有哪些系统是跨域的!!!并且它还有一个很严重的问题:假如与SSO相关但相互独立的系统中,有 20+ 须要跨域才能访问,而SSO要在用户登陆时完成20+跳转……如今你是否是要呵呵了?貌似完美解决跨域的SSO_V3 居然如此有问题,有没有心好塞!
SSO_V3 解决的核心问题是:针对跨域的系统,各系统间如何保证获取到的 验证信息是一致的,解决方法便是在用户第一次登陆时把验证信息复制给全部跨域的系统。这种方案在跨域系统少的状况下却是不须要有太多担忧,可是当跨域系统多、且验证步骤比较复杂时用户将会卡在登陆界面,最后不得不怒关页面!因此当理清这些逻辑,很天然就会想到接下来要如何对SSO_V3进行优化。核心思想就是:既然一次性解决会有问题,那就分屡次解决!简单的描述下咱们将要看到的SSO_V4,用户登陆后,当第一次访问跨域系统W 时,跳到SSO复制一份至W的cookie中,过程结束;当访问T时,重复该处理动做。
如下为SSO_V4的时序图及简要说明:
其实咱们经过上面的实用版(SSO_V2,SSO_V3,SSO_V4)SSO,能够看到除了用户的第一次登陆某个应用相对来讲比较特殊,其它处理都是一致的。因此当咱们抛去细节以后,不仿这样联想SSO的实现:完成登陆逻辑并使各系统共享验证信息和验证逻辑,从这个层次去看SSO,咱们发现它其实只负责用户登陆和身份验证这二、3个点。
下面是用户第一次登陆及SSO与其它系统交互的简图:
第3部分中的身份验证和验证信息方面都作得比较简单,在实际项目中不可能如此使用!在此提出一个方案以供参考(这也是比较流行的一种)。
这样处理后相信可以知足大部分应用的需求了!
SSO这一理念到目前为止已经很是成熟,关于它的各类设计、设置均可以定制一套标准了。然而因为SSO与用户有强关联,因此不少设计在最初时每每会把SSO设计成一个用户管理系统,而使得SSO与业务耦合,随着业务的不断变化和演进,底层数据结构、接口不断的复杂化,又反过来使得上层服务的架构设计变得尴尬。
若作更进一层的抽象和划分,SSO只需负责登陆这单一功能便可,设计上知足单一职责原则[1],加上几乎全部网站的登陆都大同小异(可能登陆界面会变幻无常)且不与业务有过多牵连,这又使得SSO与业务彻底分离,不管未来业务怎样演进,产品如何迭代,SSO做为底层应用能够以不变应万变。Really? All problem in computer science can be solved with another level of indirection,except of course for the problem of too many indirections.[2] 如何在设计中作到复杂与简洁的平衡,须要根据实际情境深度地考量,这能够扯出长篇大论了(按下不表),咱们的SSO姑且就搞这几个功能:登陆、记录轨迹、登出,如下是用例图:
第3节第5部分有提到"登陆交由SSO完成,各系统共享一套验证逻辑",很天然的验证这一逻辑对SSO也是必须的,在此就由SSO来完成,其它系统只需将其配置到各自系统里便可。再加上SSO是用户"作案的第一现场",因此记录用户登陆信息的事也很天然的就让SSO给干起来了,并且这一功能不只可以让用户感觉到咱们对客户的用心,同时也为后期数据分析业务提供数据源!
通过上面的讨论,咱们着手思考SSO的数据结构——数据表设计(我的认为面向对象编程中数据结构的优劣基本决定整个应用的质量)。从SSO 功能简单及其微服务的定位,SSO的表应该简洁、单一,上层服务若须要对其进行扩展,只须要对基本表进行外键引用便可!这里咱们暂时只用3张表,分别为User、Trace(用户轨迹表)和使用平台表,图示与描述以下:
用户表:User
用户轨迹表:Trace
使用平台表:Platform
经过上面的总体思路及数据结构的定型,咱们能够继续铺开将SSO要涉及到的一些主体类及主要方法定义好,仍旧上图:
写到这里,对于这个图示就再也不作过多解释,你们基本能够开始作各类各样的脑补了!额,仅说小小的一个点:验证由Interceptor实现,这样验证逻辑则能够以插件形式配置到其它系统,实现全部系统共享一套验证逻辑,固然你也能够根据具体状况作成Filter,看我的爱好; 访问这方面交给第三方处理,好比由Shiro、Spring Security等来完成……酱紫,结束!