关于java:我去原来单点登录这么简单这下糗大了

11次阅读

共计 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 呢?你可能会想到两种形式

  1. 申请参数
  2. 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 的受爱护资源,零碎 1 发现用户未登录,跳转至 sso 认证核心,并将本人的地址作为参数
  2. sso 认证核心发现用户未登录,将用户疏导至登录页面
  3. 用户输出用户名明码提交登录申请
  4. sso 认证核心校验用户信息,创立用户与 sso 认证核心之间的会话,称为全局会话,同时创立受权令牌
  5. sso 认证核心带着令牌跳转会最后的申请地址(零碎 1)
  6. 零碎 1 拿到令牌,去 sso 认证核心校验令牌是否无效
  7. sso 认证核心校验令牌,返回无效,注册零碎 1
  8. 零碎 1 应用该令牌创立与用户的会话,称为部分会话,返回受爱护资源
  9. 用户拜访零碎 2 的受爱护资源
  10. 零碎 2 发现用户未登录,跳转至 sso 认证核心,并将本人的地址作为参数
  11. sso 认证核心发现用户已登录,跳转回零碎 2 的地址,并附上令牌
  12. 零碎 2 拿到令牌,去 sso 认证核心校验令牌是否无效
  13. sso 认证核心校验令牌,返回无效,注册零碎 2
  14. 零碎 2 应用该令牌创立与用户的部分会话,返回受爱护资源

用户登录胜利之后,会与 sso 认证核心及各个子系统建设会话,用户与 sso 认证核心建设的会话称为全局会话,用户与各个子系统建设的会话称为部分会话,部分会话建设之后,用户拜访子系统受爱护资源将不再通过 sso 认证核心,全局会话与部分会话有如下束缚关系

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

你能够通过博客园、百度、csdn、淘宝等网站的登录过程加深对单点登录的了解,留神察看登录过程中的跳转 url 与参数

2、登记

单点登录天然也要单点登记,在一个子系统中登记,所有子系统的会话都将被销毁,用上面的图来阐明

sso 认证核心始终监听全局会话的状态,一旦全局会话销毁,监听器将告诉所有注册零碎执行登记操作

上面对上图简要阐明

  1. 用户向零碎 1 发动登记申请
  2. 零碎 1 依据用户与零碎 1 建设的会话 id 拿到令牌,向 sso 认证核心发动登记申请
  3. sso 认证核心校验令牌无效,销毁全局会话,同时取出所有用此令牌注册的零碎地址
  4. sso 认证核心向所有注册零碎发动登记申请
  5. 各注册零碎接管 sso 认证核心的登记申请,销毁部分会话
  6. 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

  1. 拦挡子系统未登录用户申请,跳转至 sso 认证核心
  2. 接管并存储 sso 认证核心发送的令牌
  3. 与 sso-server 通信,校验令牌的有效性
  4. 建设部分会话
  5. 拦挡用户登记申请,向 sso 认证核心发送登记申请
  6. 接管 sso 认证核心收回的登记申请,销毁部分会话

sso-server

  1. 验证用户的登录信息
  2. 创立全局会话
  3. 创立受权令牌
  4. 与 sso-client 通信发送令牌
  5. 校验 sso-client 令牌有效性
  6. 零碎注册
  7. 接管 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 公布!

正文完
 0