一、引子
问题1:什么是SSO?单点登录是什么?
回答1:SSO英文全称Single Sign On,单点登录。SSO是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。它包括可以将这次主要的登录映射到其他应用中用于同一个用户的登录的机制。它是目前比较流行的企业业务整合的解决方案之一。如:
如图所示,图中有4个应用,分别是Application1、Application2、Application3和SSO。Application1、Application2、Application3没有登录模块,而SSO只有登录模块,没有其他的业务模块,当Application1、Application2、Application3需要登录时,将跳到SSO系统,SSO系统完成登录,其他的应用系统也就随之登录了。这完全符合我们对单点登录(SSO)的定义。
使用单点登录后,将登录功能单独拿出来作为一个独立的模块(即SSO模块),仅且仅与登录相关的操作在该模块中完成。
问题2:为什么要使用单点登录?单点登录的需求来源?
回答2:在企业发展初期,企业使用的系统很少,通常一个或者两个,每个系统都有自己的登录模块,运营人员每天用自己的账号登录,很方便。但随着企业的发展,用到的系统随之增多,运营人员在操作不同的系统时,需要多次登录,而且每个系统的账号都不一样,这对于运营人员来说,很不方便。
例如,像百度这样系统,下面有百度知道(zhidao.baidu.com)、百度经验(jingyan.baidu.com)、百度文库(wenku.baidu.com)等子系统,如果每一个子系统,其二级域名不同,
像个人博客网站,
仅以我们熟悉的Java开发为例,早期的web开发只需要一个登录模块,所有的业务逻辑都集中在这一个应用服务器(一个Tomcat)完成,包括登录、查询、修改,因为早期web应用使用人数较少,并发量少且交互性要求低,所有这样做是没有任何问题的。但是随着公司的规模的扩大,不断开拓更多的新业务,而且它们(这些新开拓的业务)都集成到web系统中,如果此时仍然将所有的业务合并为一个项目工程,部署到一个tomcat上,使用一个登录模板,不仅出现项目开发过程中沟通、维护、部署困难,而且一个Tomcat无法承受所有业务加起来的并发,造成不好的用户体验,更加致命的是,如果这个Tomcat宕机,公司的整个系统奔溃,其后果不可想象。
随着互联网时代到来,我们仍然将所有的应用都放在公司官网(.company.com)上,但是不同应用使用不同的Tomcat,形成一个Tomcat集群,甚至一个应用一个模块就要使用一个Tomcat,均是为了最大程度上满足极限情况下的并发。
采用分布式集群架构:
这样造成了一个问题——多个Tomcat之间如何共享登录信息的问题。
即我们的需求是对于公司的所有的产品(Application1 Application2 Application3),用户仅登录一个产品就可以使用切换使用其他产品了(公司的其他所有产品都变成了已登录状态)。
于是,就想到是不是可以在一个应用(如Application1)登录,其他应用(Application2...ApplicationX)就不用登录了呢?从而实现登录状态应用间共享,这就是单点登录要解决的问题。
先看第二部分和第三部分,对比传统登录流程和单点登录流程。
二、传统登录(不采用单点登录)业务流程和存在的问题
2.1 传统登录业务流程
简单的B/S交换图:
如上图所示,我们在浏览器(Browser)中访问一个应用,这个应用需要登录,我们填写完用户名和密码后,完成登录认证。这时,我们需要做两件事,
其一,在服务端(Server),我们在这个用户的session中标记登录状态为yes(已登录);
其二,在浏览器(Browser),我们需要在浏览器中写入Cookie,这个Cookie是这个用户的唯一标识。
下次我们再访问这个应用的时候,请求中会带上这个Cookie,服务端会根据这个Cookie找到对应的session,通过session来判断这个用户是否登录。如果不做特殊配置,这个Cookie的名字叫做sessionid,值在服务端(server)是唯一的。
让我们用更详细的流程图演示上面这个过程:
一共是两次服务器请求,第一次是用户登录请求,输入用户名和密码之后,完成登录认证;第二次是某个业务请求,判断是否已经登录,若已登录,执行业务请求,若未登录,转到登录界面。传统登录业务流程图:
2.2 传统登录存在的问题
上面这个登录流程图,使用服务端session记录登录状态,乍看之下,这个登录流程已经满足需求,确实,如果像第一部分中所说的早期web系统,部署在单台tomcat上,以上实现是没有任何问题的。但是现代分布式web系统,使用集群tomcat,就会存在session无法共享问题。
每一个应用部署在一个Tomcat上,每一个应用都有一个独立的session,不与其他应用相互沟通,不能统一。
只要解决session共享问题,登录问题即可解决。
2.3 解决思路
思路1:tomcat的session复制
优点:不需要额外开发,只需要搭建tomcat集群即可。
缺点:tomcat 是全局session复制,集群内每个tomcat的session完全同步(也就是任何时候都完全一样的) 在大规模应用的时候,用户过多,集群内tomcat数量过多,session的全局复制会导致集群性能下降, 因此,tomcat的数量不能太多,5个以下为好。
小结:这种方法限制了web应用系统规模,舍去,且看思路2.
思路2:把session数据存放在redis。
Redis可以设置key的生存时间、访问速度快效率高。
优点:redis存取速度快,不会出现多个节点session复制的问题。效率高。
缺点:需要程序员开发,且额外使用到redis。
小结:需要额外借助使用到redis,这种方法我们先且不论。
思路3:实现单点登录系统,使用token取代session保存登录状态。
优点:token是用户名登录生成的一个字符串,存在请求头中进行网络传送,相对于session的优点是可以实现应用间共享。
缺点:无。
使用单点登录的方式是本文重点,且看第三部分——单点登录业务流程。
三、单点登录业务流程
单点登录是如何解决多系统登录问题的?单点登录是如何实现多个系统一次登录的?
回答:token机制。用户在第一次输入账号密码登录成功后,生成token,以后只需要验证token的合法性,token合法不需要再次登录,token不合法才需用户再次输入账号密码登陆。下面给出单点登录业务流程图:
对比单点登录业务流程图和上面的传统登录业务流程图,我们知道,简单的单点登录(即下文中同域下单点登录)其实就是使用token代替sessionId进行会话认证(传统登录将服务端的sessionId通过Cookie写入浏览器,同域下单点登录将服务端生成的token通过Cookie写入浏览器)。
这样真的有用吗,从传统登录到单点登录,仅仅将sessionId变为token,真的可以实现登录状态应用间共享吗?即公司一个应用登录,其他应用均处于登录状态。
让我们先来全面认识一下这个新的概念——Token.
问题1:token是什么?
回答1:token俗称“令牌”,实际上是用户登录成功后,服务端返回的一个字符串。
问题2:token返回来之后有什么用?怎么用?
回答2:
(1)关于token有什么用,上面不是说了吗?token的存在是为了实现多个系统中一次登录,共享登录状态。
(2)关于token怎么用,程序以后每次与服务端交互,请求服务端时(无论get请求还是post请求),都将token放在请求头里面(记住:token一般是放在请求头中的),和整个请求一起发送给服务端,服务端收到程序发来的请求,先检验请求头中的token是否有效,token有效的情况下在完成相关操作,若token无效,返回相应的状态码,程序收到状态码后,需要用户重新登录。
问题3:服务端返回的用户token存储在哪里?
回答3:浏览器收到服务端返回来的token,一般存放在cookie或者localStorage(本地存储)里面。
问题4:服务端生成的token值如何确定,随机的吗?还是有一定规则?
回答4:理论上可以是随机的,token只要满足唯一性、确定性即可。因为单点登录要完成一次登录,多系统共同使用这个功能,只需要有一个唯一确定的token字符串生成就好了,没必要要求这个字符串根据何种规则生成。但是公司项目开发,一般项目组长和C/S、B/S开发人员都会沟通好,遵循一定的规范。
简单token的组成可以是:uid(用户唯一的身份标识)、time(当前时间的时间戳)、sign(签名,可以是token的前几位以哈希算法压缩成的一定长度的十六进制字符串,也可以是token的字符串做md5加密变为32为固定长度唯一的字符串,虽然说md5加密也不安全,皮)
(就像是我们刚开始写程序的时候,变量名、方法名的命令一样,虽然理论上可以自定义,但是为了方便交流和后期维护,会遵循一些既定的准则。)
四、同域SSO原理分析(即一个系统中SSO原理分析,图略,见第三部分单点登录业务流程图)
引子问题:什么是域?(如何理解计算机网络中的域)
计算机网络中的域是应用层DNS系统中的一个概念,因为ip地址不方便记忆,所以通过域名系统,将ip地址映射为易于用户理解的域名,一个域名从右到左分为顶级域名(又称一级域名)、二级域名、三级域名等。关于域,可以简单的理解为某一级域名就是一个域(虽然不权威),把某一级域名当做一个域,可以方便下面理解同域和跨域。
什么是同域和跨域?且看下表:
URL(域名,这里只有两级域名) | 明细 | 同域/跨域 | 示例 |
http://application1.company.com/a.html http://application1.company.com/b.html |
同一域名下不同网页 域名相同(一级域名、二级域名均相同)页面不同 |
同域 | 完全同域SSO,域名完全相同 |
http://application1.companycom/test1/a.html http://application1.company.com/test2/b.html |
同一域名不同文件夹下不同网页 域名相同(一级域名、二级域名均相同)子文件夹不相同,页面自然不同 |
同域 | 完全同域SSO,域名完全相同 |
http://application1.company.com/a.html http://application1.company.com:8080/b.html |
同一域名不同端口下不同网页(默认为80端口,这里为80端口和8080端口) 域名相同(一级域名、二级域名均相同)端口号不同,页面自然不同 |
跨域(端口不同) | |
http://application1.company.com/a.html https://application1.company.com/b.html |
同一域名不同协议下不同网页 域名相同(一级域名、二级域名均相同)端口号相同80端口,协议不同(http和https) |
跨域(协议不同) | |
http://application1.company.com/a.html http://183.232.231.172/b.html |
一个为域名,一个为ip | 跨域(认为跨域) | |
http://application1.company.com/a.html http://application2.company.com/b.html |
域名不同(一级域名相同、二级域名不同) | 跨域(域名不同) | 同父域SSO, 二级域名不同 |
http://application1.company.com/a.html http://company.com/b.html |
域名不同(一级域名相同、二级域名不同) 二级域名缺省值由运维负责,一般javaweb项目由服务器nginx配置 |
跨域(域名不同) | 同父域SSO,二级域名不同 |
http://application1.company.com/a.html http://application2.company2.com/b.html |
域名不同(一级域名、二级域名均不同) | 跨域(域名不同) | 跨域SSO, 一级域名不同 |
王道:王道在于,根据同源策略,协议,域名,端口三者必须都相同,其中有一个不同都会产生跨域。
注意:域名分级的说法不唯一,以http://application1.company.com为例
说法一(学术界):完整规范格式 协议://N级域名...三级域名.二级域名.一级域名(如上"协议://三级域名.二级域名.一级域名")
com为一级域名(也称顶级域名)
company为二级域名
application1为三级域名
http为协议名
说法二(工业界):
company.com为一级域名
application1为二级域名
注意:说法一为官方说法,国内计算机网络权威教材《计算机网络 谢希仁著》和国外维基百科中均采用说法一,且说法一有完整的规范格式。但是,国内工业界,我们总是从域名购买(实际上是租用)者的角度对域名分级,比如某公司购买了company.com这个域名,所以从购买者的角度来看company.com就是一级域名,则application1为二级域名。
有时候说qq.com和baidu.com是一级域名,也是这个道理,从腾讯公司和百度公司的角度来讲的,它们把自己购买的域名(qq.com和baidu.com)看作是一级域名,自己开发的子系统(lol.qq.com、game.qq.com)在当做是二级域名。
附:工业界中,不仅仅company.com被认为一级域名,www.company.com也被认为是一级域名,如www.baidu.com、www.qq.com被认为是一级域名。原因:因为www太常用了,大家都这么认了......
总而言之,域名划分有两种方式,学术界是说法一,工业界是说法二,这里大家都是程序员,请允许本文采用说法二。
回到我们的正题,单点登录、
web应用采用client/server架构,http协议进行网络通信,http协议是一种无状态协议(即不记录上一次连接),单个系统的会话由服务端Session进行维持,Session保持会话的原理是通过Cookie把sessionId写入浏览器,每次访问都会自动携带全部Cookie,在服务端读取其中的sessionId进行验证实现会话保持。
token的生成
token的生成包括两部分,
一是服务端生成token,存放在map的key中,value为user对象,即服务端(key,value)=(token具体值,user具体对象);
二是浏览器要生成一个名为token的Cookie,这个Cookie用来存放服务端返回的token的值,即浏览器Cookie=("token","token具体值xxx");
token过期移除
token的移除也是两部分,一是将服务端的token从Map中移除,二是删除浏览器端的名为token的Cookie。
假设某公司官网域名为company.com,该公司的各个应用域名为applicaton1.company.com,application2.company.com,application3.company.com,给公司官网采用单点登录,将登录模块单独拿出来,域名为sso.company.com。
我们只要在sso.company.com登录,application1.company.com、application2.company.com和application3.company.com就也登录了。通过上面的登陆认证机制,我们可以知道,在sso.company.com中登录,实际上要完成两件事,一是
在sso.company.com的服务端的session中记录了登录状态,同时在浏览器端(Browser)的sso.application.com下写入了Cookie。那么我们怎么才能让app1.a.com和app2.a.com登录呢?这里有两个问题:
(1)浏览器端的Cookie是不能跨域:我们Cookie的domain属性是sso.company.com,在给application1.company.com、application2.company.com和application3.company.com发送请求是带不上的。
(2)服务端的session不能共享:sso、application1、application2、application3是不同的应用,它们的session存在自己的应用内,是不共享的。
关于第一问题,浏览器的Cookie不能跨域,如何让其他应用(application1、application2、application3)访问到sso的Cookie?
解决:在sso登录以后,可以将sso的Cookie的域设置为顶域,即.company.com,这样所有子域的系统都可以访问到顶域的Cookie。我们在设置Cookie时,任何应用只能设置顶域和自己的域,不能设置其他的域。
关于第二个问题,服务端的session不能应用间共享?
不同应用间session无法共享:
我们在sso系统登录了,这时再访问app1,Cookie也带到了app1的服务端(Server),app1的服务端怎么找到这个Cookie对应的Session呢?这里就要把各个系统的Session共享,如图所示:
共享Session的解决方案有很多,例如:Spring-Session。
同域下的单点登录就实现了:
其实我们看到,同域下单点登录和传统登录似乎没有什么区别,唯一的区别就是,
同域下单点登录其实就是手写token代替sessionId进行会话认证(传统登录将服务端的JSESSIONID通过Cookie写入浏览器,同域下单点登录将服务端生成的token通过Cookie写入浏览器)。
但是这对用户是不可见,并没有给用户带来任何用户体验的改进,没有贡献于用户便利。
理由是,这还不是真正的单点登录,即单点登录的作用还没有真正发挥出来。且看跨域SSO原理分析。
五、跨域SSO原理分析(即多个系统中SSO原理分析,图略,见第三部分单点登录业务流程图)
当有多个系统时,认证机制的流程如下:
- 提供用户登录界面,供用户进行身份认证
- 用户验证通过后,生成新token
- 将token<->user 对存入全局MAP中供校验
- 将token写入所有域的Cookie中(这一步是关键,是跨域SSO比之同域SSO的根本变化)
- 页面重定向回原始请求URL
当应用有多个并且在不同域(domain)时,Cookie只会作用在当前域下。
王道:王道在于,将token写入所有域的Cookie中才是解决跨域SSO的核心。
六、自己动手写SSO
要写单点登录demo,最少要有三个子系统,SSO系统(用于登录),A系统,B系统,A系统登录后生成了token,B系统就无需登录直接使用了。
三个情况 AB系统完全同域,AB系统二级域名不同、AB系统一级域名不同
附:单点登录、第三方登录、手机验证码登录、账号密码登录四种登录方式区别。
单点登录只有在多系统才能显现它的优势,(单点登录在用户第一次登录成功后,生成token,使用token机制保证多个系统只需一次登录即可,最大程度保证用户体验。)