一、单系统登录机制1、http无状态协议Web应用采用浏览器/服务器架构,使用http作为通信协议。http是无状态协议。来自浏览器的每个请求都将由服务器独立处理,不会与之前或之后的请求关联。这个过程如下图所示。三个请求/响应对之间没有任何联系,但也意味着任何用户都可以通过浏览器访问服务器资源。如果要保护服务器的某些资源,就必须限制浏览器的请求;限制浏览器请求,必须识别浏览器请求,响应合法请求,忽略非法请求;浏览器请求,你必须知道浏览器请求状态。既然http协议是无状态的,那就让服务器和浏览器一起维护一个状态吧!这就是session机制2、session机制浏览器第一次请求服务器,服务器创建一个session,并将sessionid作为响应的一部分发送给浏览器,浏览器存储sessionid,并在后续的second和second三个请求中都带上sessionid,服务端拿到请求中的sessionid后就知道是不是同一个用户了。这个过程如下图所示。后续请求与第一个请求相关联,服务器将会话对象保存在内存中。,浏览器如何保存sessionid?你可能会想到两种方法。请求参数cookie使用sessionid作为每次请求的参数。服务端在收到请求的时候自然可以解析参数得到sessionid,用来判断是否来自同一个session。显然,这种方法并不可靠。然后浏览器自己维护sessionid。每次发送http请求浏览器都会自动发送sessionid,cookie机制就是用来做这个的。Cookie是浏览器用来存储少量数据的一种机制。数据以“键/值”的形式存储。浏览器在发送http请求时,会自动附加cookie信息。tomcatsession机制也实现了cookies。访问tomcat服务器时,浏览器可以看到一个名为“JSESSIONID”的cookie,这是tomcat会话机制维护的sessionid。使用cookie的请求响应流程如图3所示。登录状态有session机制,登录状态简单易懂。假设浏览器第一次请求服务器时,需要输入用户名和密码来验证身份。服务器获取用户名和密码与数据库进行比较。"或者"登录"之类的,既然是session的状态,自然是存储在session对象中的。Tomcat在session对象中设置登录状态如下12HttpSessionsession=request.getSession();session.setAttribute("isLogin",true);当用户再次访问时,tomcat检查session对象中的登录状态12HttpSessionsession=request.getSession();session.getAttribute("isLogin");实现的浏览器登录状态请求服务器模型如下图所示,每次请求受保护的资源时,都会检查session对象中的登录状态,只有isLogin=true的session才能访问,因此登录机制并实现多系统的复杂性。Web系统已经从长期存在的单一系统发展为由多个系统组成的应用群。面对这么多系统,用户是不是要一个一个登录,然后一个一个注销?如下图所示,Web系统已经从单一系统发展为由多个系统组成的应用群。复杂度应该由系统自己来承担,而不是用户。无论多么复杂的Web系统,对于用户来说都是一个统一的整体。也就是说,用户访问web系统的整个应用组与访问单个系统是一样的。仅一次登录/注销就足够了。虽然单系统登录的解决方案很完美,但是对于多系统的应用群体就不再适用了,为什么呢?单系统登录解决方案的核心是cookie,它携带sessionid来维护浏览器和服务器之间的会话状态。但对cookie有限制。这个限制就是cookie的域(通常对应网站的域名)。浏览器发送http请求时,会自动携带匹配域的cookie,而不是所有的cookie。网络中所有子系统的域名统一在一个***域名下,如“*.baidu.com”,然后将它们的cookie域设置为“baidu.com”。这种方法在理论上是可行的,即使在早期也是如此。多系统登录就是采用这种同域名共享cookie的方式。但是,可行不代表好,分享cookie的方式也有很多局限性。首先,应用组的域名要统一;其次,应用组各系统使用的技术(至少是web服务器)必须相同,否则cookie的key值(tomcat为JSESSIONID)不同,无法维持session。语言技术平台登录,如java、php、.net系统;第三,cookie本身并不安全。因此,我们需要一种全新的登录方式来实现多系统应用组的登录,这就是单点登录3.单点登录什么是单点登录?单点登录的全称是SingleSignOn(以下简称SSO),意思是在多系统应用组中登录一个系统后,无需登录即可在所有其他系统中获得授权再次,包括单点登录和单点注销两部分1、登录相对于单系统登录,sso需要独立的认证中心。只有认证中心才能接受用户的用户名、密码等安全信息。其他系统不提供登录入口,只接受认证中心的间接授权。间接授权是通过令牌实现的。sso认证中心验证用户的用户名和密码没有问题,并创建一个授权令牌。在接下来的跳转过程中,将授权令牌作为参数发送给各个子系统,子系统获得令牌,即已授权,您可以使用它来创建本地会话。本地session的登录方式与单系统相同。这个过程,也就是单点登录的原理,如下图所示。下面简单介绍一下用户访问系统1的受保护资源,当系统1发现用户没有登录后,跳转到sso认证中心,发送自己的地址作为参数,sso认证中心发现用户未登录,引导用户到登录页面。用户输入用户名和密码提交登录申请。sso认证中心验证用户信息,并在用户和sso认证中心之间建立一个会话,称为全局会话。授权tokensso认证中心拿token跳转到初始请求地址(系统1)系统1拿到token,去sso认证中心验证token是否有效,sso认证中心验证token,返回valid,并注册系统1系统1使用此令牌创建与用户的会话,称为本地会话,并返回受保护的资源。用户访问系统2的受保护资源,系统2发现用户未登录,跳转到SSO认证中心,并发送自己的地址作为参数,sso认证中心发现用户已登录,跳转回到系统2的地址,并附上令牌。系统2拿到token,去sso认证中心验证token是否有效。sso认证中心验证token,返回valid。,注册系统2系统2使用此令牌创建与用户的本地会话,并返回受保护的资源。用户登录成功后,会与sso认证中心及各个子系统建立会话。用户与sso认证中心之间建立的会话称为全局会话,用户与各个子系统之间建立的会话称为本地会话。本地会话建立后,用户对子系统受保护资源的访问将不再通过sso认证中心。全局会话和本地会话具有以下约束。localsession存在,globalsession一定存在有globalsession,localsession不一定存在globalsession销毁,localsession一定要销毁可以通过博客园登录流程加深对单点登录的理解,百度,csdn,淘宝等网站,注意登录过程中的跳转url和参数2.注销单点登录自然要单点登出。当您在子系统中注销时,所有子系统会话都将被销毁。用下图说明sso认证中心一直在监听全局会话的状态。一旦全局会话被销毁,监听器将通知所有注册的系统执行注销操作。下面简单介绍一下系统1的用户发起注销请求System1根据用户与System1建立的sessionid获取token,向sso认证中心发起注销请求sso认证中心验证token有效,销毁全局session,取出同时用这个token注册的所有系统地址sso认证中心向所有注册系统发起注销请求。各注册系统收到sso认证中心的注销请求,销毁本地会话。sso认证中心引导用户进入登录页面4.部署图单点登录涉及到sso认证中心和多个子系统,子系统需要和sso认证中心通信,交换token,验证token,发起注销请求,所以子系统必须集成sso客户端,sso认证中心为sso服务器。整个单点登录流程的本质就是sso客户端和服务器端通信的过程,用下图来描述sso认证中心和sso客户端之间的通信方式有很多种。这里我们以简单易用的httpClient为例。Web服务、rpc、restfulapi都可以使用。5.基于java的实现过程只是简单介绍,并没有提供完整的源码。如果你明白原理,相信你可以自己实现sso采用客户端/服务器架构。先来看看sso-client和sso-server要实现的功能(下图:sso认证中心=sso-server)。中心接收并存储sso认证中心发送的token与sso-server通信,验证token的合法性,建立部分会话,拦截用户注销请求,向sso认证中心发送注销请求,接收sso认证中心的注销请求,并销毁本地Sessionsso-server验证用户登录信息创建全局session创建授权token与sso-client通信发送token验证sso-clienttoken有效性系统注册接收sso-client注销请求,注销所有会话接下来,我们就按照原理一步步实现sso吧!1、sso-client拦截非登录请求。Java通过三种方式拦截请求:servlet、filter和listener。我们使用过滤器。在sso-client新建一个LoginFilter.java类并实现Filter接口,在doFilter()方法中添加拦截未登录用户123456789101112publicvoiddoFilter(ServletRequestrequest,ServletResponse响应,FilterChain链)抛出IOException,ServletException{HttpServletRequestreq=(HttpServletRequest)request;HttpServletResponseres=(HttpServletResponse)响应;HttpSessionsession=req.getSession();if(session.getAttribute("isLogin")){chain.doFilter(request,response);返回;}//跳转到sso认证中心res.sendRedirect("sso-server-url-with-system-url");}2.sso-server拦截非登录请求,拦截从sso-client跳转到sso认证中心的非登录请求,跳转进入登录页面,流程和sso-client完全一样3.sso-server验证用户登录信息用户在登录页面输入用户名和密码,请求登录,sso认证中心验证用户信息。如果验证成功,则会话状态标记为“已登录”123456@RequestMapping("/login")publicStringlogin(Stringusername,Stringpassword,HttpServletRequestreq){this.checkLoginInfo(username,密码);req.getSession().setAttribute("isLogin",true);返回“成功”;}4.sso-server创建授权令牌。授权令牌是一串随机字符。不管是怎么产生的,只要不重复,不易伪造即可。下面是一个例子1Stringtoken=UUID.randomUUID().toString();5、sso-client获取token并验证sso认证中心登录后,跳回子系统并附加token。子系统(sso-client)获取token,然后去sso认证中心验证,在LoginFilter.java的doFilter()中添加几行1234567891011//请求带有token参数Stringtoken=req.getParameter("token");if(token!=null){//去sso认证中心验证tokenbooleanverifyResult=this.verify("sso-server-verify-url",token);如果(!verifyResult){res.sendRedirect("sso-server-url");返回;}chain.doFilter(请求,响应);}verify()方法是使用httpClient实现的,这里只是简单介绍一下,httpClient的详细用法请参考官方文档12HttpPosthttpPost=newHttpPost("sso-server-verify-url-with-token");HttpResponsehttpResponse=httpClient.execute(httpPost);6.sso-server接收并处理验证令牌请求。用户登录sso认证中心成功后,sso-server会创建一个授权token并保存。所以,sso-server对token的校验就是看这个token是否存在,是否已经过期。token验证成功后,sso-server会将发送验证请求的系统注册到sso认证中心(即存储)。token和注册系统地址通常保存在key-value数据库中(如redis),redis可以为key设置有效期,即token的有效期。Redis运行在内存中,速度很快。碰巧sso-server不需要持久化任何数据。令牌和注册系统地址可以使用下图描述的结构存储在redis中。你可能会问,为什么要存储这些系统的地址呢?如果不保存,注销时会很麻烦。用户向sso认证中心提交注销请求,sso认证中心注销全局会话。系统发送注销请求注销本地会话7.sso-client验证令牌成功。本地会话令牌验证成功后,sso-client将当前本地会话标记为“已登录”。修改LoginFilter.java并添加几行123if(verifyResult){session.setAttribute("isLogin",true);}sso-client还需要将当前sessionid与token绑定,表示本次session的登录状态与token相关。这种关系在java8中可以使用,注销过程中,用户向子系统发送一个带有“logout”参数的请求(logoutrequest),sso-client拦截器拦截该请求,发送给sso认证center发起注销请求1234Stringlogout=req.getParameter("logout");if(logout!=null){this.ssoServer.logout(token);}sso认证中心也是用同样的方法来识别sso-client请求为注销请求(带"logout"参数),sso认证中心注销全局会话12345678@RequestMapping("/logout")publicStringlogout(HttpServletRequestreq){HttpSessionsession=req.getSession();if(session!=null){session.invalidate();//触发LogoutListener}return"redirect:/";}sso认证中心有一个全局会话监听器。一旦全局会话退出,它会通知所有注册的系统退出12345678publicclassLogoutListenerimplementsHttpSessionListener{@OverridepublicvoidsessionCreated(HttpSessionEventevent){}@OverridepublicvoidsessionDestroyed(HttpSessionEventevent){//通过httpClient向所有注册系统发送注销请求}}[责任编辑:求职者TEL:(010)68476606]