兄弟萌,单点登录(SSO)这个概念或许大家并不陌生,但我想有些兄弟还是没搞清楚什么叫做单点登录,下面我们先来介绍一下什么是单点登录?
单点登录(SSO):用户的一次登录能够得到其它所有系统的信任,便可在其它所有系统中得到授权而无需再次登录。
这个概念理解起来似懂非懂,下面直接用几张图来让大家理解:
-
登录京东首页,会看到有登录按钮,我们点击它可以登录,登录成功后会返回发起登录请求的这个页面
-
我先不登录,我直接点击一个商品浏览,上方还是有登录按钮
-
如果我们在商品详情页面登录后,切换到首页刷新一下发现也已经登录了
1.单点登录流程
到这里,想必大家了解了什么是单点登录,下面我们说一说单点登录的流程:
在其中一个子系统登录,跳转到登录系统,登录系统完成登录,完成登录后向发起登录的子系统写入一个 cookie,保存用于认证用户是否登录的信息,其它的子系统要能访问到这个 cookie,在其它子系统向服务器发起请求的时候,携带这个 cookie 完成登录。
注意:只有主域名相同,浏览器在访问时才会携带对应的 cookie
大家留意一下刚才访问京东首页和商品详情页面时,它的主域名是:jd.com。
2.实现单点登录
- 在每个子系统上添加一个登录按钮,能够跳转到登录页面,如:在 www.codeshop.com 和 vip.codeshop.com 中 html 首页添加登录按钮
- 在 www.codeshop.com 中点击登录,被拦截器拦截,检查登陆凭证,由于是首次登陆,凭证为空,直接请求服务器
- 输入用户名、密码、验证码登录后,在服务器端生成 cookie,并把这个 cookie 响应给前端,同时将这个 cookie + 用户信息封装成一个登录凭证保存在 MySQL 数据库中
String token = UUID.randomUUID().toString();
Cookie cookie = new Cookie("token",token);
//cookie 要在子系统间互相访问的话,它们的域要相同
cookie.setDomain("codeshop.com");
response.addCookie(cookie);
- www.codeshop.com 子系统登录成功后,在 vip.codeshop.com 子系统刷新页面,它会带着 cookie 去请求服务器,被拦截器拦截到,检查登陆凭证,去 MySQL 中查找是否有这个 cookie,如果数据库中有,直接返回用户信息,无需登录,如果没有拦截器直接放行,页面无变化。
到这里,我们就简单的实现了单点登录,当然,实现单点登录肯定不止 Cookie 这种方式,实际上使用 Cookie,它是保存在客户端上,如果被窃取或用户禁用了 Cookie,那么就失效了 。我们这里来想一下其它实现方式。
3.实现单点登录的其它方案
- 如果项目用户量少且不是分布式项目,追求简单可以使用 Session 实现单点登录,但如果在分布式系统中我们还要考虑解决 Session 共享问题,比较麻烦。
- 考虑使用 Redis + Token 来实现单点登录
- 客户端去请求服务器端
- 服务器端收到请求后,验证用户名和密码,验证成功后,服务器端生成 Token 令牌(简单理解为生成一段不重复的字符串),将 Token 作为 key,user 作为 value 存放在 Redis 中,同时把这个 Token 令牌响应给客户端
- 客户端收到 Token 令牌,使用 localStorage 把它存放在请求头中,客户端每次向服务器发送请求时,都要携带这个 Token
- 服务端拦截器进行 Token 校验(去 Redis 中查找是否有对应的令牌),如果是登录状态就放行,未登录就跳转到登录页面
- 用户注销时,将存放在 Redis 中的令牌删除,同时把前端保存在 localStorage 中的令牌删掉,避免浪费内存
此外,我还想再谈一下跨域的问题,我们要知道浏览器默认情况下无法主动跨域向后端发送 cookie,需要在前端请求时加入配置项 {withCredentials:true}
,当然在本篇文章的代码中我们手动添加了 cookie.setDomain(“codeshop.com”),所以本篇文章中不用考虑前端向后端跨域发送 cookie 的问题。
两个域之间是不是存在跨域问题,主要是根据协议、域名、端口号这三个点进行判断,只要有一个不一样就是跨域。例如:
- 协议不同:http://www.baidu.com 与 https://www.baidu.com
- 域名不同:http://www.baidu.com 与 http://www.google.com
- 端口号不同: http://www.baidu.com:8080 与 http://www.baidu.com:8000