(SSO单点登录)多个系统之间如何实现账号互通

SSO具有以下优点:

  • 降低访问第三方网站风险;
  • 降低用户名和密码的管理成本;
  • 提高用户试用满意度;
  • SSO使用标准的身份认证和授权协议,如OAuth、OpenID Connect等,可以保障用户身份的安全性和隐私性。

单点登录最大的问题:

  • 最主要的问题是大部分实现方式都需要对单点登录目标系统进行修改,或者在目标系统中放入单点登录代码。如果我们自己对目标系统没有控制能力,那么与目标系统的沟通就成为最大的阻碍。

单点登录最理想的方式:

  • 如果可以不修改目标系统,或者可以不在目标系统放入代码就可以登录就是最理想的单点登录。

目录

一、背景

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

2.1 Cookie 与服务器的交互

2.2 服务器端的 session 的机制

2.3 基于 session 的身份认证流程

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

3.1 Session 共享方案

(1)session 复制

(2)session 集中存储

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

4.1 SSO 的产生背景

4.2 SSO 的底层原理 CAS

(1)CAS 实现单点登录流程

(2)单点登录流程演示

CAS 登录服务 demo 核心代码如下:

web 系统 demo 核心代码如下:

(3)CAS 的单点登录和 OAuth2 的区别


一、背景

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

图片

一个公司产品矩阵比较丰富的时候,用户在不同系统之间来回切换,固然对产品用户体验上较差,并且增加用户密码管理成本。

也没有很好地利用内部流量进行用户打通,并且每个产品的独立体系会导致产品安全度下降。

因此实现集团产品的单点登录对用户使用体验以及效率提升有很大的帮助。那么如何实现统一认证呢?我们先了解一下传统的身份验证方式。

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

2.1 Cookie 与服务器的交互

众所周知,http 是无状态的协议,因此客户每次通过浏览器访问 web。

页面,请求到服务端时,服务器都会新建线程,打开新的会话,而且服务器也不会自动维护客户的上下文信息。

比如我们现在要实现一个电商内的购物车功能,要怎么才能知道哪些购物车请求对应的是来自同一个客户的请求呢?

因此出现了 session 这个概念,session 就是一种保存上下文信息的机制,他是面向用户的,每一个 SessionID 对应着一个用户,并且保存在服务端中。

session 主要以 cookie 或 URL 重写为基础的来实现的,默认使用 cookie 来实现,系统会创造一个名为 JSESSIONID 的变量输出到 cookie 中。

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

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

2.2 服务器端的 session 的机制

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

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

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

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

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

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

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

2.3 基于 session 的身份认证流程

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

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

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

随着技术的发展,用户流量增大,单个服务器已经不能满足系统的需要了,分布式架构开始流行。

通常都会把系统部署在多台服务器上,通过负载均衡把请求分发到其中的一台服务器上,这样很可能同一个用户的请求被分发到不同的服务器上。

因为 session 是保存在服务器上的,那么很有可能第一次请求访问的 A 服务器,创建了 session,但是第二次访问到了 B 服务器,这时就会出现取不到 session 的情况。

我们知道,Session 一般是用来存会话全局的用户信息(不仅仅是登陆方面的问题),用来简化/加速后续的业务请求。

传统的 session 由服务器端生成并存储,当应用进行分布式集群部署的时候,如何保证不同服务器上 session 信息能够共享呢?

3.1 Session 共享方案

Session 共享一般有两种思路:

  • session 复制

  • session 集中存储

(1)session 复制

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

图片

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

(2)session 集中存储

图片

集中存储就是将获取 session 单独放在一个服务中进行存储,所有获取 session 的统一来这个服务中去取。

这样就避免了同步和维护多套 session 的问题。一般我们都是使用 redis 进行集中式存储 session。

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

4.1 SSO 的产生背景

图片

如果企业做大了之后,一般都有很多的业务支持系统为其提供相应的管理和 IT 服务,按照传统的验证方式访问多系统,每个单独的系统都会有自己的安全体系和身份认证系统。

进入每个系统都需要进行登录,获取 session,再通过 session 访问对应系统资源。

这样的局面不仅给管理上带来了很大的困难,对客户来说也极不友好,那么如何让客户只需登陆一次,就可以进入多个系统,而不需要重新登录呢?

图片

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

4.2 SSO 的底层原理 CAS

(1)CAS 实现单点登录流程

我们知道对于完全不同域名的系统,cookie 是无法跨域名共享的,因此 sessionId 在页面端也无法共享,因此需要实现单店登录,就需要启用一个专门用来登录的域名如(ouath.com)来提供所有系统的 sessionId。

当业务系统被打开时,借助中心授权系统进行登录,整体流程如下:

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

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

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

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

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

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

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

整个交互流程图如下:

图片

(2)单点登录流程演示
CAS 登录服务 demo 核心代码如下:

用户实体类:

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;
}}

登录控制器:

@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;
}
}

登录过滤器:

public class LoginFilter implements Filter {public static final String USER_INFO = "user";@Overridepublic void init(FilterConfig filterConfig) throws ServletException {}@Override
public 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);
}@Override
public void destroy() {}
}

配置过滤器:

@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;
}
}

登录页面:

<!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 核心代码如下:

过滤器:

public class SSOFilter implements Filter {private RedisTemplate redisTemplate;public static final String USER_INFO = "user";public SSOFilter(RedisTemplate redisTemplate){this.redisTemplate = redisTemplate;
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {}@Override
public 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);
}@Override
public void destroy() {}
}

控制器:

@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;
}
}

首页:

<!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>
(3)CAS 的单点登录和 OAuth2 的区别

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

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

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

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/244735.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

文件上传技术总结

语言可解析的后缀 &#xff08;前提&#xff1a;在Apache httpd.conf 配置文件中有特殊语言的配置 AddHandler application/x-httpd-php .php 搭配大小写、双重、空格来进行 其中&#xff1a; phtml、pht、php3、php4和php5都是Apache和php认可的php程序的文件后缀 常见的…

C#使用IsLeapYear方法判断指定年份是否为闰年

目录 一、判断指定年是否为闰年的2个方法 1.使用IsLeapYear方法判断指定年份是否为闰年 2.使用自定义的算法计算指定年份是否为闰年 二、示例 1.方法1的实例 2.方法2的实例 一、判断指定年是否为闰年的2个方法 1.使用IsLeapYear方法判断指定年份是否为闰年 使用IsLeapY…

【立创EDA-PCB设计基础】6.布线铺铜实战及细节详解

前言&#xff1a;本文进行布线铺铜实战及详解布线铺铜的细节 在本专栏中【立创EDA-PCB设计基础】前面完成了布线铺铜前的设计规则的设置&#xff0c;接下来进行布线 布局原则是模块化布局&#xff08;优先布局好确定位置的器件&#xff0c;例如排针、接口、主控芯片&#xff…

司铭宇老师:门店经理培训:如何成为一位卓越的门店经理

门店经理培训&#xff1a;如何成为一位卓越的门店经理 在激烈的市场竞争中&#xff0c;门店经理作为门店的灵魂人物&#xff0c;肩负着提升门店业绩、维护品牌形象、带领团队成长等重要职责。本文将为您解析如何成为一位卓越的门店经理&#xff0c;助力您的职业生涯迈向新高峰…

【latex】在Overleaf的IEEE会议模板中,快速插入参考文献

【LaTeX】在Overleaf的IEEE会议模板中&#xff0c;快速插入参考文献 写在最前面第一步&#xff1a;在文献检索网站导出引用文献的bib文件第二步&#xff1a;编辑overleaf模版方法二&#xff1a;EduBirdie生成参考文献&#xff08;补充&#xff09;使用LaTeX在Overleaf的IEEE会议…

html火焰文字特效

下面是代码&#xff1a; <!DOCTYPE html> <html><head><meta charset"UTF-8"><title>HTML5火焰文字特效DEMO演示</title><link rel"stylesheet" href"css/style.css" media"screen" type&quo…

接口测试 02 -- JMeter入门到实战

前言 JM eter毕竟是做压测的工具&#xff0c;自动化这块还是有缺陷。 如果公司做一些简单的接口自动化&#xff0c;可以考虑使用JMeter快速完成&#xff0c;如果想做完善的接口自动化体系&#xff0c;建议还是基于Python来做。 为什么学习接口测试要先从JMeter开始&#xff1f;…

路由器配置虚拟服务器

文章目录 路由器配置虚拟服务器1.前言2.配置流程2.1 进入路由器的登录页面2.2 找到端口映射功能2.3 添加虚拟服务器2.4 查找路由器的动态IP2.5 SSH连接 路由器配置虚拟服务器 1.前言 局域网下面连接着路由器&#xff0c;路由器下面连接着服务器&#xff0c;我们自己的电脑想要…

Unity | 渡鸦避难所-8 | URP 中利用 Shader 实现角色受击闪白动画

1. 效果预览 当角色受到攻击时&#xff0c;为了增加游戏的视觉效果和反馈&#xff0c;可以添加粒子等动画&#xff0c;也可以使用 Shader 实现受击闪白动画&#xff1a;受到攻击时变为白色&#xff0c;逐渐恢复为正常颜色 本游戏中设定英雄受击时播放粒子效果&#xff0c;怪物…

什么是ORM思想?

1. ORM概念 ORM&#xff08;Object Relational Mapping&#xff09;对象关系映射模式&#xff0c;是一种技术&#xff0c;解决了面向对象与关系型数据库存互不匹配的现象。 ORM在业务逻辑层和数据库层之间充当了桥梁的作用。 2. ORM由来 在软件开发的过程中&#xff0c;通常…

【每日一题】最长交替子数组

文章目录 Tag题目来源解题思路方法一&#xff1a;双层循环方法二&#xff1a;单层循环 写在最后 Tag 【双层循环】【单层循环】【数组】【2024-01-23】 题目来源 2765. 最长交替子数组 解题思路 两个方法&#xff0c;一个是双层循环&#xff0c;一个是单层循环。 方法一&am…

C++——结构体

1&#xff0c;结构体基本概念 结构体属于用户自定义的数据类型&#xff0c;允许用户存储不同的数据类型。像int&#xff08;整型&#xff09;&#xff0c;浮点型&#xff0c;bool型&#xff0c;字符串型等都是属于系统内置的数据类型。而今天要学习的结构体则是属于我们自定义…

Redis(五)

1、布隆过滤 1.1、简介 由一个初值都为零的bit数组和多个哈希函数构成&#xff0c;可以用来快速判断集合中是否存在某个元素&#xff0c;减少占用内存&#xff0c;不保存数据信息&#xff0c;只是在内存中做出一个标记。 它实际上是一个很长的二进制数组(00000000)一系列随机h…

Linux破解密码

破解root密码&#xff08;Linux 7&#xff09; 1、先重启——e 2、Linux 16这一行 末尾加rd.break&#xff08;不要回车&#xff09;中断加载内核 3、再ctrlx启动&#xff0c;进入救援模式 4、mount -o remount&#xff0c;rw /sysroot/——&#xff08;mount挂载 o——opti…

浅学JAVAFX布局

JAVAFX FlowPane布局 Flowpane是一个容器。它在一行上排列连续的子组件&#xff0c;并且如果当前行填充满了以后&#xff0c;则自动将子组件向下推到一行 public class FlowPanedemo extends Application {Overridepublic void start(Stage stage) throws Exception {stage.s…

【网站项目】医院管理系统源码(有源码)

🙊作者简介:多年一线开发工作经验,分享技术代码帮助学生学习,独立完成自己的项目或者毕业设计。 代码可以私聊博主获取。🌹赠送计算机毕业设计600个选题excel文件,帮助大学选题。赠送开题报告模板,帮助书写开题报告。作者完整代码目录供你选择: 《Springboot网站项目…

Java基础 - 09 Set之linkedHashSet , CopyOnWriteArraySet

LinkedHashSet和CopyOnWriteArraySet都是Java集合框架提供的特殊集合类&#xff0c;他们在特定场景下有不同的用途和特点。 LinkedHashSet是Java集合框架中的一种实现类&#xff0c;它继承自HashSet并且保持插入顺序。它使用哈希表来存储元素&#xff0c;并使用链表来维护插入…

Docker 47 个常见故障的原因和解决方法

【作者】曹如熙&#xff0c;具有超过十年的互联网运维及五年以上团队管理经验&#xff0c;多年容器云的运维&#xff0c;尤其在Docker和kubernetes领域非常精通。 Docker是一种相对使用较简单的容器&#xff0c;我们可以通过以下几种方式获取信息&#xff1a; 1、通过docker r…

爬虫进阶之selenium模拟浏览器

爬虫进阶之selenium模拟浏览器 简介环境配置1、建议先安装conda2、创建虚拟环境并安装对应的包3、下载对应的谷歌驱动以及与驱动对应的浏览器 代码setting.py配置scrapy脚本参考中间件middlewares.py 附录&#xff1a;selenium教程 简介 Selenium是一个用于自动化浏览器操作的…

03 SpringBoot实战 -微头条之首页门户模块(跳转某页面自动展示所有信息+根据hid查询文章全文并用乐观锁修改阅读量)

1.1 自动展示所有信息 需求描述: 进入新闻首页portal/findAllType, 自动返回所有栏目名称和id 接口描述 url地址&#xff1a;portal/findAllTypes 请求方式&#xff1a;get 请求参数&#xff1a;无 响应数据&#xff1a; 成功 {"code":"200","mes…