往期热门文章:

1、用了BigDecimal就不会资损?了解下BigDecimal这五个坑
2、一个依赖搞定 Spring Boot 反爬虫,防止接口盗刷!
3、千万不要把 Request 传递到异步线程里面!有坑!
4、不卷了!入职字节一周就果断跑了。
5、SpringBoot+ShardingSphereJDBC实现读写分离!
来源:juejin.cn/post/7123787027652280356

最近开发新产品,然后老板说我们现在系统太多了,每次切换系统登录太麻烦了,能不能做个优化,同一账号互通掉。作为一个资深架构狮,老板的要求肯定要满足,安排!

安排

一个公司产品矩阵比较丰富的时候,用户在不同系统之间来回切换,固然对产品用户体验上较差,并且增加用户密码管理成本。也没有很好地利用内部流量进行用户打通,并且每个产品的独立体系会导致产品安全度下降。因此实现集团产品的单点登录对用户使用体验以及效率提升有很大的帮助。那么如何实现统一认证呢?我们先了解一下传统的身份验证方式。

传统Session机制及身份认证方案

Cookie与服务器的交互

Cookie与服务器的交互

众所周知,http是无状态的协议,因此客户每次通过浏览器访问web页面,请求到服务端时,服务器都会新建线程,打开新的会话,而且服务器也不会自动维护客户的上下文信息。比如我们现在要实现一个电商内的购物车功能,要怎么才能知道哪些购物车请求对应的是来自同一个客户的请求呢?

因此出现了session这个概念,session 就是一种保存上下文信息的机制,他是面向用户的,每一个SessionID 对应着一个用户,并且保存在服务端中。session主要 以 cookie 或 URL 重写为基础的来实现的,默认使用 cookie 来实现,系统会创造一个名为JSESSIONID的变量输出到cookie中。

JSESSIONID 是存储于浏览器内存中的,并不是写到硬盘上的,如果我们把浏览器的cookie 禁止,则 web 服务器会采用 URL 重写的方式传递 Sessionid,我们就可以在地址栏看到 sessionid=KWJHUG6JJM65HS2K6 之类的字符串。

通常 JSESSIONID 是不能跨窗口使用的,当你新开了一个浏览器窗口进入相同页面时,系统会赋予你一个新的sessionid,这样我们信息共享的目的就达不到了。

服务器端的session的机制

当服务端收到客户端的请求时候,首先判断请求里是否包含了JSESSIONID的sessionId,如果存在说明已经创建过了,直接从内存中拿出来使用,如果查询不到,说明是无效的。

如果客户请求不包含sessionid,则为此客户创建一个session并且生成一个与此session相关联的sessionid,这个sessionid将在本次响应中返回给客户端保存。

对每次http请求,都经历以下步骤处理:

  • 服务端首先查找对应的cookie的值(sessionid)。

  • 根据sessionid,从服务器端session存储中获取对应id的session数据,进行返回。

  • 如果找不到sessionid,服务器端就创建session,生成sessionid对应的cookie,写入到响应头中。

session是由服务端生成的,并且以散列表的形式保存在内存中

基于 session 的身份认证流程

基于seesion的身份认证主要流程如下:

基于 session 的身份认证流程

因为 http 请求是无状态请求,所以在 Web 领域,大部分都是通过这种方式解决。但是这么做有什么问题呢?我们接着看

集群环境下的 Session 困境及解决方案

集群环境下的 Session 困境及解决方案

随着技术的发展,用户流量增大,单个服务器已经不能满足系统的需要了,分布式架构开始流行。通常都会把系统部署在多台服务器上,通过负载均衡把请求分发到其中的一台服务器上,这样很可能同一个用户的请求被分发到不同的服务器上,因为 session 是保存在服务器上的,那么很有可能第一次请求访问的 A 服务器,创建了 session,但是第二次访问到了 B 服务器,这时就会出现取不到 session 的情况。

我们知道,Session 一般是用来存会话全局的用户信息(不仅仅是登陆方面的问题),用来简化/加速后续的业务请求。
传统的 session 由服务器端生成并存储,当应用进行分布式集群部署的时候,如何保证不同服务器上 session 信息能够共享呢?

Session共享方案

Session共享一般有两种思路

  • session复制

  • session集中存储

session复制

session复制即将不同服务器上 session 数据进行复制,用户登录,修改,注销时,将session信息同时也复制到其他机器上面去。

session复制

这种实现的问题就是实现成本高,维护难度大,并且会存在延迟登问题。

session集中存储

session集中存储

集中存储就是将获取session单独放在一个服务中进行存储,所有获取session的统一来这个服务中去取。这样就避免了同步和维护多套session的问题。一般我们都是使用redis进行集中式存储session。

多服务下的登陆困境及SSO方案

SSO的产生背景

SSO的产生背景

如果企业做大了之后,一般都有很多的业务支持系统为其提供相应的管理和 IT 服务,按照传统的验证方式访问多系统,每个单独的系统都会有自己的安全体系和身份认证系统。进入每个系统都需要进行登录,获取session,再通过session访问对应系统资源。这样的局面不仅给管理上带来了很大的困难,对客户来说也极不友好,那么如何让客户只需登陆一次,就可以进入多个系统,而不需要重新登录呢?

“单点登录”就是专为解决此类问题的。其大致思想流程如下:通过一个 ticket 进行串接各系统间的用户信息

SSO的底层原理 CAS

CAS实现单点登录流程

我们知道对于完全不同域名的系统,cookie 是无法跨域名共享的,因此 sessionId 在页面端也无法共享,因此需要实现单店登录,就需要启用一个专门用来登录的域名如(ouath.com)来提供所有系统的sessionId。当业务系统被打开时,借助中心授权系统进行登录,整体流程如下:

  1. 当b.com打开时,发现自己未登陆,于是跳转到ouath.com去登陆

  2. ouath.com登陆页面被打开,用户输入帐户/密码登陆成功

  3. ouath.com登陆成功,种 cookie 到ouath.com域名下

  4. 把 sessionid 放入后台redis,存放<ticket,sesssionid>数据结构,然后页面重定向到A系统

  5. 当b.com重新被打开,发现仍然是未登陆,但是有了一个 ticket值

  6. 当b.com用ticket 值,到 redis 里查到 sessionid,并做 session 同步,然后种cookie给自己,页面原地重定向

  7. 当b.com打开自己页面,此时有了 cookie,后台校验登陆状态,成功

整个交互流程图如下:

单点登录流程演示

CAS登录服务demo核心代码

  1. 用户实体类

public class UserForm implements Serializable{private static final long serialVersionUID = 1L;private String username;private String password;private String backurl;public String getUsername() {return username;}public void setUsername(String username) {this.username = username;}public String getPassword() {return password;}public void setPassword(String password) {this.password = password;}public String getBackurl() {return backurl;}public void setBackurl(String backurl) {this.backurl = backurl;}
}
  1. 登录控制器

@Controller
public class IndexController {@Autowiredprivate RedisTemplate redisTemplate;@GetMapping("/toLogin")public String toLogin(Model model,HttpServletRequest request) {Object userInfo = request.getSession().getAttribute(LoginFilter.USER_INFO);//不为空,则是已登陆状态if (null != userInfo){String ticket = UUID.randomUUID().toString();redisTemplate.opsForValue().set(ticket,userInfo,2, TimeUnit.SECONDS);return "redirect:"+request.getParameter("url")+"?ticket="+ticket;}UserForm user = new UserForm();user.setUsername("laowang");user.setPassword("laowang");user.setBackurl(request.getParameter("url"));model.addAttribute("user", user);return "login";}@PostMapping("/login")public void login(@ModelAttribute UserForm user,HttpServletRequest request,HttpServletResponse response) throws IOException, ServletException {System.out.println("backurl:"+user.getBackurl());request.getSession().setAttribute(LoginFilter.USER_INFO,user);//登陆成功,创建用户信息票据String ticket = UUID.randomUUID().toString();redisTemplate.opsForValue().set(ticket,user,20, TimeUnit.SECONDS);//重定向,回原url  ---a.comif (null == user.getBackurl() || user.getBackurl().length()==0){response.sendRedirect("/index");} else {response.sendRedirect(user.getBackurl()+"?ticket="+ticket);}}@GetMapping("/index")public ModelAndView index(HttpServletRequest request) {ModelAndView modelAndView = new ModelAndView();Object user = request.getSession().getAttribute(LoginFilter.USER_INFO);UserForm userInfo = (UserForm) user;modelAndView.setViewName("index");modelAndView.addObject("user", userInfo);request.getSession().setAttribute("test","123");return modelAndView;}
}
  1. 登录过滤器

public class LoginFilter implements Filter {public static final String USER_INFO = "user";@Overridepublic void init(FilterConfig filterConfig) throws ServletException {}@Overridepublic void doFilter(ServletRequest servletRequest,ServletResponse servletResponse, FilterChain filterChain)throws IOException, ServletException {HttpServletRequest request = (HttpServletRequest) servletRequest;HttpServletResponse response = (HttpServletResponse)servletResponse;Object userInfo = request.getSession().getAttribute(USER_INFO);;//如果未登陆,则拒绝请求,转向登陆页面String requestUrl = request.getServletPath();if (!"/toLogin".equals(requestUrl)//不是登陆页面&amp;&amp; !requestUrl.startsWith("/login")//不是去登陆&amp;&amp; null == userInfo) {//不是登陆状态request.getRequestDispatcher("/toLogin").forward(request,response);return ;}filterChain.doFilter(request,servletResponse);}@Overridepublic void destroy() {}
}
  1. 配置过滤器

@Configuration
public class LoginConfig {//配置filter生效
@Bean
public FilterRegistrationBean sessionFilterRegistration() {FilterRegistrationBean registration = new FilterRegistrationBean();registration.setFilter(new LoginFilter());registration.addUrlPatterns("/*");registration.addInitParameter("paramName", "paramValue");registration.setName("sessionFilter");registration.setOrder(1);return registration;
}
}
  1. 登录页面

<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head><title>enjoy login</title><meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
<div text-align="center"><h1>请登陆</h1><form action="#" th:action="@{/login}" th:object="${user}" method="post"><p>用户名: <input type="text" th:field="*{username}" /></p><p>密  码: <input type="text" th:field="*{password}" /></p><p><input type="submit" value="Submit" /> <input type="reset" value="Reset" /></p><input type="text" th:field="*{backurl}" hidden="hidden" /></form>
</div>
</body>
</html>

web系统demo核心代码

  1. 过滤器

public class SSOFilter implements Filter {private RedisTemplate redisTemplate;public static final String USER_INFO = "user";public SSOFilter(RedisTemplate redisTemplate){this.redisTemplate = redisTemplate;}@Overridepublic void init(FilterConfig filterConfig) throws ServletException {}@Overridepublic void doFilter(ServletRequest servletRequest,ServletResponse servletResponse, FilterChain filterChain)throws IOException, ServletException {HttpServletRequest request = (HttpServletRequest) servletRequest;HttpServletResponse response = (HttpServletResponse)servletResponse;Object userInfo = request.getSession().getAttribute(USER_INFO);;//如果未登陆,则拒绝请求,转向登陆页面String requestUrl = request.getServletPath();if (!"/toLogin".equals(requestUrl)//不是登陆页面&amp;&amp; !requestUrl.startsWith("/login")//不是去登陆&amp;&amp; null == userInfo) {//不是登陆状态String ticket = request.getParameter("ticket");//有票据,则使用票据去尝试拿取用户信息if (null != ticket){userInfo = redisTemplate.opsForValue().get(ticket);}//无法得到用户信息,则去登陆页面if (null == userInfo){response.sendRedirect("http://127.0.0.1:8080/toLogin?url="+request.getRequestURL().toString());return ;}/*** 将用户信息,加载进session中*/UserForm user = (UserForm) userInfo;request.getSession().setAttribute(SSOFilter.USER_INFO,user);redisTemplate.delete(ticket);}filterChain.doFilter(request,servletResponse);}@Overridepublic void destroy() {}
}
  1. 控制器

@Controller
public class IndexController {@Autowiredprivate RedisTemplate redisTemplate;@GetMapping("/index")public ModelAndView index(HttpServletRequest request) {ModelAndView modelAndView = new ModelAndView();Object userInfo = request.getSession().getAttribute(SSOFilter.USER_INFO);UserForm user = (UserForm) userInfo;modelAndView.setViewName("index");modelAndView.addObject("user", user);request.getSession().setAttribute("test","123");return modelAndView;}
}
  1. 首页

<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head><title>enjoy index</title><meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
<div th:object="${user}"><h1>cas-website:欢迎你"></h1>
</div>
</body>
</html>

CAS的单点登录和OAuth2的区别

  • OAuth2: 三方授权协议,允许用户在不提供账号密码的情况下,通过信任的应用进行授权,使其客户端可以访问权限范围内的资源。

  • CAS: 中央认证服务(Central Authentication Service),一个基于Kerberos票据方式实现SSO单点登录的框架,为Web 应用系统提供一种可靠的单点登录解决方法(属于 Web SSO )。

  1. CAS的单点登录时保障客户端的用户资源的安全 ;OAuth2则是保障服务端的用户资源的安全 。

  2. CAS客户端要获取的最终信息是,这个用户到底有没有权限访问我(CAS客户端)的资源;OAuth2获取的最终信息是,我(oauth2服务提供方)的用户的资源到底能不能让你(oauth2的客户端)访问。

因此,需要统一的账号密码进行身份认证,用CAS;需要授权第三方服务使用我方资源,使用OAuth2。

往期热门文章:1、线上MySQL的自增id用尽怎么办?被面试官干趴下了!
2、计算机专业会不会成为下一个土木?
3、xxl-job惊艳的设计,怎能叫人不爱
4、ArrayList#subList这四个坑,一不小心就中招
5、面试官:大量请求 Redis 不存在的数据,从而影响数据库,该如何解决?
6、MySQL 暴跌!
7、超越 Xshell!号称下一代 Terminal 终端神器,用完爱不释手!
8、IDEA 官宣全新默认 UI,太震撼了!!
9、让你直呼「卧槽」的 GitHub 项目!
10、Kafka又笨又重,为啥不选Redis?

公司产品太多了,怎么实现一次登录产品互通?相关推荐

  1. 公司如何利用互联网渠道 加强企业的品牌宣传 产品推广

    IT小喇叭,关注移动互联网创新创业的科技媒体,助力企业的品牌宣传,下面给大家说说如何在互联网上推广自己的品牌.产品? 公司推广品牌.产品的主要目的是? IT小喇叭从专业媒体人的视角.专注移互联网的市场 ...

  2. 阿里巴巴起诉迪拜“阿里巴巴币”公司商标侵权;腾讯云发布区块链TBaaS产品白皮书;韩国将要推出区块链证券

    1.阿里巴巴起诉迪拜"阿里巴巴币"公司商标侵权 北京时间3日早间路透社称,阿里巴巴集团周一向美国法院提起诉讼,指控迪拜一家公司使用一种名为"阿里巴巴币"的加密货 ...

  3. 1995年的一次访谈中,乔布斯谈到处于垄断地位的科技公司因为营销人员掌握了话语权,不再注重产品研发而衰败

    1995年的一次访谈中,乔布斯谈到处于垄断地位的科技公司因为营销人员掌握了话语权,不再注重产品研发而衰败. 乔布斯先是以百事可乐为例:"他们的产品可以几十年不变,顶多换一下可乐的瓶子,所以如 ...

  4. Corel Painter 11的余温还没有过呢,Corel Painter 12就发布了,Corel 公司真是太有速度了。小猪我也是刚刚听朋友说Corel Painter 12发布一个月了。Core

    Corel Painter 11的余温还没有过呢,Corel Painter 12就发布了,Corel 公司真是太有速度了.小猪我也是刚刚听朋友说Corel Painter 12发布一个月了.Core ...

  5. 公司增值税太高了怎么办?除了此法解决就别无他法了吗?

    公司增值税太高了怎么办?除了此法解决就别无他法了吗? <税筹顾问>专注于园区招商.企业税务筹划,合理合规助力企业节税! 企业面临的问题不少,但税务问题尤为让人头疼.对于很多企业而言,增值税 ...

  6. 辞职都能监测 扒一扒背后公司深信服:官网已找不到产品 去年利润大幅下滑

    近日,有网友爆料称,知乎的视频部门正在裁员,几乎裁掉一半员工. 与上述消息几乎同时曝出的,是知乎安装了行为感知系统,可以通过监控员工的上网行为获悉其浏览招聘网站.投简历等行为,以此了解员工是否想离职. ...

  7. 新产品孕育记:PM如何把一款产品从0带到1

    背景介绍: 这篇文章呢,是我6月份写的,在负责的产品上线之后,写的反思总结.我想,这个过程,对于中小创公司的产品经理来说会有一定的借鉴意义,因为中小创公司并不像大公司那样把岗位分的非常细,产品经理经常 ...

  8. 小啊呜产品读书笔记001:《邱岳的产品手记-05》第9讲 产品案例分析:Hopper的“人工智能” 第10讲 产品被抄袭了怎么办?

    小啊呜产品读书笔记001:<邱岳的产品手记-05>第9讲 产品案例分析:Hopper的"人工智能" & 第10讲 产品被抄袭了怎么办? 一.今日阅读计划 二.泛 ...

  9. 小啊呜产品读书笔记001:《邱岳的产品手记-07》第13讲 无用却必要:产品规划【上】 第14讲 留白与节奏:产品规划【下】

    小啊呜产品读书笔记001:<邱岳的产品手记-07>第13讲 无用却必要:产品规划[上] & 第14讲 留白与节奏:产品规划[下] 一.今日阅读计划 二.泛读&知识摘录 1. ...

最新文章

  1. Session 过期问题处理
  2. ConfigurationClassPostProcessor设计与实现
  3. 配置springboot在访问404时自定义返回结果以及统一异常处理
  4. iOS 数据持久化-- FMDB
  5. linux 重建文件系统命令,fsck命令 – 检查并修复Linux文件系统
  6. JVM分代垃圾回收策略的基础概念
  7. shell制表与脚本运行进度条写法
  8. 2014ACM/ICPC亚洲区西安站 F题 color (组合数学,容斥原理)
  9. iOS底层探索之KVO(五)—FBKVOController分析
  10. 《老码识途》读书笔记:第一章(中)
  11. Java的executorservice_ExecutorService-10个要诀和技巧
  12. NoSQL之【MongoDB】学习(三):配置文件说明
  13. 给树莓派安装手柄驱动
  14. IEC104规约学习笔记
  15. 核磁共振成像基本原理——杨正汉(1)
  16. Systrace抓取
  17. html老师祝福语,给大学老师的祝福语
  18. Android中文件的读写---assets和raw下的资源文件
  19. XP Professional开机就要激活,否则无法登录桌面
  20. 读取obj+mtl 文件 (OpenGL C++)

热门文章

  1. 安装和设置vAG服务器虚拟机的过程,CDSM-5 桌面云安装部署
  2. ubuntu 14.04 安装gollum
  3. Custom Draw
  4. 【python计算机二级】python论语文本提纯-计算机二级操作题
  5. 实验室电磁铁EM4S的技术参数
  6. 大聪明教你学Java设计模式 | 第二篇:建造者模式
  7. 快速读懂人脸识别1:1/1:N/M:N模式
  8. 2022年全球塑料阻隔层压管行业分析报告
  9. [渝粤教育] 西南科技大学 概率与数理统计 在线考试复习资料
  10. 有意思的复活节彩蛋[摘]