用户登录系统的探索和深入--从单实例登录到单点登录

随着网站的逐渐变大,登录系统的设计也不尽相同

背景

系统是为用户服务的,用户在系统上登录之后可以完成签到、评论、分享等与用户绑定的操作。

当系统很小时,只有一个实例即一个Tomcat为用户提供服务,这时的登录系统的设计十分简单。

随着用户的增多,就不可能只有一个主机或一个Tomcat实例提供服务,而是需要多个主机分布式地提供服务,这时登录系统的设计就开始慢慢的复杂起来。

本文就从单tomcat实例→多tomcat实例→单点登录来进行探索和深入。

单实例时的登录方式

系统架构

当只有一个Tomcat实例提供服务时,对象就只有三个:用户、系统和数据库,http请求直接访问tomcat,tomcat又直接访问数据库就能实现所有业务了。

时序图

只有一个Tomcat实例时实现登录十分简单:用户登录之后将用户信息存放到Session中,当下次访问时直接从Session中取出用户是否登录,如果没有登录则跳转到登录页面,如果已登录就返回对应的页面。

代码实现

代码实现方面,需要实现一个拦截器拦截页面请求,每次请求都先从Session取出用户的登录信息判断是否登录。

单实例登录代码:https://github.com/xanderma/sso-single

1
2
3
4
5
6
7
8
9
10
11
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {
HttpSession session = httpServletRequest.getSession();
if (session.getAttribute("username") != null) {
// session存在,已登录
return true;
} else {
// session不存在,跳转到登录页
httpServletResponse.sendRedirect("/login");
return false;
}
}

多实例时的登录方式

单实例登录的缺点

单tomcat实例登录演示了只有一个Tomcat为用户服务的情况,Tomcat只需要把用户的登录信息存放到Session中即可。

但当网站流量变大时,一个Tomcat已不能足够撑起业务,就需要多台主机来协同处理请求了。

一般网站架构

通常使用nginx作为负载均衡入口,每台主机上运行了一个nginx和两个tomcat,nginx分别反向代理了两个tomcat的8080和8081端口,而向外暴露80端口。

nginx将用户请求加权分在多个Tomcat实例上,也许用户的第一个请求是Tomcat1处理的,而下一个请求就是Tomcat2来处理了。而Tomcat1和Tomcat2的Session是相互独立的,他们分别保存了一份用户登录信息。这就意味着用户在Tomcat1上登录之后,下一个请求时用户又需要重新登录了,这样肯定是不行的。

Session共享架构

当某个http请求进来时,先经过负载均衡服务器(如LVS),分配到某个主机的nginx上,然后nginx负载均衡将请求下发到反向代理的tomcat上,所以这个请求可能会被发送到任意一个tomcat来处理。如果此时还是采用单实例的登录方式,即将登录信息保存到Session中是不可行的。除非这些Tomcat的Session可以共享。

实现Tomcat Session共享的方式有很多种,比如修改Tomcat配置就可以共享Session,但这里演示另外一种更通用的方式:使用Spring-Session在Redis中共享Session

这里引入了一个Redis集群,每个Tomcat都连接这个Redis集群。所以Session也不再是保存在内存中,而是统一放在这个Redis集群中被所有Tomcat共享。这时多每个Tomcat就使用同一Session容器,最后的实现就变得和单实例登录基本一致了。

代码实现

使用Sping-Session实现Session共享的代码十分简单,只需要在单实例代码的基础上引入Spring-Session,配置Redis信息即可。

多实例登录代码实现:https://github.com/xanderma/sso-mult

单点登录

当网站做的更大时,例如豆瓣网站,发展出了豆瓣电影、豆瓣读书、豆瓣音乐、豆瓣小组……如果上豆瓣要登录豆瓣电影、豆瓣读书、豆瓣音乐……每个网站都需要登录,那这个用户体验是十分差的。而上文中的多实例登录只是针对豆瓣电影这一个网站多主机的情况,并不能解决这种多网站共享登录的情况。

而单点登录就很好地解决了多网站共享登录的情况,只要登录了豆瓣电影,就不用重复登录豆瓣读书豆瓣音乐了,岂不美哉。

CAS基础协议

CAS(Central Authentication Service) 是 Yale 大学发起的一个开源项目,是搭建SSO系统中比较常用的一种。它的大体流程如下:

1、用户首先试图访问应用程序(如豆瓣主页),如果本地不存在保存着ticket的cookie,则重定向到单点登录服务器的登录页面。

2、用户输入登录信息之后通过单点登录服务器验证登录信息是否正确。如果登录信息正确,则创建一个ticket保存到单点登录服务器中,然后重定向返回到应用程序页面,并附带刚才产生的ticket。而应用程序将这个ticket保存到cookie中,以便下次验证登录信息。

3、当访问应用程序的其他页面时(如豆瓣电影),首先从cookie中取出ticket,然后通过单点登录服务器验证用户是否有效。这样就实现了登录共享。

单点登录架构

网站架构图如下图所示,多个应用程序都和单点登录集群相连,而刚才所说的ticket即用户验证信息则统一存放到Redis集群中。

在abc.com页面登录之后,ticket存放到了cookie中,而cookie又属于abc.com这个域,所以a.abc.com这个子域也能读取到abc.com保存的cookie信息,从中拿到ticket而去单点登录集群进行验证,最终从Redis中读取到用户信息返回。所以abc.com页面登录之后,a.abc.com这个子域也不需要重复登录了。

代码实现

代码实现方面,这里也给出了一个比较完整的demo:https://github.com/xanderma/sso

三种登录架构的对比

总结

在网站很小的时候,例如课程作业,使用一个tomcat,将用户登录信息保存到Session中即可实现用户的登录需要,

而当网站慢慢变大,一个tomcat实例无法应对用户大量的访问需求时,就需要多个主机或tomcat实例来分担流量,这时就可以使用Session共享来同步Session以共享用户登录信息。

网站继续变大,不再是一个主站点,而是有多个分站点的时候,用户不可能在主站点登录之后还要去分站点再次登录,这时候就需要单点登录来解决问题。而CAS协议是实现单点登录的较好的方式,它通过一个专门的登录服务集群来管理用户的登录。

不管是单实例登录、多实例登录还是单点登录,都需要根据场景来合理使用,例如完成一个课程作业就没必要使用单点登录功能了。