1. 概述

上一次我们聊了一下《使用Redis实现分布式会话》,原理就是使用 客户端Cookie + Redis 的方式来验证用户是否登录。

如果分布式系统中,只是对Tomcat做了负载均衡,或者所有的子系统都在同一个二级域名下,则 客户端Cookie + Redis 的方式是可以支持验证用户是否登录的。

如果分布式系统中包含了不同域名的子系统,之前的 客户端Cookie + Redis 的方式就不支持了,因为二级域名不同,Cookie无法共享。

例如:浏览器在子系统A的二级域名中保存了Cookie,在访问子系统B时,无法从Cookie中拿到数据,因此没有依据判断用户是否登录。

此时我们就需要引入CAS(中央认证服务),让CAS帮助我们实现跨域单点登录。

今天我们就来聊聊这个跨域分布式系统单点登录的实现。

2. 场景说明

子系统A域名:www.a.com

子系统B域名:www.b.com

CAS系统域名:www.cas.com

当子系统A和子系统B都未登录,则客户端请求时需要登录。

当客户端请求子系统A触发了登录,且登录成功,则客户端再请求子系统B时,不需要登录,可直接访问。

3.概念说明

全局票据:一个全局唯一的uuid,存放在域名为 .cas.com 的 Cookie 中,是用户已登录的标识。

临时票据:一个全局唯一的uuid,存放在Redis中,有过期时限,用于验证用户的身份。

4. 跨域单点登录的实现逻辑

4.1 用户访问子系统A

用户访问子系统A的页面,子系统A的页面检查参数中是否包含临时票据,此时是没有的,则什么都不做。

子系统A的页面请求子系统A的后台接口,子系统A后台检查当前系统的Cookie中是否存在用户ID。

因为从未登录过,所以Cookie中是不存在用户ID的,因此子系统A后台返回未登录给客户端。

客户端将当前子系统A页面的 url 作为参数,重定向到CAS系统的登录页面。

4.2 用户在CAS系统登录

重定向到CAS系统的登录页,登录页首先判断客户端在CAS系统的Cookie中,是否存在全局票据。

因为没有登录过,所以在CAS系统的Cookie中不存在全局票据,用户需要填写用户名、密码进行登录。

用户在CAS系统的登录页面,填写用户名、密码后,提交登录。

CAS系统后台,校验用户名、密码,通过后,生成一个全局唯一的 uuid,作为全局票据。

将全局票据存储在域名为 .cas.com 的 Cookie 中。

以全局票据为 key,用户ID 为 value,存到 Redis 中。

以用户ID为 key,用户信息为 value,存到 Redis 中。

再生成一个 uuid 作为临时票据,以临时票据为 key,临时票据为 value,存储到Redis中,5分钟过期。

CAS系统返回临时票据给客户端。

客户端将页面重定向回子系统A的页面,临时票据作为参数携带。

4.3 再次访问子系统A

页面被重定向回子系统A的页面。

子系统A的页面检查参数中是否有临时票据,此时是有的,则子系统A的页面Ajax调用CAS后台的用户身份验证接口,以临时票据作为参数。

CAS用户身份验证接口,以临时票据为key,从redis中获取value,value不为空,则验证成功,然后清空Redis中的临时票据。

从CAS系统的Cookie中得到全局票据,以全局票据为key,从Redis中得到用户ID,返给子系统A的页面。

子系统A的页面拿到用户ID后,存储在域名为 .a.com 的 Cookie 中。

然后继续完成业务接口逻辑。

子系统A再次访问业务接口时,后台检查当前系统的Cookie中是否存在用户ID,此时是存在的。

后台使用用户ID到Redis中获取完整的用户信息,然后完成接口逻辑。

如果Redis中用户信息不存在,则表示用户已注销登录,则返回未登录。

4.4 用户访问子系统B

用户访问子系统B的页面,子系统B的页面检查参数中是否包含临时票据,此时是没有的,则什么都不做。

子系统B的页面调用子系统B的后台接口,子系统B的后台检查当前系统的Cookie中是否存在用户ID,不存在,返回未登录。

重定向到CAS系统的登录页面,子系统B页面的 url 作为参数。

CAS系统后台,判断在CAS系统的Cookie中,是否存在全局票据,此时是存在的,因为使用的是同一个客户端。

CAS系统后台生成一个uuid作为临时票据,以临时票据为 key,临时票据为 value,存储到Redis中,5分钟过期。

CAS系统返回临时票据给客户端。

客户端将页面重定向回子系统B的页面,临时票据作为参数携带。

子系统B的页面检查参数中是否有临时票据,此时是有的,则子系统B的页面Ajax调用CAS后台的用户身份验证接口,以临时票据作为参数。

CAS用户身份验证接口,以临时票据为key,从redis中获取value,value不为空,则验证成功,然后清空Redis中的临时票据。

从CAS系统的Cookie中得到全局票据,以全局票据为key,从Redis中得到用户ID,返给子系统B的页面。

子系统A的页面拿到用户ID后,存储在域名为 .b.com 的 Cookie 中。

子系统B再次访问业务接口时,后台检查当前系统的Cookie中是否存在用户ID,此时是存在的。

后台使用用户ID到Redis中获取完整的用户信息,然后完成接口逻辑。

这样一来,用户无感知的访问了子系统B,不需要再次输入用户名、密码去登录。

4.5 用户注销

用户注销时,首先清空当前子系统中用户ID的Cookie。

然后子系统前端页面Ajax访问CAS系统的用户注销接口。

CAS系统,从Cookie中拿到全局票据,根据全局票据从Redis中得到用户ID。

删除Cookie中的全局票据。

从Redis中删除 key 为 全局票据 的数据。

从Redis中删除 key 为 用户ID 的数据 。

4.6 总结

这里用了一个小技巧,a.com 和 b.com 的 Cookie 无法共享,因此就在一个公共的 cas.com 中去存Cookie,然后再做一次身份校验就可以了。

5. 综述

今天简单聊了一下跨域分布式系统单点登录的实现,希望能对大家的工作有所帮助。

关注追风人聊Java,每天更新Java干货。