前言实现方式一:父域Cookie实现方式二:认证中心实现方式三:LocalStorage跨域补充:域名分类前言在B/S系统中,登录功能通常基于关于Cookie的实现。当用户登录成功后,一般会在Session中记录登录状态,或者给用户颁发一个Token。无论哪种方式,都需要在客户端保存一些信息(SessionID或Token),并且要求客户端在后续的每次请求登录时携带它们。在这样的场景下,使用cookie无疑是最方便的,所以我们一般会在cookie中保存SessionID或者Token,当服务端收到请求时,会通过验证其中的信息来验证用户是否登录曲奇饼。单点登录(SingleSignOn,SSO)是指在同一账户平台下的多个应用系统中,用户只需登录一次,即可访问所有相互信任的应用系统。例如,百度贴吧和百度地图是百度公司旗下的两个不同的应用系统。如果用户登录百度贴吧后访问百度地图不需要重新登录,那么百度贴吧和百度地图之间就存在差距。实现单点登录。单点登录的本质是在多个应用系统中共享登录状态。如果Session中记录了用户的登录状态,为了共享登录状态,必须先共享Session。比如可以将Session序列化到Redis中,让多个应用系统共享同一个Redis,直接读取Redis获取Session。当然,仅仅这样是不够的,因为不同的应用系统有不同的域名。Session虽然是共享的,但是由于SessionID经常保存在浏览器cookie中,所以有范围限制,不能跨域名传递,也就是说,用户登陆app1.com后,SessionID会只会在浏览器访问app1.com时在请求头中自动携带,浏览器访问app2.com时不会携带SessionID。实现单点登录的关键是如何在多个域中共享SessionID(或Token)。实现方式一:ParentDomainCookie在实现之前,先说一下Cookie的作用域。cookie的范围由domain属性和path属性共同决定。domain属性的有效值为当前域或其父域的域名/IP地址。在Tomcat中,domain属性默认为当前域的域名/IP地址。path属性的有效值是以“/”开头的路径。在Tomcat中,路径属性默认为当前Web应用程序的上下文路径。如果cookie的域属性设置为当前域的父域,则该cookie被视为父域cookie。Cookies有一个特点,就是父域的cookies是子域共享的,也就是说,子域会自动继承父域的cookies。利用cookies的这个特性,不难想象在父域中保存SessionID(或Token)是不够的。没错,我们只需要将cookie的domain属性设置为父域的域名(主域名),将cookie的path属性设置为根路径即可,这样所有的子域应用都可以访问到这个cookie.但是,这就要求应用系统的域名必须建立在一个共同的主域名下,比如tieba.baidu.com和map.baidu.com,两者都是建立在主域名baidu.com下,所以他们可以通过这种方式来实现单点登录。总结:这种实现方式比较简单,但是不支持跨主域名。实现方式二:认证中心我们可以部署一个认证中心,它是一个独立的web服务,负责处理登录请求。用户在认证中心统一登录。登录成功后,认证中心记录用户的登录状态,并将Token写入cookie中。(注意这个cookie是属于认证中心的,应用系统无法访问。)应用系统检查当前请求中是否有Token。如果没有,说明用户没有在当前系统登录,页面会被重定向到认证中心。由于该操作会自动带入认证中心的cookie,认证中心可以根据cookie知道用户是否登录。如果认证中心发现用户未登录,则返回登录页面等待用户登录,如果发现用户已经登录,则不允许用户再次登录,但会跳回目标URL并生成跳转前的URL。Token拼接在目标URL后面,回传给目标应用系统。应用系统拿到Token后,需要向认证中心确认Token的合法性,防止用户伪造。确认无误后,应用系统记录用户的登录状态,将Token写入cookie中,然后允许本次访问。(注意这个cookie是属于当前应用系统的,不能被其他应用系统访问。)当用户再次访问当前应用系统时,会自动带上这个Token。应用系统对Token进行验证,发现用户已经登录,所以不会去认证机构那里发生什么。这里顺便介绍两个认证中心的开源实现:ApereoCAS是一个企业级的单点登录系统,其中CAS是“CentralAuthenticationService”的意思。原本是耶鲁大学实验室的一个项目,后来转给了JASIG组织,项目更名为JASIGCAS,后来该组织并入ApereoFoundation,项目更名为ApereoCAS。XXL-SSO是由大众点评工程师徐学礼开发的一款简单的单点登录系统。代码比较简单,没有安全控制。所以不建议直接应用到项目中。列在这里仅供参考。总结:这个实现比较复杂,支持跨域,扩展性好。这是单点登录的标准做法。实现方式三:LocalStorage跨域上一节我们说了实现单点登录的关键是如何在多个域中共享SessionID(或Token)。父域cookie确实是一个很好的解决方案,但是不支持跨域支持。那么有没有什么奇葩的技巧可以让cookies跨域传递呢?不幸的是,浏览器越来越多地限制跨域cookie。Chrome浏览器还为cookie添加了一个SameSite属性,禁止几乎所有的跨域请求cookie传输(超链接除外),只有使用HTTPs协议时,才可能允许在AJAX跨域请求中接受来自服务器。但是在前后端分离的情况下,根本就没有必要使用cookies。我们可以选择将SessionID(或Token)保存在浏览器的LocalStorage中,这样前端会主动保存LocalStorage的数据传递给服务端。这些都是由前端控制的,后端需要做的就是在用户登录成功后,将响应体中的SessionID(或Token)传递给前端。这样的场景下,完全可以在前端实现单点登录。前端拿到SessionID(或Token)后,除了写入自己的LocalStorage外,还可以通过特殊手段写入其他多个域下的LocalStorage。关键代码如下://获取tokenvartoken=result.data.token;//动态创建一个不可见的iframe,并在iframe中加载一个跨域的HTMLvariframe=document.createElement("iframe");iframe.src="http://app1.com/localstorage.html";document.body.append(iframe);//使用postMessage()方法将token传给iframesetTimeout(function(){iframe.contentWindow.postMessage(token,"http://app1.com");},4000);setTimeout(function(){iframe.remove();},6000);//在这个iframe加载的HTML中绑定一个事件监听器,当事件触发,将收到的token数据写入localStoragewindow.addEventListener('message',function(event){localStorage.setItem('token',event.data)},false);前端使用iframe+postMessage()方法将同一份Token写入多个域下的LocalStorage中。在向后端发送请求之前,前端会主动从LocalStorage中读取Token并携带在请求中,这样同一个Token就可以被多个域使用了。共享。总结:本实现完全由前端控制,几乎不需要后端的参与,同时也支持跨域。补充:从专业的角度(根据《计算机网络》的定义),.com和.cn是一级域名(又称顶级域名),.com.cn和baidu.com是二级域名,新浪.com.cn,贴吧.baidu.com为三级域名,以此类推,N级域名为N-1级域名的直接子域名。从用户的角度来说,可以支持独立备案的主域名一般称为一级域名,如baidu.com、sina.com.cn可以称为一级域名,而直接在主域名下建立的子域名称为二级域名,比如tieba.baidu.com就是二级域名。为了避免歧义,我将使用“一级域名”而不是“一级域名”。