SpringBoot 简单实现仿CAS单点登录系统

  • 新境界开源开源SSO项目介绍
    • 新境界开源SSO项目实现原理大致如下:
      • 新境界开源SSO项目登录流程介绍
      • 新境界开源SSO项目授权登录流程介绍
      • 新境界开源SSO项目退出流程介绍
  • 自己写单点登录Demo
    • 单点登录Demo介绍
      • 单点登录
      • 单点授权验证
      • 单点退出

新境界开源开源SSO项目介绍

最近看了一下杰哥开源的项目 https://gitee.com/zhanghejie/jeexjj_sso 感觉写的不错.

自己根据项目的思路简单写了一个demo。在介绍demo之前我先简单介绍一下杰哥sso项目的思路。

新境界开源SSO项目实现原理大致如下:

首先要说明的是杰哥 开源 sso项目 jeexjj_sso 是基于 cookie 实现的, 通过 GrantingTicketServiceTicket 来完成一系列的登录操作。

首先说明一下 GrantingTicketServiceTicket 是什么?

  1. GrantingTicket: 来表示用户登录的唯一标识票据。
  2. ServiceTicket:来表示每个 sso客户端应用的信息。如果一个用户登陆了基于sso的三个互相信任的系统。那么对应该用户总共有1个GrantingTicket,3个ServiceTicket

新境界开源SSO项目登录流程介绍

登陆窗口在client端的登陆流程如下:

新境界开源SSO项目授权登录流程介绍

已经登陆sso的用户首次访问某信任系统时的验证流程

新境界开源SSO项目退出流程介绍

单点退出流程

自己写单点登录Demo

单点登录Demo介绍

为了练手自己尝试写了一个Demo,但是实现思路和杰哥的 sso项目大体是一致的。涉及到sso的单点登录一般需要处理3块业务逻辑:

  1. 单点登录
    就是通过sso服务端进行登录的操作。
  2. 单点授权验证
    当在sso服务登录成功后访问别的项目无需在次登录的操作。
  3. 单点退出
    当一个sso客户端退出后其他的sso客户端也需要统一退出的操作。

单点登录

大致原理如下:

  1. 我们sso客户端访问需要登录的url 或者登录的url 直接跳转到sso服务端的登录页面(如果已经登录则直接跳转到访问的url)

    1. 直接访问登录的url,sso客户端的拦截器会将其直接重定向到 sso服务端的登录页面并携带sso客户端访问地址。
    2. 访问sso客户端需要登录的url(没有登录的情况下)会重定向到sso服务端单点授权验证逻辑中。如果该用户未登录则重定向到 sso服务端的登录页面并携带sso客户端访问地址。
  2. 输入用户名和密码在sso服务端进行登录,如果用户名和密码错误则重新跳转到登陆页面并提示错误信息。

  3. 如果用户名和密码正确,则生成 GrantingTicket 并将GrantingTicket 的id 放入cookie中。然后生成 ServiceTicket 并将ServiceTicket 放入GrantingTicket 的缓存map中。

  4. 将生成 GrantingTicket放入到缓存中(key:GrantingTicket的id value:GrantingTicket)然后再将ServiceTicket 的id 和对应的GrantingTicket的id放入缓存中。

  5. 重定向到sso客户端 并将 ServiceTicket 的id(ticket)拼接到重新向的url中。

  6. sso客户端根据ticket 再次调用sso服务端去获取用户信息。

  7. sso服务端接受到sso客户端 传来ticket后, 根据ticket 获取缓存中的 GrantingTicket的 id,然后再根据GrantingTicket的id 获取GrantingTicket。然后再从GrantingTicket获取用户信息返回给 sso客户端。
    8.sso客户端接受到用户信息后将其保存在session中。然后再将ticket 和session的关系存入sso客户端 缓存的map中。

  8. 如果sso客户端根据ticket 获取用户失败则返回sso服务端的登录页面并提示非法操作。

单点授权验证

大致原理如下:

  1. 当我们访问需要登录url时并且用户session信息为null的情况下 则走单点授权验证。
  2. 如果该用户未登录则重定向到 sso服务端的登录页面并携带sso客户端访问地址。后面的就走sso单点登录操作。
  3. 如果该用户已经在别的系统已经登录过。sso客户端拦截器拦截到该操作是单点授权验证后会重定向到sso服务端单点授权url。
    4.sso服务端拦截器拦截到 单点授权url后 首先获取 cookie中 GrantingTicket的id 。然后根据GrantingTicket的id获取缓存中的GrantingTicket。
  4. sso服务端 生成ServiceTicket 并放入到GrantingTicket的缓存中。
  5. sso服务端 重定向到sso客户端并携带ServiceTicket 的id (ticket)。
  6. sso客户端拦截器拦截到重定向url并获取到ticket 。然后根据ticket从sso服务端获取用户的信息。
  7. sso服务端接受到sso客户端ticket 根据ticket 获取缓存中的 GrantingTicket的id,然后再根据GrantingTicket的id 获取GrantingTicket。然后再从GrantingTicket获取用户信息,返回给客户端。
  8. sso客户端接受到用户的信息后 将信息保存在session中
  9. 然后将 ticket 和sesion的关系对应保存在缓存中。

单点退出

  1. sso客户端点击退出按钮,会向sso服务端发起退出登录操作。
  2. sso服务端拦截器拦截到退出的操作后会将进入退出的操作逻辑
  3. sso服务端 第一步先获取sso服务端在存储的GrantingTicket的cookie信息
  4. 根据在cookie中存储的GrantingTicket的id 从缓存中获取到GrantingTicket。
  5. 然后将sso服务端在存储的GrantingTicket的cookie信息清除。
  6. 获取GrantingTicket 缓存的ServiceTicket缓存map信息。
  7. 遍历ServiceTicket的Map信息。
  8. 通过ServiceTicket的host信息依次调用客户端的退出url并携带ticket。
  9. sso客户端拦截器拦截到退出的操作并获取到ticket.根据之前在客户端缓存的map中根据ticket获取到sesion的信息并调用sess.invalidate();
  10. sso服务端重定向到sso服务端的登录页面。

sso客户端拦截器代码:

public class SSOClientFilter implements Filter{private final String SUCESSURL = "/index/index";private final String SSOSERVERLOGINURL = "http://localhost:8080/ssoServer/login/login";private final String SSOSERVERAUTHENTICATIONURL = "http://localhost:8080/ssoServer/sso/authentication";public static ConcurrentHashMap<String,HttpSession> TICKET_SESSION_CACHE = new ConcurrentHashMap<String,HttpSession>();@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,FilterChain filterChain) throws IOException, ServletException {HttpServletRequest request = (HttpServletRequest )servletRequest;HttpServletResponse response = (HttpServletResponse )servletResponse;HttpSession session = request.getSession();String path = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + request.getContextPath();String uri = request.getRequestURI();//被动单点退出String requestLogoutTicket = request.getParameter("requestLogout");if(!StringUtils.isEmpty(requestLogoutTicket)){ssoLogout(requestLogoutTicket);response.sendRedirect(SSOSERVERLOGINURL+"?service="+path);return;}//根据ticket获取用户信息String ticket = request.getParameter("ticket");if(!StringUtils.isEmpty(ticket)){User user = getUserByTicket(ticket);if(user == null){response.sendRedirect(SSOSERVERLOGINURL+"ERRORMSG="+"非法访问");return;}else{session.setAttribute("LOGIN_INFO", user);response.sendRedirect(path+SUCESSURL);TICKET_SESSION_CACHE.put(ticket, session);return;}}User user = (User)session.getAttribute("LOGIN_INFO");if(uri.endsWith("/login")){response.sendRedirect(SSOSERVERLOGINURL+"?service="+path);return;}if(null != user || uri.endsWith("css") || uri.endsWith("js")){filterChain.doFilter(servletRequest, servletResponse);return;}else{response.sendRedirect(SSOSERVERAUTHENTICATIONURL+"?service="+path);return;}}private void ssoLogout(String ticket) {if(TICKET_SESSION_CACHE.containsKey(ticket)){HttpSession sess = TICKET_SESSION_CACHE.get(ticket);if(null != sess){sess.invalidate();}TICKET_SESSION_CACHE.remove(ticket);}}private User getUserByTicket(String ticket)  {User user = null;CloseableHttpResponse closeableHttpResponse = null;try {CloseableHttpClient httpClient = HttpClients.createDefault();URI uriHttp = uriHttp = new URI("http://localhost:8080/ssoServer/sso/validateTicket");URIBuilder uriBuilder = new URIBuilder(uriHttp);uriBuilder.setParameter("ticket", ticket);URI uriParma  = uriBuilder.build();HttpGet httpGet = new HttpGet(uriParma);//执行请求访问closeableHttpResponse = httpClient.execute(httpGet);//获取返回HTTP状态码int satausCode = closeableHttpResponse.getStatusLine().getStatusCode();if(satausCode == 200 ){HttpEntity entity = closeableHttpResponse.getEntity();String content = EntityUtils.toString(entity,"UTF-8");user = JSONUtil.toBean(content, User.class);EntityUtils.consume(entity);}} catch (ClientProtocolException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();} catch (URISyntaxException e) {e.printStackTrace();}finally{try {closeableHttpResponse.close();} catch (IOException e) {e.printStackTrace();}}return user;}
}

sso服务端拦截器代码:

public class SSOServerFilter implements Filter{private final String LOGINURL = "http://localhost:8080/ssoServer/login/login";private final String cookieName = "zhuoqianmingyue";@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,FilterChain filterChain) throws IOException, ServletException {HttpServletRequest request = (HttpServletRequest )servletRequest;HttpServletResponse response = (HttpServletResponse )servletResponse;String uri = request.getRequestURI();if(uri.endsWith("sso/validateTicket")){String ticket = request.getParameter("ticket");String service = request.getParameter("service");String gtId = TicketCache.getGTByST(ticket);GrantingTicket grantingTicket = TicketCache.getGrantingTicket(gtId);User user = grantingTicket.getUser();String jsonStr = JSONUtil.toJsonStr(user);//让ticket过期ServiceTicket serviceTicket = grantingTicket.getServiceTicketMap().get(ticket);boolean expired = serviceTicket.isExpired();if(expired){serviceTicket.setExpired(false);response.getOutputStream().write(jsonStr.getBytes("UTF-8"));return;}else{response.sendRedirect(LOGINURL+"?srevice="+service+"&ERRORMSG=ticket已经过期!");return;}}else if(uri.endsWith("sso/signin")){String loginName = request.getParameter("loginName");String password = request.getParameter("password");String service = request.getParameter("service");if("lijunkui".equals(loginName)){UUID randomUUID = UUID.randomUUID();String ticketId = randomUUID.toString();GrantingTicket gt = new GrantingTicket();gt.setId(ticketId);User user = new User();user.setUserId(1l);user.setLoginName("lijunkui");user.setPassword("123456");gt.setUser(user);Cookie cookie = new Cookie("zhuoqianmingyue", ticketId);       cookie.setMaxAge(-1);   cookie.setPath("/");response.addCookie(cookie);TicketCache.putGrantingTicket(ticketId, gt);ServiceTicket st = new ServiceTicket();String stId = UUID.randomUUID().toString();st.setId(stId);st.setHost(service);ConcurrentHashMap<String, ServiceTicket> serviceTicketMap = gt.getServiceTicketMap();serviceTicketMap.put(stId, st);TicketCache.putSTAndGT(stId, ticketId);response.sendRedirect(service+"?ticket="+st.getId());return;}else{response.sendRedirect(LOGINURL+"?srevice="+service+"&ERRORMSG=登录名或者密码错误");return;}}else if(uri.endsWith("sso/authentication")){String gtId = getSSOCookie(request,response);String service =  request.getParameter("service");GrantingTicket gt = null;if(gtId != null){gt = TicketCache.getGrantingTicket(gtId);}if(StringUtils.isEmpty(service) || null == gtId || null == gt){response.sendRedirect(LOGINURL+"?srevice="+service);return;}ServiceTicket st = new ServiceTicket();String stId = UUID.randomUUID().toString();st.setId(stId);st.setHost(service);ConcurrentHashMap<String, ServiceTicket> serviceTicketMap = gt.getServiceTicketMap();serviceTicketMap.put(stId, st);TicketCache.putSTAndGT(stId, gt.getId());response.sendRedirect(service+"?ticket="+st.getId());return;}else if(uri.endsWith("sso/signout")){String gtCookie = getSSOCookie(request, response);String service = request.getParameter("service");ssoLogout(request,response,gtCookie);response.sendRedirect(LOGINURL+"?service="+service);return;}filterChain.doFilter(servletRequest, servletResponse);    }

测试步骤:

  1. 点单登录Demo并没有对接mysql数据库 在进行部署测试的时候请使用 登录名:lijunkui 密码:123456 进行登录
  2. 访问 http://localhost:8090/ssoClient/index/index 或者
    http://localhost:8090/ssoClient/login 进行登录
  3. 登录成功后会跳转到 http://localhost:8090/ssoClient/index/index。
  4. 然后再访问 http://localhost:8070/ssoClient2/index/index 进行测试是时候可以直接登录。

示例程序代码地址:https://gitee.com/junkuiLee/ssoexamples

SpringBoot 简单实现仿CAS单点登录系统相关推荐

  1. SpringBoot+MyBatis+Redis实现SSO单点登录系统(二)

    SpringBoot+MyBatis+Redis实现SSO单点登录系统(二) 三.代码 配置文件配置数据库,redis等相关的信息. # See http://docs.spring.io/sprin ...

  2. cas client 更新ticket_有人知道 cas单点登录系统是怎么样取得proxyticket的?

    展开全部 CAS 原理和协议 从结构上看,CAS 包含两个部分: CAS Server 和 CAS Client.CAS Server 需要独立部署,主要负责62616964757a686964616 ...

  3. SpringBoot+MyBatis+Redis实现SSO单点登录系统(一)

    SpringBoot+MyBatis+Redis实现SSO单点登录系统(一) 一.SSO系统概述 SSO英文全称Single Sign On,单点登录.SSO是在多个应用系统中,用户只需要登录一次就可 ...

  4. CAS单点登录系统的实现

    CAS单点登录系统的实现 前言 一.CAS思路 二.代码实现 环境准备 代码实现 2.1 MTV系统前端判断是否登录,依据前端cookie是否存在用户信息 2.2 MTV系统前端判断链接是否携带临时票 ...

  5. 使用http的asp.net项目接入https的CAS单点登录系统

    使用http的应用网站,接入https的CAS单点登录系统,结果就是登录之后,提示重定向次数太多,打不开应用网站的页面. 猜测原因是单点登录服务器与集成应用网站之间没有建立起有效连接.实际上应该也是, ...

  6. cas登录成功什么意思_你知道CAS单点登录系统吗

    单点登录(Single Sign On),简称为 SSO,是目前比较流行的企业业务整合的解决方案之一.SSO的定义是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统.我们目前的系统 ...

  7. cas单点登录系统:客户端(client)详细配置(包含统一单点注销配置)

    最近一直在研究cas登录中心这一块的应用,分享一下记录的一些笔记和心得.后面会把cas-server端的配置和重构,另外还有这几天再搞nginx+cas的https反向代理配置,以及cas的证书相关的 ...

  8. 单点登录cas常见问题(九) - android app怎么接入cas单点登录系统?

    cas-server-support-rest子项目有什么用:如果一个android app要接入cas中心系统,就需要用到这个子项目. 引入的前提条件是, cas项目已经启用了,通常不会启用一部分项 ...

  9. 爆破专栏丨Spring Security系列教程之实现CAS单点登录上篇-概述

    作者:千锋一一哥 前言 从本章节开始,一一哥 会给各位讲解一个很常见也很重要的知识点,就是单点登录!现在的大型分布式项目,基本都会考虑实现单点登录,而且现在网上也有很多单点登录的实现方案.开源项目,但 ...

最新文章

  1. 海思3559A上编译OpenCV4.1.0源码操作步骤
  2. !亲测有效!质量最高的pr模板网站,有点小贵罢了
  3. infor wms 项目启动_全一,企业物流定制专家——企业客户项目管理流程解析
  4. iOS之仿QQ好友列表展开收缩效果的实现
  5. weblogic 8.1 安装并配置数据源
  6. vue-cli搭建的项目打包之后报“资源路径错误资源文件找不到“
  7. 五分钟搞懂后缀数组!
  8. 为什么国内流行hbase,国外反而多用cassandra?
  9. 二叉树——二叉树问题(洛谷 P3884)
  10. 一个进程在执行过程中可以被中断事件打断_Linux操作系统:中断类型和中断的作用...
  11. php处理post表单数据,php – Httpful post表单数据
  12. 双线性插值算法实现和opencv、matlab结果不一致问题
  13. 《狂人C》阅读笔记(1)
  14. 龙卷风路径_关于龙卷风,看这篇文章就够了
  15. 第073封“情书”:小目标20181022Using UV Layout ForGeometryPacking<Entagma>Houdini 2018
  16. 史上最全 ArcGIS 软件安装包分享
  17. 基于matlab的天线方向图
  18. 老师给我推荐的经典管理书籍
  19. Redis是否存在线程安全问题
  20. JavaScript 实现延迟合并处理任务

热门文章

  1. nginx html 不缓存,nginx如何实现js和css不缓存
  2. spark sql uv_内置函数_SparkSQL学习 - 编程那点事
  3. 没学过编程可以学python吗_没编程基础可以学python吗
  4. android登录界面居中,Android TextView前加图标垂直居中第一行(仿大众点评购买须知/提示语)...
  5. 服务器系统怎么写,服务器操作系统语言写的
  6. php怎么创建对象变量,php实现变量动态创建类的对象用法
  7. Ubuntu 15 周年!
  8. oracle预备份,oracle自动备份
  9. 电脑安装系统后出现no bootable device_炉石传说:用电脑领取安卓和iOS专属卡包你能信?亲测有效!...
  10. 电子计算机简称什么也称什么,点点点电子美容仪