共计 6720 个字符,预计需要花费 17 分钟才能阅读完成。
作者:承一个人
原文链接:https://www.cnblogs.com/ywlak…
git 仓库:GitHub:https://github.com/sheefee/si…
一、单零碎登录机制
1、http 无状态协定
web 利用采纳 browser/server 架构,http 作为通信协议。http 是无状态协定,浏览器的每一次申请,服务器会独立解决,不与之前或之后的申请产生关联,这个过程用下图阐明,三次申请 / 响应对之间没有任何分割
但这也同时意味着,任何用户都能通过浏览器拜访服务器资源,如果想爱护服务器的某些资源,必须限度浏览器申请;要限度浏览器申请,必须甄别浏览器申请,响应非法申请,疏忽非法申请;要甄别浏览器申请,必须分明浏览器申请状态。既然 http 协定无状态,那就让服务器和浏览器独特保护一个状态吧!这就是会话机制
2、会话机制
浏览器第一次申请服务器,服务器创立一个会话,并将会话的 id 作为响应的一部分发送给浏览器,浏览器存储会话 id,并在后续第二次和第三次申请中带上会话 id,服务器获得申请中的会话 id 就晓得是不是同一个用户了,这个过程用下图阐明,后续申请与第一次申请产生了关联
服务器在内存中保留会话对象,浏览器怎么保留会话 id 呢?你可能会想到两种形式
- 申请参数
- cookie
将会话 id 作为每一个申请的参数,服务器接管申请天然能解析参数取得会话 id,并借此判断是否来自同一会话,很显著,这种形式不靠谱。那就浏览器本人来保护这个会话 id 吧,每次发送 http 申请时浏览器主动发送会话 id,cookie 机制正好用来做这件事。cookie 是浏览器用来存储大量数据的一种机制,数据以”key/value“模式存储,浏览器发送 http 申请时主动附带 cookie 信息
tomcat 会话机制当然也实现了 cookie,拜访 tomcat 服务器时,浏览器中能够看到一个名为“JSESSIONID”的 cookie,这就是 tomcat 会话机制保护的会话 id,应用了 cookie 的申请响应过程如下图
3、登录状态
有了会话机制,登录状态就好明确了,咱们假如浏览器第一次申请服务器须要输出用户名与明码验证身份,服务器拿到用户名明码去数据库比对,正确的话阐明以后持有这个会话的用户是非法用户,应该将这个会话标记为“已受权”或者“已登录”等等之类的状态,既然是会话的状态,天然要保留在会话对象中,tomcat 在会话对象中设置登录状态如下
HttpSession session = request.getSession();
session.setAttribute("isLogin", true);
用户再次拜访时,tomcat 在会话对象中查看登录状态
HttpSession session = request.getSession();
session.getAttribute("isLogin");
实现了登录状态的浏览器申请服务器模型如下图形容
每次申请受爱护资源时都会查看会话对象中的登录状态,只有 isLogin=true 的会话能力拜访,登录机制因而而实现。
二、多零碎的复杂性
web 零碎早已从长远的单零碎倒退成为现在由多零碎组成的利用群,面对如此泛滥的零碎,用户难道要一个一个登录、而后一个一个登记吗?就像下图形容的这样
web 零碎由单零碎倒退成多零碎组成的利用群,复杂性应该由零碎外部承当,而不是用户。无论 web 零碎外部如许简单,对用户而言,都是一个对立的整体,也就是说,用户拜访 web 零碎的整个利用群与拜访单个零碎一样,登录 / 登记只有一次就够了
尽管单零碎的登录解决方案很完满,但对于多零碎利用群曾经不再实用了,为什么呢?
单零碎登录解决方案的外围是 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 自身不平安。
因而,咱们须要一种全新的登录形式来实现多零碎利用群的登录,这就是单点登录
三、单点登录
什么是单点登录?单点登录全称 Single Sign On(以下简称 SSO),是指在多零碎利用群中登录一个零碎,便可在其余所有零碎中失去受权而无需再次登录,包含单点登录与单点登记两局部
1、登录
相比于单零碎登录,sso 须要一个独立的认证核心,只有认证核心能承受用户的用户名明码等平安信息,其余零碎不提供登录入口,只承受认证核心的间接受权。间接受权通过令牌实现,sso 认证核心验证用户的用户名明码没问题,创立受权令牌,在接下来的跳转过程中,受权令牌作为参数发送给各个子系统,子系统拿到令牌,即失去了受权,能够借此创立部分会话,部分会话登录形式与单零碎的登录形式雷同。这个过程,也就是单点登录的原理,用下图阐明
上面对上图简要形容
- 用户拜访零碎 1 的受爱护资源,零碎 1 发现用户未登录,跳转至 sso 认证核心,并将本人的地址作为参数
- sso 认证核心发现用户未登录,将用户疏导至登录页面
- 用户输出用户名明码提交登录申请
- sso 认证核心校验用户信息,创立用户与 sso 认证核心之间的会话,称为全局会话,同时创立受权令牌
- sso 认证核心带着令牌跳转会最后的申请地址(零碎 1)
- 零碎 1 拿到令牌,去 sso 认证核心校验令牌是否无效
- sso 认证核心校验令牌,返回无效,注册零碎 1
- 零碎 1 应用该令牌创立与用户的会话,称为部分会话,返回受爱护资源
- 用户拜访零碎 2 的受爱护资源
- 零碎 2 发现用户未登录,跳转至 sso 认证核心,并将本人的地址作为参数
- sso 认证核心发现用户已登录,跳转回零碎 2 的地址,并附上令牌
- 零碎 2 拿到令牌,去 sso 认证核心校验令牌是否无效
- sso 认证核心校验令牌,返回无效,注册零碎 2
- 零碎 2 应用该令牌创立与用户的部分会话,返回受爱护资源
用户登录胜利之后,会与 sso 认证核心及各个子系统建设会话,用户与 sso 认证核心建设的会话称为全局会话,用户与各个子系统建设的会话称为部分会话,部分会话建设之后,用户拜访子系统受爱护资源将不再通过 sso 认证核心,全局会话与部分会话有如下束缚关系
- 部分会话存在,全局会话肯定存在
- 全局会话存在,部分会话不肯定存在
- 全局会话销毁,部分会话必须销毁
你能够通过博客园、百度、csdn、淘宝等网站的登录过程加深对单点登录的了解,留神察看登录过程中的跳转 url 与参数
2、登记
单点登录天然也要单点登记,在一个子系统中登记,所有子系统的会话都将被销毁,用上面的图来阐明
sso 认证核心始终监听全局会话的状态,一旦全局会话销毁,监听器将告诉所有注册零碎执行登记操作
上面对上图简要阐明
- 用户向零碎 1 发动登记申请
- 零碎 1 依据用户与零碎 1 建设的会话 id 拿到令牌,向 sso 认证核心发动登记申请
- sso 认证核心校验令牌无效,销毁全局会话,同时取出所有用此令牌注册的零碎地址
- sso 认证核心向所有注册零碎发动登记申请
- 各注册零碎接管 sso 认证核心的登记申请,销毁部分会话
- sso 认证核心疏导用户至登录页面
四、部署图
单点登录波及 sso 认证核心与众子系统,子系统与 sso 认证核心须要通信以替换令牌、校验令牌及发动登记申请,因此子系统必须集成 sso 的客户端,sso 认证核心则是 sso 服务端,整个单点登录过程本质是 sso 客户端与服务端通信的过程,用下图形容
sso 认证核心与 sso 客户端通信形式有多种,这里以简略好用的 httpClient 为例,web service、rpc、restful api 都能够
五、实现
只是简要介绍下基于 java 的实现过程,不提供残缺源码,明确了原理,我置信你们能够本人实现。sso 采纳客户端 / 服务端架构,咱们先看 sso-client 与 sso-server 要实现的性能(上面:sso 认证核心 = sso-server)
sso-client
- 拦挡子系统未登录用户申请,跳转至 sso 认证核心
- 接管并存储 sso 认证核心发送的令牌
- 与 sso-server 通信,校验令牌的有效性
- 建设部分会话
- 拦挡用户登记申请,向 sso 认证核心发送登记申请
- 接管 sso 认证核心收回的登记申请,销毁部分会话
sso-server
- 验证用户的登录信息
- 创立全局会话
- 创立受权令牌
- 与 sso-client 通信发送令牌
- 校验 sso-client 令牌有效性
- 零碎注册
- 接管 sso-client 登记申请,登记所有会话
接下来,咱们依照原理来一步步实现 sso 吧!
1、sso-client 拦挡未登录申请
java 拦挡申请的形式有 servlet、filter、listener 三种形式,咱们采纳 filter。在 sso-client 中新建 LoginFilter.java 类并实现 Filter 接口,在 doFilter() 办法中退出对未登录用户的拦挡。
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse res = (HttpServletResponse) response;
HttpSession session = req.getSession();
if (session.getAttribute("isLogin")) {chain.doFilter(request, response);
return;
}
// 跳转至 sso 认证核心
res.sendRedirect("sso-server-url-with-system-url");
}
2、sso-server 拦挡未登录申请
拦挡从 sso-client 跳转至 sso 认证核心的未登录申请,跳转至登录页面,这个过程与 sso-client 齐全一样。
3、sso-server 验证用户登录信息
用户在登录页面输出用户名明码,申请登录,sso 认证核心校验用户信息,校验胜利,将会话状态标记为 “ 已登录 ”
@RequestMapping("/login")
public String login(String username, String password, HttpServletRequest req) {this.checkLoginInfo(username, password);
req.getSession().setAttribute("isLogin", true);
return "success";
}
4、sso-server 创立受权令牌
受权令牌是一串随机字符,以什么样的形式生成都没有关系,只有不反复、不易伪造即可,上面是一个例子
String token = UUID.randomUUID().toString();
5、sso-client 获得令牌并校验
sso 认证核心登录后,跳转回子系统并附上令牌,子系统(sso-client)获得令牌,而后去 sso 认证核心校验,在 LoginFilter.java 的 doFilter() 中增加几行
// 申请附带 token 参数
String token = req.getParameter("token");
if (token != null) {
// 去 sso 认证核心校验 token
boolean verifyResult = this.verify("sso-server-verify-url", token);
if (!verifyResult) {res.sendRedirect("sso-server-url");
return;
}
chain.doFilter(request, response);
}
verify() 办法应用 httpClient 实现,这里仅简略介绍,httpClient 具体应用办法请参考官网文档
HttpPost httpPost = new HttpPost("sso-server-verify-url-with-token");
HttpResponse httpResponse = httpClient.execute(httpPost);
6、sso-server 接管并解决校验令牌申请
用户在 sso 认证核心登录胜利后,sso-server 创立受权令牌并存储该令牌,所以,sso-server 对令牌的校验就是去查找这个令牌是否存在以及是否过期,令牌校验胜利后 sso-server 将发送校验申请的零碎注册到 sso 认证核心(就是存储起来的意思)
令牌与注册零碎地址通常存储在 key-value 数据库(如 redis)中,redis 能够为 key 设置无效工夫也就是令牌的有效期。redis 运行在内存中,速度十分快,正好 sso-server 不须要长久化任何数据。
令牌与注册零碎地址能够用下图形容的构造存储在 redis 中,可能你会问,为什么要存储这些零碎的地址?如果不存储,登记的时候就麻烦了,用户向 sso 认证核心提交登记申请,sso 认证核心登记全局会话,但不晓得哪些零碎用此全局会话建设了本人的部分会话,也不晓得要向哪些子系统发送登记申请登记部分会话
7、sso-client 校验令牌胜利创立部分会话
令牌校验胜利后,sso-client 将以后部分会话标记为 “ 已登录 ”,批改 LoginFilter.java,增加几行
if (verifyResult) {session.setAttribute("isLogin", true);
}
sso-client 还需将以后会话 id 与令牌绑定,示意这个会话的登录状态与令牌相干,此关系能够用 java 的 hashmap 保留,保留的数据用来解决 sso 认证核心发来的登记申请
8、登记过程
用户向子系统发送带有 “logout” 参数的申请(登记申请),sso-client 拦截器拦挡该申请,向 sso 认证核心发动登记申请
String logout = req.getParameter("logout");
if (logout != null) {this.ssoServer.logout(token);
}
sso 认证核心也用同样的形式辨认出 sso-client 的申请是登记申请(带有 “logout” 参数),sso 认证核心登记全局会话
@RequestMapping("/logout")
public String logout(HttpServletRequest req) {HttpSession session = req.getSession();
if (session != null) {session.invalidate();// 触发 LogoutListener
}
return "redirect:/";
}
sso 认证核心有一个全局会话的监听器,一旦全局会话登记,将告诉所有注册零碎登记
public class LogoutListener implements HttpSessionListener {
@Override
public void sessionCreated(HttpSessionEvent event) {}
@Override
public void sessionDestroyed(HttpSessionEvent event) {// 通过 httpClient 向所有注册零碎发送登记申请}
}
(完)
本文由博客一文多发平台 OpenWrite 公布!