单点登录详解



  • 单点登录

    为什么要单点登录

    随着公司的发展,公司内部使用的系统越来越多,这就导致了以下的问题:

    1. 每个系统都需要记住对应的账号和密码,如果有十个系统,意味员工需要记忆十套的账号密码,要记忆这些太痛苦了。所以很多员工的会把每个系统的账号密码都设置成一样的。但是这样又会存在安全上的问题。
    2. 公司需要维护的系统数量庞大,给管理带来很大压力。
    3. 用户如果不使用了,还需要分别在多个系统中依次的注销。

    为了解决这样一些问题,就产生了单点登录的机制。

    什么是单点登录

    总的来说,单点登录就是在多个应用系统中,只需要让用户登录一次就可以访问所有相互信任的应用系统的机制。

    单点登录的运行原理

    单点登录需要一个独立的认证中心,只有认证中心能接受用户的用户名密码等安全信息,其他系统不提供登录入口,只接受认证中心的间接授权。间接授权通过令牌实现,认证中心验证用户的用户名密码没问题,创建授权令牌,在接下来的跳转过程中,授权令牌作为参数发送给各个子系统,子系统拿到令牌,即得到了授权,可以借此创建局部会话,局部会话登录方式与单系统的登录方式相同。这个过程,也就是单点登录的原理。见下图:

    avatar

    在学习具体的单点登录原理之前,我们回顾一下会话和登录机制:

    会话机制

    Web 环境中的会话指一个浏览器与交互系统进行通信的时间间隔,通常指从注册进入系统到注销退出系统之间所经过的时间,浏览器和服务器之间的会话是通过本地的 cookie 来实现的,通过下图加深理解:

    avatar

    1. 浏览器在第一次访问 Tomcat 服务器的时候,Tomcat 服务器会在服务端创建 session 对象,并存储到 map 中。key 是 session 的id,value 是 session 对象本身。
    2. 在响应的时候会把 session 的 id 通过 cookie 的方式写到客户端浏览器中。
    3. 浏览器会在本地的目录中把 session 的 id 写入到本地的 cookie 中。
    4. 在后续的请求中,都会读取本地的 cookie 中的内容,并在请求的时候带上对应的 cookie。

    avatar

    我们可以做这样的一个类比:

    每一个用户到服务器请求数据都会被要求办一张 ID 卡,这张 ID 卡仅仅携带了标记用户的 ID,这就是 cookie;而服务器会在后台记录下办了卡的 ID 和它对应的状态,这就是 session。cookie 和 session 的组合就是为了解决 http 协议无状态的缺陷。然而有些资源不是一般人就可以获得的,这就需要用户把 ID 卡升级成会员卡,就是下面要说的登录机制。

    登录机制

    有了会话机制,登录状态就好明白了,我们假设浏览器第一次请求服务器需要输入用户名与密码验证身份,服务器拿到用户名密码去数据库比对,正确的话说明当前持有这个会话的用户是合法用户,应该将这个会话标记为“已授权”或者“已登录”等等之类的状态。既然是会话的状态,自然要保存在会话对象中, Tomcat 在会话对象中设置登录状态如下:

    HttpSession session = request.getSession();
    session.setAttribute("isLogin", true);
    

    服务端没有查询到 session ID,则自动为其创建会话及 session ID,并设置会话的 session 对象中登录状态为 true。用户再次访问时,浏览器保存的 cookie 中就带上了刚才服务端创建的 session ID,从而服务端可以根据 session ID 直接查看到用户的登录状态:

    HttpSession session = request.getSession();
    session.getAttribute("isLogin");
    

    每次请求受保护资源时都会检查会话对象中的登录状态,只有 isLogin=true 的会话才能访问,登录机制因此而实现。

    登录机制原理图:

    avatar

    接着上面的类比,就是当我需要获得会员专属的资源时,服务器会要求我先进行登录。当我的用户名和密码认证通过之后,服务器就将 session 中对应我的 ID 的对象添加一条 isLogin=true,表明我已经成为了一名会员。之后我再携带 ID 卡(cookie)访问时,服务器就能直接查出我的 ID 卡是办理过会员的,就会直接把资源发送给我而不会再让我登录了。不过 cookie 和 session 过了指定时间之后都会失效,所以不是只登录一次就可以永久保持登录状态。

    总结一下登录的要求,总共有三点:

    1. 浏览器带有 cookie;
    2. 服务端能够根据 cookie 的 session ID 得到已经创建的 session;
    3. session 对象中 isLogin 为 true;

    多系统应用群的限制

    虽然单系统的登录解决方案很完美,但对于多系统应用群已经不再适用了,为什么呢?

    单系统登录解决方案的核心是 cookie,cookie 携带会话 id 在浏览器与服务器之间维护会话状态。但 cookie 是有限制的,这个限制就是 cookie 的域(通常对应网站的域名),浏览器发送 http 请求时会自动携带与该域匹配的 cookie,而不是所有 cookie.

    既然这样,为什么不将 web 应用群中所有子系统的域名统一在一个顶级域名下,例如 “*.baidu.com”,然后将它们的 cookie 域设置为“baidu.com”。这种做法理论上是可以的,甚至早期很多多系统登录就采用这种同域名共享 cookie 的方式。

    然而,可行并不代表好,共享 cookie 的方式存在众多局限。首先,应用群域名得统一;其次,应用群各系统使用的技术(至少是 web 服务器)要相同,不然 cookie 的 key 值(tomcat 为 JSESSIONID)不同,无法维持会话,共享 cookie 的方式是无法实现跨语言技术平台登录的,比如 java、php、.net 系统之间;第三,cookie 本身不安全。

    因此,我们需要一种全新的登录方式来实现多系统应用群的登录,这就是单点登录。

    单点登录详解

    登录过程

    对照之前说到的单点登录的流程,我们画出这样的原理图:

    avatar

    步骤分析:

    1. 用户通过浏览器访问 WMS 系统的受保护资源,访问地址为:"http://www.wms.com/index",该资源为受保护资源,所以需要先判断一下用户登陆了(是否有局部会话)。这个时候浏览器还不带有该域的 cookie,服务器端也没有创建 session。
    2. 由于用户端没有 WMS 对应的 cookie,WMS 系统发现用户并没有登陆,此时重定向到统一认证中心,并把请求地址作为参数传过去。
    3. 此时浏览器发出一个请求查看统一认证中心是否已经登录了,同样发现用户并没有登录,转发到统一认证中心的登陆页面。
    4. 用户输入账号密码。
    5. 统一认证中心认证用户信息。如果认证成功,创建浏览器与统一认证中心之间的会话,称为全局会话。即服务端创建 session,浏览器收到来自认证中心的 cookie。同时创建授权令牌。重定向到WMS之前请求的地址,并把令牌信息(查询符号后内容)"http://www.wms.com/index?token=4KLdkEo9k7CXfle4"带上。
    6. WMS 拿到令牌后,需要到统一认证中心检验令牌是否有效。
    7. 统一认证中心认证令牌有效,返回有效,并注册 WMS 的系统地址。
    8. WMS 得到统一认证中心的响应,知道令牌是有效的,创建局部的会话并放行请求。即 WMS 创建 session,用户端收到 WMS 的 cookie。
    9. WMS 后续的请求访问的时候,发现 WMS 系统中有局部的会话,直接就放行了。
    10. 用户访问 CRM(客户关系管理)系统的受保护资源,访问地址为:http://www.crm.com/index
    11. CRM 系统发现用户并没有登陆(没有局部会话),此时重定向到统一认证中心,并把请求地址作为参数传过去。
    12. 此时浏览器发出一个请求查看统一认证中心是否已经登录了, 发现用户已经登录了,从会话中取出令牌,重定向到 CRM 系统,并把令牌带上。http://www.crm.com/index?token=SrEpDwAQlHLdkJIE
    13. CRM 拿到令牌后,需要到统一认证中心检验令牌是否有效。
    14. 统一认证中心认证令牌有效,返回有效,并注册 CRM 的系统地址。
    15. CRM 得到统一认证中心的响应,知道令牌是有效的,创建局部的会话。并放行请求。
    16. CRM 后续的请求访问的时候,发现 CRM 系统中有局部的会话,直接就放行了。

    用户登录成功之后,浏览器会与统一认证中心及各个子系统建立会话,浏览器与统一认证中心建立的会话称为全局会话,浏览器与各个子系统建立的会话称为局部会话,局部会话建立之后,浏览器访问子系统受保护资源将不再通过统一认证中心,全局会话与局部会话有如下约束关系:

    1. 局部会话存在,全局会话一定存在
    2. 全局会话存在,局部会话不一定存在
    3. 全局会话销毁,局部会话必须销毁

    存储原理

    下图中,我们有三个服务器,分别是统一认证中心:www.sso.com、CRM 客户关系管理系统:www.crm.com、WMS 系统:www.wms.com。

    每个系统都有一个区域存储 session 的地方,可以暂时理解就是有个 map 来存储 session 对象,这个 map 的 key 存的是 session 的 id,value 存的是 session 对象。
    在浏览器本地会有三个目录存储对应域名的 cookie。比如:访问 www.crm.com 的时候会在浏览器本地的 crm.com 目录找对应的 cookie,并在请求的时候把这个目录下的 cookie 请求一并的带到服务器(这个动作是浏览器完成的,不需要用户操作),而且 www.crm.com 服务器返回响应 cookie 的时候会写入到浏览器本地的 crm.com 目录。

    目前我们还没发起请求,所以所有的内容都是空的。

    avatar

    第一次访问 "http://www.crm.com/employee",首先会在浏览器本地的 crm.com 目录中寻找是否有对应的 cookie,如果没有说明目前浏览器和服务器之间还没有建立会话,也就是说没有局部的会话。

    这时候我们需要重定向到统一认证中心,查看是否有全局会话(如果有全局会话说明有其他系统已经登录了)。

    需要把请求访问的地址作为参数传递过去,因为待会得回调这个地址。
    具体代码如下:

    String url = "<http://www.sso.com/checkLogin?redirectUrl=http://www.crm.com/employee>";
    response.sendRedirect(url);
    

    avatar

    avatar

    avatar

    用户在统一认证中心的登陆页面输入了账户名和密码之后,统一认证中心服务器对用户信息进行认证。认证通过需要做这几个事情:

    1. 创建令牌,后续操作中得发给子系统,相当于间接授权。
    2. 创建全局会话,并把令牌存储到全局会话中。
    3. 把令牌信息存储到数据库中的 t_token 表中。主要是后续客户端校验 token 的有效性需要查询这种表。
    4. 重定向到之前用户请求的地址 redirectUrl。并把令牌(http://www.crm.com/employee?token=VcnVMguCDWJX5zHa)发给该子系统。
      统一认证中心会把 session 的 id 响应到客户端浏览器本地目录 sso.com 的 cookie 文件中。存储的结构是 key/value 格式。key 是固定的字符串 JSESSIONID,value 是服务器 sessionid 的字符串。
      在后续访问 www.sso.com 的时候都会带上这个 JSESSIONID。

    avatar

    因为重定向,浏览器访问“http://www.crm.com/employee?token=VcnVMguCDWJX5zHa” ,此时浏览器会在本地的 crm.com 把所有的 cookie 一并的带上。
    但是本地的 crm.com 目录中没有内容,说明浏览器还没有和 CRM 系统建立会话,说明没有局部会话。
    但是这次的请求中包含了 token 令牌的信息,
    可 token 是直接拼接在地址栏上的,存在被伪造的可能性。所以我们需要对 token 令牌做有效性的校验。

    avatar

    CRM系统在接受到令牌token信息之后,需要去统一认证中心中校验令牌信息是否有效。
    我们使用 HttpUrlConnection 发送 http 请求 http://www.sso.com/verify?token=VcnVMguCDWJX5zHa
    统一认证中心接受到这个请求后,拿到令牌token对应的值,在数据库表 t_token 中查询是否有这条记录。如果能找到说明这个令牌是统一认证中心发放的,返回 true 给调用者。
    如果找不到,说明不是统一认证中心产生的,我们就该返回 false 给调用者。

    avatar

    CRM 系统发送的校验请求之后,统一认证中心返回 true 的结果。说明令牌是有效的。此时需要做这几件事情:

    1. 创建局部的会话,并且标记这个会话已登录,设置 isLogin=true。
    2. 放行该次的请求。
      服务器会把 session 的 id 响应到客户端,存储在浏览器本地目录 crm.com 目录的 cookie 文件中。在后续访问 www.crm.com 的域名的时候会把该目录下的 cookie 信息一并带上。

    到这一步其实我们单个系统的登陆就已经完成了。

    avatar

    后续的操作就直接看图:

    avatar
    avatar
    avatar
    avatar
    avatar
    avatar
    avatar

    到此即为单点登录的存储原理

    单点注销

    单点登录自然也要单点注销,在一个子系统中注销,所有子系统的会话都将被销毁,用下面的图来说明:

    avatar

    步骤分析:

    1. 用户在CRM系统中点击注销按钮,会重定向到统一认证中心的注销方法。
    2. 统一认证中心接受到注销请求之后,会销毁全局的会话。
    3. 统一认证中心会拿到之前在该系统中注册的子系统集合。
    4. 依次的调用子系统的登出方法,销毁局部会话。
    5. 每个系统中的会话都已经销毁之后,跳转到登陆页面。

    我们在统一认证中心登录成功后,除了需要做这几件事情:

    1. 创建令牌,后续操作中得发给子系统,相当于间接授权。
    2. 创建全局会话,并把令牌存储到全局会话中。
    3. 把令牌信息存储到数据库中的 t_token 表中。主要是后续客户端校验 token 的有效性需要查询这种表。
    4. 重定向到之前用户请求的地址 redirectUrl,并把令牌(http://www.crm.com/employee?token=VcnVMguCDWJX5zHa) 发给该子系统

    还需要创建一张 t_client_info 信息,
    这张表存储的是究竟有哪些子系统在统一认证中心注册了。
    在单点登录的时候拿到注册子系统的集合,依次调用子系统的登出方法。

    avatar

    我们在 CRM 系统给统一认证中心发送一个校验令牌 token 有效性的请求的时候,除了带上令牌 token 信息之外.还需要带上如下信息:

    1. 客户端的登出地址: http://www.crm.com/logout
    2. 客户端的 JSESSIONID(目的是在调用客户端销毁方法的时候能找到对应的会话并销毁),
      所以此时请求的地址为:
      http://www.sso.com/verify?token=VcnVMguCDWJX5zHa&clientUrl=http://www.crm.com/logout&JSESSIONID=F4C441B40D00A03DD8DDEBF751F2BF01

    这里有一个疑问:我在验证 token 之前还没有创建 session,那我怎么把 session 的 id 传给认证中心呢?其实验证已经登录不仅要求存在 session,还要求 session 中 isLogin 的值为 true,所以我们在验证 token 之前先创建一个 session 是完全没有问题的。只要当认证中心返回 token 无效时设置 isLogin 为 false 就可以了。

    avatar

    当两个系统都已经在统一认证中心注册好之后,数据库表 t_token 中存储的信息如下图所示

    avatar

    我们在 CRM 系统中点击注销按钮,这个注销按钮的地址其实指向
    http://www.sso.com/logout, 访问的是统一认证中心的注销方法

    avatar

    浏览器发出请求 http://www.sso.com/logout, 浏览器会根据请求的域名找到本地的 sso.com 目录 cookie,并把 cookie 里面的信息一并带到服务器。服务器通过cookie中的JSESSION可以找到统一认证中心的会话对象。

    avatar

    拿到统一认证中心的会话对象之后

    1. 取出会话中的令牌信息token,通过令牌信息在表 t_clinet_info 中找到之前注册的子系统集合。
    2. 遍历子系统集合,依次调用子系统的登出方法,并把 JSESSIONID 带上。

    avatar

    子系统接受到请求之后,根据 JSESSIONID 找到对应的会话对象进行销毁

    avatar

    需要把令牌信息从数据库中删除

    avatar

    清除全局会话对象

    avatar

    响应统一认证中心的登陆页面

    avatar

    到这里单点登录的内容就全部结束了。第一次写博客还是要认真一点写的。想更深入了解的大佬可以看下面链接:

    参考链接:

    最强SSO单点登录教程(一)多系统的复杂性 https://www.jianshu.com/p/9dc05d51879f

    最强SSO单点登录教程(二)单点登录流程分析 https://www.jianshu.com/p/5cc9457942b5

    最强SSO单点登录教程(三)单点注销流程分析 https://www.jianshu.com/p/23f8e3958460

    拓展内容请看:

    最强SSO单点登录教程(四)自己动手写SSO单点登录服务端和客户端 https://www.jianshu.com/p/79cfab236877

    最强SSO单点登录教程(五)自己动手写SSO单点注销服务端和客户端 https://www.jianshu.com/p/667c8f0b514f

    最强SSO单点登录教程(六)CAS-简介&配置https https://www.jianshu.com/p/75b0c7fe6721

    最强SSO单点登录教程(七)CAS-搭建CAS Server服务端(静态认证) https://www.jianshu.com/p/2bd8f2b1db86

    多点登录:

    多点登录限制,禁止单用户多点在线:https://blog.csdn.net/github_36859217/article/details/79344236



  • 这个markdown开头的索引很nice


 

Copyright © 2018 bbs.dian.org.cn All rights reserved.

与 Dian 的连接断开,我们正在尝试重连,请耐心等待