CAS单点登录

1.相同顶级域名的单点登录SSO

相同顶级域名的单点登录:SSO:SINGLE SIGN ON
单点登录可以通过基于用户会话的共享;分为两种,第一种:相同顶级域名;
原理是分布式会话完成的;关键是顶级域名的cookie值是可以共享的

比如说现在有个一级域名为 www.xxx.com,是教育类网站,但是xxx网有其他的产品线,可以通过构建二级域名提供服务给用户访问,比如:music.xxx.com,shop.xxx.com,blog.xxx.com等等,分别为xx音乐,xx电商以及xx博客等,用户只需要在其中一个站点登录,那么其他站点也会随之而登录。

也就是说,用户自始至终只在某一个网站下登录后,那么他所产生的会话,就共享给了其他的网站,实现了单点网站登录后,同时间接登录了其他的网站,那么这个其实就是单点登录,他们的会话是共享的,都是同一个用户会话。

Cookie + Redis 实现 SSO
那么之前我们所实现的分布式会话后端是基于redis的,如此会话可以流窜在后端的任意系统,都能获取到缓存中的用户数据信息,前端通过使用cookie,可以保证在同域名的一级二级下获取,那么这样一来,cookie中的信息userid和token是可以在发送请求的时候携带上的,这样从前端请求后端后是可以获取拿到的,这样一来,其实用户在某一端登录注册以后,其实cookie和redis中都会带有用户信息,只要用户不退出,那么就能在任意一个站点实现登录了。

那么这个原理主要也是cookie和网站的依赖关系,顶级域名 www.xxx.com和*.xxx.com的cookie值是可以共享的,可以被携带至后端的,比如设置为 .xxx.com,.t.xxx.com,如此是OK的。
二级域名自己的独立cookie是不能共享的,不能被其他二级域名获取,比如:music.xxx.com的cookie是不能被blog.xxx.com共享,两者互不影响,要共享必须设置为.xxx.com

2.不同顶级域名的单点登录

如果顶级域名都不一样,咋办?比如 wwww.xxx.com要和www.yyy.com的会话实现共享,这个时候又该如何?!
这个时候的cookie由于顶级域名不同,就不能实现cookie跨域了,每个站点各自请求到服务端,cookie无法同步。比如,www.xxx.com下的用户发起请求后会有cookie,但是他又访问了www.yyy.com,由于cookie无法携带,所以要求二次登录。

那么遇到顶级域名不同却又要实现单点登录该如何实现呢?我们来参考下面一张图:

在这里插入图片描述
如上图所示,多个系统之间的登录会通过一个独立的登录系统去做验证,它就相当于是一个中介公司,整合了所有人,你要看房经过中介允许拿钥匙就行,实现了统一的登录。那么这个就称之为CAS系统,CAS全称为Central Authentication Service即中央认证服务,是一个单点登录的解决方案,可以用于不同顶级域名之间的单点登录。构建两个静态站点来测试使用即可。
在CAS中的具体的流程参考如下时序图:
在这里插入图片描述

3.代码实现:

SSO-MTV;SSO-MUSIC为两个不同顶级域名的子系统;用于测试用的;运行在tomcat的8080端口;
依赖:

<dependencies><!--  自定义Service   --><dependency><groupId>com.nly</groupId><artifactId>foodie-dev-service</artifactId><version>1.0-SNAPSHOT</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId></dependency></dependencies>

部分配置信息:


############################################################
#
# web访问端口号  约定:8088
#
############################################################
server:tomcat:uri-encoding: UTF-8max-http-header-size: 80KB############################################################
#
# 配置数据源信息
#
############################################################
spring:profiles:active: devdatasource:                                           # 数据源的相关配置type: com.zaxxer.hikari.HikariDataSource          # 数据源类型:HikariCPdriver-class-name: com.mysql.jdbc.Driver          # mysql驱动username: roothikari:connection-timeout: 30000       # 等待连接池分配连接的最大时长(毫秒),超过这个时长还没可用的连接则发生SQLException, 默认:30秒minimum-idle: 5                 # 最小连接数maximum-pool-size: 20           # 最大连接数auto-commit: true               # 自动提交idle-timeout: 600000            # 连接超时的最大时长(毫秒),超时则被释放(retired),默认:10分钟pool-name: DateSourceHikariCP     # 连接池名字max-lifetime: 1800000           # 连接的生命时长(毫秒),超时而且没被使用则被释放(retired),默认:30分钟 1800000msconnection-test-query: SELECT 1servlet:multipart:max-file-size: 512000 #文件上传大小限制为500kbmax-request-size: 512000 #请求大小限制为500kb
#  session:
#    store-type: redisthymeleaf:mode: HTMLencoding: utf-8prefix: classpath:/templates/suffix: .html############################################################
#
# mybatis 配置
#
############################################################
mybatis:type-aliases-package: com.nly.pojo          # 所有POJO类所在包路径mapper-locations: classpath:mapper/*.xml      # mapper映射文件############################################################
#
# mybatis mapper 配置
#
############################################################
# 通用 Mapper 配置
mapper:mappers: com.nly.my.mapper.MyMappernot-empty: false #在进行数据库操作的时候,判断表达式username! = null,是否追加username!=''identity: MYSQL
# 分页插件配置
pagehelper:helperDialect: mysqlsupportMethodsArguments: true
server:port: 8090spring:datasource:                                           # 数据源的相关配置url: jdbc:mysql://localhost:3306/foodie-shop-dev?useUnicode=true&characterEncoding=UTF-8&autoReconnect=truepassword: xxxredis:#redis单机单实例database: 2host: 192.168.56.102port: 6379timeout: 5000password: xxx

启动类

@SpringBootApplication
//扫描mybatis通用的包
@MapperScan(basePackages = "com.nly.mapper")
//扫描所有包以及相关组件包
@ComponentScan(basePackages = {"com.nly","org.n3r.idworker"})public class Application {public static void main(String[] args) {SpringApplication.run(Application.class,args);}//ApplicationListener
}

构建登录的模版页:

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head><meta charset="UTF-8"><title>SSO单点登录</title>
</head>
<body>
<h1>欢迎访问单点登录系统</h1>
<form action="doLogin" method="post"><input type="text" name="username" placeholder="请输入用户名"/><input type="password" name="password" placeholder="请输入密码"/><input type="hidden" name="returnUrl" th:value="${returnUrl}"><input type="submit" value="提交登录"/>
</form>
<span style="color:red" th:text="${errmsg}"></span></body>
</html>
@Controller
public class SSOController {@Autowiredprivate UserService userService;@Autowiredprivate RedisOperator redisOperator;public static final String REDIS_USER_TOKEN = "redis_user_token";public static final String REDIS_USER_TICKET= "redis_user_ticket";public static final String REDIS_TMP_TICKET = "redis_tmp_ticket";public static final String COOKIE_USER_TICKET = "cookie_user_ticket";/*@RequestMapping("/hello")@ResponseBodypublic  Object hello(){return "hello,world";}*/@RequestMapping("/login")public  String login(String returnUrl,Model model,HttpServletRequest request,HttpServletResponse response){model.addAttribute("returnUrl", returnUrl);//从cookie中获取userTicket门票,如果cookie中能够获取到,说明用户登录过,签发tmpTicket即可String userTicket = getCookie(request,COOKIE_USER_TICKET);boolean isVerified = verifyUserTicket(userTicket);if (isVerified) {String tmpTicket = createTmpTicket();return "redirect:" + returnUrl + "?tmpTicket=" + tmpTicket;}//用户从未登录过,第一次进入则跳转到CAS的统一登录页面return "login";}private boolean verifyUserTicket(String userTicket){if (StringUtils.isBlank(userTicket)){return false;}//1.验证CAS门票是否有效String userId = redisOperator.get(REDIS_USER_TICKET+":"+userTicket);if (StringUtils.isBlank(userId)){return false;}//2.验证门票对应的user会话是否存在String userRedis = redisOperator.get(REDIS_USER_TOKEN+":"+userId);if (StringUtils.isBlank(userRedis)){return  false;}return true;}/*** CAS的统一登录接口* 目的:*      1.登录后创建用户的全局会话------》uniqueToken*      2.创建用户全局门票,用以表示在CAS是否登录 ---》userTicket*      3.创建用户的临时票据,用于回跳回传------》tmpTicket*/@PostMapping("/doLogin")public  String doLogin(String username,String password,String returnUrl,Model model,HttpServletRequest request,HttpServletResponse response) throws Exception {model.addAttribute("returnUrl",returnUrl);//0.判断用户名和密码必须不为空if(StringUtils.isBlank(username)||StringUtils.isBlank(password)){model.addAttribute("errmsg", "用户名或密码不能为空");return "login";}//1.实现登录Users userResult = userService.queryUserForLogin(username, MD5Utils.getMD5Str(password));if (userResult == null){model.addAttribute("errmsg","用户名或密码不正确");return "login";}//2.实现用户的redis会话String uniqueToken = UUID.randomUUID().toString().trim();UsersVO usersVO = new UsersVO();BeanUtils.copyProperties(userResult,usersVO);usersVO.setUserUniqueToken(uniqueToken);redisOperator.set(REDIS_USER_TOKEN+":"+userResult.getId(), JsonUtils.objectToJson(usersVO));//3.生成ticket门票,全局门票,代表用户在CAS端登录过String userTicket = UUID.randomUUID().toString().trim();//3.1用户全局门票需要放入CAS端的cookie中setCookie(COOKIE_USER_TICKET,userTicket,response);//4.userTicket关联用户id,并且放入到redis中国,代表这个用户有门票了,可以在各个景区游玩redisOperator.set(REDIS_USER_TICKET+":"+userTicket,userResult.getId());//5.生成临时票据,回跳到调用网站,是有CAS端锁签发的一个一次性的临时ticketString tmpTicket = createTmpTicket();/*** userTicket:用于表示用户在CAS端的一个登录状态:已经登录* tmpTicket:用于颁发给用户进行一次性的验证的票据,有时效性*/
//        return "login";return "redirect:" + returnUrl + "?tmpTicket=" + tmpTicket;}@PostMapping("/verifyTmpTicket")@ResponseBodypublic  JSONResult verifyTmpTicket(String tmpTicket,HttpServletRequest request,HttpServletResponse response) throws Exception {//使用一次性临时票据来验证用户是否登录,如果登录过,把用户会话信息返回给站点//使用完毕后,需要销毁临时票据String tmpTicketValue = redisOperator.get(REDIS_TMP_TICKET+":"+tmpTicket);if (StringUtils.isBlank(tmpTicketValue)){return JSONResult.errorUserTicket("用户票据异常");}//0.如果临时票据ok,则需要销毁,并且拿到CAS端cookie中的全局userTicket,以此再获得用户会话if(!tmpTicketValue.equals(MD5Utils.getMD5Str(tmpTicket))){return JSONResult.errorUserTicket("用户票据异常");}else {//销毁临时票据redisOperator.del(REDIS_TMP_TICKET +":"+ tmpTicket);}//验证并且获取用户的userTicketString userTicket = getCookie(request,COOKIE_USER_TICKET);String userId = redisOperator.get(REDIS_USER_TICKET +":"+ userTicket);if (StringUtils.isBlank(userId)){return JSONResult.errorUserTicket("用户票据异常");}//2.验证门票对应的user会话是否存在String userRedis =redisOperator.get(REDIS_USER_TOKEN+":"+userId);if (StringUtils.isBlank(userRedis)) {return JSONResult.errorUserTicket("用户票据异常");}//验证成功,返回ok,携带用户会话return JSONResult.ok(JsonUtils.jsonToPojo(userRedis,UsersVO.class));}@PostMapping("/logout")@ResponseBodypublic JSONResult logout(String userId,HttpServletRequest request,HttpServletResponse response){//0.获取CAS中的用户门票String userTicket = getCookie(request,COOKIE_USER_TICKET);//1.清除userTicket票据,redis/cookiedeleteCookie(COOKIE_USER_TICKET,response);redisOperator.del(REDIS_USER_TICKET+""+userTicket);//2.清除用户全局会话(分布式会话)redisOperator.del(REDIS_USER_TOKEN+""+userId);return JSONResult.ok();}/*** 创建临时票据* @return*/private String createTmpTicket(){String tmpTicket =UUID.randomUUID().toString().trim();try{redisOperator.set(REDIS_TMP_TICKET+":"+tmpTicket,MD5Utils.getMD5Str(tmpTicket),600);} catch (Exception e) {e.printStackTrace();}return tmpTicket;}private void setCookie(String key,String val,HttpServletResponse response){Cookie cookie = new Cookie(key,val);cookie.setDomain("sso.com");cookie.setPath("/");response.addCookie(cookie);}private String getCookie(HttpServletRequest request,String key){Cookie[] cookieList = request.getCookies();if (cookieList == null || StringUtils.isBlank(key)){return null;}String cookieValue  = null ;for (int i = 0 ; i < cookieList.length; i ++) {if (cookieList[i].getName().equals(key)) {cookieValue = cookieList[i].getValue();break;}}return cookieValue;}private void deleteCookie(String key,HttpServletResponse response){Cookie cookie = new Cookie(key,null);cookie.setDomain("sso.com");cookie.setPath("/");cookie.setMaxAge(-1);response.addCookie(cookie);}
}

那么对于SSO的整个处理流程来讲,其实我们实现起来并不是很难,主要是为的理解整个流程,因为在面试过程中有可能会被问到。如果有兴趣的同学,可以去参考一下Apereo的CAS系统,是非常牛的,地址如下:
https://github.com/apereo/cas
https://www.apereo.org/projects/cas
(备注:后续会将所有的代码传到github上,有兴趣的可以关注一下)

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

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

相关文章

【C#】ThreadPool的使用

1.Thread的使用 Thread的使用参考&#xff1a;【C#】Thread的使用 2.ThreadPool的使用 .NET Framework 和 .NET Core 提供了 System.Threading.ThreadPool 类来帮助开发者以一种高效的方式管理线程。ThreadPool 是一个线程池&#xff0c;它能够根据需要动态地分配和回收线程…

【Kubernetes】Deployment 的清理策略

Deployment 的清理策略 在 Deployment 中配置 spec.revisionHistoryLimit 字段&#xff0c;可以指定其 清理策略。该字段用于指定 Deployment 保留旧 ReplicaSet 的个数&#xff0c;即更新 Pod 前的版本个数。该字段的默认值是 10。 创建 revisionhistory-demo.yaml 文件&…

上升探索WebKit的奥秘:打造高效、兼容的现代网页应用

嘿&#xff0c;朋友们&#xff01;想象一下&#xff0c;你正在浏览一个超级炫酷的网站&#xff0c;页面加载飞快&#xff0c;布局完美适应你的设备&#xff0c;动画流畅得就像你在看一场好莱坞大片。这一切的背后&#xff0c;有一个神秘的英雄——WebKit。今天&#xff0c;我们…

学习笔记--算法(双指针)3

快乐数 . - 力扣&#xff08;LeetCode&#xff09; 题目 编写一个算法来判断一个数 n 是不是快乐数。 「快乐数」 定义为&#xff1a; 对于一个正整数&#xff0c;每一次将该数替换为它每个位置上的数字的平方和。然后重复这个过程直到这个数变为 1&#xff0c;也可能是 无…

如何在IDEA上使用JDBC编程【保姆级教程】

目录 前言 什么是JDBC编程 本质 使用JDBC编程的优势 JDBC流程 如何在IEDA上使用JDBC JDBC编程 1.创建并初始化数据源 2.与数据库服务器建立连接 3.创建PreparedStatement对象编写sql语句 4.执行SQL语句并处理结果集 executeUpdate executeQuery 5.释放资源 前言 在…

二叉树中的深搜

目录 二叉树中的深搜&#xff1a; 一、计算布尔二叉树的值 1.题目链接&#xff1a;2331. 计算布尔二叉树的值 2.题目描述&#xff1a; 3.解法&#xff08;递归&#xff09; &#x1f352;算法思路&#xff1a; &#x1f352;算法流程&#xff1a; &#x1f352;算法代码…

C# Unity 面向对象补全计划 泛型

本文仅作学习笔记与交流&#xff0c;不作任何商业用途&#xff0c;作者能力有限&#xff0c;如有不足还请斧正 1.什么是泛型 泛型&#xff08;Generics&#xff09;是C#中的一个强大特性&#xff0c;允许你编写可以适用于多种数据类型的可重用代码&#xff0c;而不需要重复编写…

CSP-J复赛-模拟题4

1.区间覆盖问题&#xff1a; 题目描述 给定一个长度为n的序列1,2,...,a1​,a2​,...,an​。你可以对该序列执行区间覆盖操作&#xff0c;即将区间[l,r]中的数字,1,...,al​,al1​,...,ar​全部修改成同一个数字。 现在有T次操作&#xff0c;每次操作由l,r,p,k四个值组成&am…

GD32 SPI 通信协议

1.0 SPI 简介 SPI是一种串行通信接口&#xff0c;相对于IIC而言SPI需要的信号线的个数多一点&#xff0c;时钟的信号是主机产生的。 MOSI&#xff1a;主机发送&#xff0c;从机接收 MISO&#xff1a;主机接收&#xff0c;从机发送 CS&#xff1a;表示的是片选信号 都是单向…

C# Unity 面向对象补全计划 泛型约束

本文仅作学习笔记与交流&#xff0c;不作任何商业用途&#xff0c;作者能力有限&#xff0c;如有不足还请斧正 1.泛型约束了什么 在C#中&#xff0c;泛型约束用于限制泛型类型参数的类型 可以在泛型类型或方法的声明中使用 where 关键字来指定这些约束 2.约束栗子 基类约束…

LearnOpenGL-入门章节学习笔记

LearnOpenGL-入门章节学习笔记 简介一、核心模式与立即渲染模式二、扩展三、状态机四、对象 创建窗口一、Main函数——实例化窗口二、Callback Function 回调函数三、processInput 函数 创建三角形一、顶点输入二、顶点着色器三、编译着色器四、片段着色器五、着色器程序六、链…

【原创】下载RealEstate10K数据集原始视频的方法

前言:目前互联网上能搜到下载RealEstate10K数据集原始视频的方法都已经不能用了,这篇博客介绍一种目前可用的下载RealEstate10K数据集原始视频的方法,并给出自动化的脚本代码。 目录 RealEstate10K简介 RealEstate10K标注文本下载 RealEstate10K原始视频下载 环境安装 …

全面提升PDF编辑效率,2024年五大顶级PDF编辑器推荐!

在这个数字化飞速发展的时代&#xff0c;PDF文件已经成为我们日常工作和学习中不可或缺的一部分。然而&#xff0c;面对PDF文件的编辑和管理&#xff0c;许多人仍然感到困惑和无助。今天&#xff0c;就让我们一起探索几款高效、易用的PDF编辑器&#xff0c;它们将彻底改变你的工…

萱仔环境记录——git的安装流程

最近由于我有一个大模型的offer&#xff0c;由于我只在实验室的电脑上装了git&#xff0c;我准备在自己的笔记本上本地安装一个git&#xff0c;也给我的一个师弟讲解一下git安装和使用的过程&#xff0c;给我的环境安装章节添砖加瓦。 github是基于git的一个仓库托管平台。 g…

前端的学习-CSS(二)-弹性盒子-flex

一&#xff1a;子元素的属性 order&#xff1a;项目的排列顺序&#xff0c;数值越小&#xff0c;排列越靠前&#xff0c;默认为0。 flex-grow&#xff1a;定义项目的放大比例&#xff0c;默认为 0 &#xff0c;即如果存在剩余空间&#xff0c;也不放大。 flex-shrink&#xff1…

鸿蒙应用服务开发【华为账号服务】

Account Kit 介绍 本示例展示了使用Account Kit提供的登录、授权头像昵称、实时验证手机号、收货地址、发票抬头、未成年人模式的能力。 本示例模拟了在应用里&#xff0c;调用一键登录Button组件拉起符合华为规范的登录页面&#xff1b;调用获取头像昵称接口获取头像昵称&a…

excel中有些以文本格式存储的数值如何批量转换为数字

一、背景 1.1 文本格式存储的数值特点 在平时工作中有时候会从别地方导出来表格&#xff0c;表格中有些数值是以文本格式存储的&#xff08;特点&#xff1a;单元格的左上角有个绿色的小标&#xff09;。 1.2 文本格式存储的数值在排序时不符合预期 当我们需要进行排序的时候…

IDEA全局搜索Jar包中内容

IDEA全局搜索Jar包中内容 【一】下载源码【二】搜索内容【1】按文件名搜索【2】全局关键字搜索【3】方法引用 【一】下载源码 想要搜索Jar中关键字&#xff0c;必须先把jar包源码下载下来&#xff0c;否则搜不到。 Preferences --> Maven --> Importing&#xff0c;根据…

微信丨QQ丨TIM防撤回工具

适用于 Windows 下 PC 版微信/QQ/TIM的防撤回补丁。支持最新版微信/QQ/TIM&#xff0c;其中微信能够选择安装多开功能。微信防撤回信息&#xff01; 「防撤回」来自UC网盘分享https://drive.uc.cn/s/95f9aabbc9684

基于SSM的哈米音乐实战项目,Java课程设计作业,Java毕业设计项目,找工作前的实战项目,附部分源码以及数据库

1、项目所需技术 java基础&#xff0c;java反射&#xff0c;泛型html&#xff0c;css&#xff0c;JavaScript&#xff0c;jquery&#xff0c;bootstrap&#xff0c;layuijstl&#xff0c;el表达式&#xff0c;jsp&#xff0c;mysql&#xff0c;jdbc&#xff0c;xml&#xff0c…