SSO单点登录实例详解(前端传Code授权登录)

news2024/12/23 20:12:47

什么是 SSO(单点登录)

SSO 英文全称 Single Sign On,单点登录。SSO 是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。

单点登录流程

单点登录大致流程如下所示:
在这里插入图片描述
单点登录详细流程:
首先在前端点击登录子系统的时候(主系统已经完成账号和密码登录),主系统会给前端在页面的地址栏上面给出code值(我感觉不是很安全,但就是设计成这样),code值是随机生成的,并且有时效性(过段时间就无效了)。
在这里插入图片描述
后端代码里面会直接接收这样的code值:
在这里插入图片描述
之后根据文档,把code值和其他在配置文件配置的固定值封装在一起并且传给主系统以获取accessToken 以及其他的信息(用户登录信息等),代码如下:

// 使用授权码code换取accessToken
List<NameValuePair> params = new ArrayList<>();
params.add(new BasicNameValuePair("code", code));
params.add(new BasicNameValuePair("client_id", Configuration.getClientId()));
params.add(new BasicNameValuePair("client_secret", Configuration.getClientSecret()));
params.add(new BasicNameValuePair("auth_type", Configuration.getGrantType()));
params.add(new BasicNameValuePair("redirect_uri", Configuration.getRedirectUri()));
JSONObject result = HttpClientUtils.post(Configuration.getClientUrl(), params);
log.info("使用授权码code换取accessToken  URL:{},params:{}",Configuration.getClientUrl(),JSON.toJSONString(params));

接下来把获取的信息遍历并且封装起来,最后需要把这些信息都传给前端:

// 一般用HashMap传给前端
Map<String,Object> res = new HashMap<String,Object>();
// 直接用迭代器遍历JSONObject对象result 
Iterator iter = result.entrySet().iterator();
while (iter.hasNext()) {
	Map.Entry entry = (Map.Entry) iter.next();
	res.put(entry.getKey().toString(), entry.getValue().toString());
}

如果主系统传给我的result不为空,就直接开始解析这个accessToken以获取单点登录的用户信息,子系统可以直接解析这个accessToken

String key = Configuration.getJwtKey();
Claims claims = Jwts.parser().setSigningKey(generalKey(key)).parseClaimsJws(result.getString("access_token")).getBody();
JWTInfo info = new JWTInfo(claims);
log.info("解析access_token:{}", info);
log.info("state:" + state);
log.info("用户信息:{}", info);

解析accessToken之后,就开始走子系统(我这个系统)的登录逻辑。
如果不存在时,则直接添加用户(和直接新增一个用户一样的逻辑):

// 本地登陆逻辑
User user = UserService.findByUserName(info.getUsername());
if (user==null){
	log.info("当前用户在系统不存在时 新增用户");
	// 当前用户在系统不存在时 新增用户
	User User=new User();
	// 默认密码是当前账号名
	User.setPassword(info.getUsername());
	User.setUsername(info.getUsername());
	User.setTruename(info.getName());
	User.setNickname(info.getUsername());
	User.setTenantid(tenantid_default);
	User.setSchemaname(crtenant + tenantid_default.toString());
	// 设置用户状态
	User.setStatus(1);
	// todo 给新增用户设置默认的权限
	log.info("新增用户,参数:{}",User);
	user = UserService.addUser(User);
	log.info("新增用户返回的结果:{}",user==null?"null":JSON.toJSONString(user));
}

如果新增完用户或者本地本来就有这个用户,就继续往下走,先把这个用户放入登录用户的上下文(当前线程ThreadLocal,每个用户都会有单独的线程,以用户名作为当前线程的唯一标识,以后都从这里取用户名),然后再新增一个JWT Token(本系统新增的token信息,目的是为了传给前端,前端以后都用这个token信息来获取数据),并且将这个token信息放入res中,最后通过mergeRightValues来整合用户的权限信息:

log.info("使用用户名密码登录 username:{},password:{}",user.getUsername()==null?"null":user.getUsername(),user.getPassword()==null?"null":user.getPassword());
//  放入当前登录用户上下文
UserContext.setCurrentUserName(user.getUsername());
String token = JWTUtils.createJWT("username", user.getUsername(), user.getUsername(), expire * 1000);
res.put("Token", token);
String rightvalues = authService.mergeRightValues(user);

接着,通过access_token来获取主系统的用户信息和token信息:

// 获取主系统的token
String access_token = result.getString("access_token");
List<NameValuePair> queryParams = new ArrayList<>();
queryParams.add(new BasicNameValuePair("token", access_token));
FrontUserDto frontUserDto = HttpClientUtils.getForxxx("http://172.xxx.xxx:8080/xxx/xxx/xxx/front/info", queryParams, access_token);
log.info("获取主系统的信息:frontUserDto:{}",frontUserDto);
res.put("userinfo", frontUserDto);

接下来把用户的媒体权限信息保存到redis中,之后前端每次从后端获取数据的时候都会从redis里面直接获取用户的媒体权限信息来确保信息的安全性:

// 把用户的权限缓存到redis中
List<PermissionInfoDto> copyright = frontUserDto.getElements().stream().filter(permissionInfoDto -> permissionInfoDto.getCode().contains("COPYRIGHT")).collect(Collectors.toList());
String userMediaIds = loginUserService.getMediaIdsBasedMediaName(copyright);
if (StringUtils.isEmpty(userMediaIds)){
	return new CallBackMessage(result);
}
redisTemplate.opsForValue().set("userMedia-"+ frontUserDto.getUsername(),userMediaIds);

接下来解析之前的权限信息rightvalues,解析成为权限ID值rightIds (这个ID值具有检索用户权限的作用,直接传给前端,之后前端会根据这些权限ID值来获取数据,需要注意的是,这个和前面的媒体权限ID不是一样的):

String rightIds = authService.getRightIdsByRightvalues(rightvalues);
log.info("defaultRightIds:{}",defaultRightIds==null?"null":defaultRightIds);
log.info("rightIds:{}",rightIds==null?"null":rightIds);
//当获取到权限为空时  使用默认权限防止前端报错
if (StringUtils.isBlank(rightIds)){
	rightIds=StringUtils.join(defaultRightIds.split(","), "_");
}else {
	rightIds = StringUtils.join(rightIds.split(","), "_");
}
log.info("rightIds:{}",rightIds==null?"null":rightIds);
log.info("token:{}",token==null?"null":token);
res.put("rightIds", rightIds);

接着将前面通过code获取的refresh_token缓存到redis中,之后注销登录的时候需要通过获取refresh_token来确认这个用户以此来注销用户:

//将refresh_token缓存到redis
String refreshToken=result.getString("refresh_token");
if (StringUtils.isNotBlank(refreshToken)&&StringUtils.isNotBlank(token)){
	String redisKey="data:center:auth:logout:";
	stringRedisTemplate.opsForValue().set(redisKey+token,refreshToken,1, TimeUnit.DAYS);
}

最后,把登录日志存入数据库,并且返回res给前端:

//记录登录日志
MySchema mySchema = new MySchema();
mySchema.setSchemaname(user.getSchemaname());
mySchema.setTenantid(user.getTenantid());
MySchemaHolder.setCurrentMySchema(mySchema);
sysLogService.addSafetyLog(user, request);
return new CallBackMessage(res);

全部代码

	@RequestMapping(value = "/token", method = RequestMethod.GET)
	public CallBackMessage login(String code, String state, HttpServletResponse response,HttpServletRequest request) throws IOException {
		Map<String,Object> res = new HashMap<String,Object>();
		// 使用授权码code换取accessToken
		List<NameValuePair> params = new ArrayList<>();
		params.add(new BasicNameValuePair("code", code));
		params.add(new BasicNameValuePair("client_id", Configuration.getClientId()));
		params.add(new BasicNameValuePair("client_secret", Configuration.getClientSecret()));
		params.add(new BasicNameValuePair("auth_type", Configuration.getGrantType()));
		params.add(new BasicNameValuePair("redirect_uri", Configuration.getRedirectUri()));
		JSONObject result = HttpClientUtils.post(Configuration.getClientUrl(), params);
		log.info("使用授权码code换取accessToken  URL:{},params:{}",Configuration.getClientUrl(),JSON.toJSONString(params));

		Iterator iter = result.entrySet().iterator();
		while (iter.hasNext()) {
			Map.Entry entry = (Map.Entry) iter.next();
			res.put(entry.getKey().toString(), entry.getValue().toString());
		}
		System.out.println(res);

		if (result != null) {
			log.info("使用授权码code换取accessToken,返回结果:{}",result.toJSONString());
			try {
				String key = Configuration.getJwtKey();
				Claims claims = Jwts.parser().setSigningKey(generalKey(key)).parseClaimsJws(result.getString("access_token")).getBody();
				JWTInfo info = new JWTInfo(claims);
				log.info("解析access_token:{}", info);
				log.info("state:" + state);
				log.info("用户信息:{}", info);
				// 本地登陆逻辑
				User user = UserService.findByUserName(info.getUsername());
				if (user==null){
					log.info("当前用户在系统不存在时 新增用户");
					// 当前用户在系统不存在时 新增用户
					User User=new User();
					// 默认密码是当前账号名
					User.setPassword(info.getUsername());
					User.setUsername(info.getUsername());
					User.setTruename(info.getName());
					User.setNickname(info.getUsername());
					User.setTenantid(tenantid_default);
					User.setSchemaname(crtenant+tenantid_default.toString());
					// 设置用户状态
					User.setStatus(1);
					// todo 给新增用户设置默认的权限
					log.info("新增用户,参数:{}",User);
					user = UserService.addUser(User);
					log.info("新增用户返回的结果:{}",user==null?"null":JSON.toJSONString(user));
				}
				log.info("使用用户名密码登录 username:{},password:{}",user.getUsername()==null?"null":user.getUsername(),user.getPassword()==null?"null":user.getPassword());
				// 放入当前登录用户上下文
				UserContext.setCurrentUserName(user.getUsername());
				String token = JWTUtils.createJWT("username", user.getUsername(), user.getUsername(), expire * 1000);
				res.put("copyRightToken", token);
				String rightvalues = authService.mergeRightValues(user);

				// 获取主系统的token
				String access_token = result.getString("access_token");
				List<NameValuePair> queryParams = new ArrayList<>();
				queryParams.add(new BasicNameValuePair("token", access_token));
				FrontUserDto frontUserDto = HttpClientUtils.getForSzxm("http://xxxx.xxxx.xxx/xxx/xxx/xxx/front/info", queryParams, access_token);
				log.info("获取主系统的信息:frontUserDto:{}",frontUserDto);
				res.put("userinfo", frontUserDto);

				// 把用户的权限缓存到redis中
				List<PermissionInfoDto> copyright = frontUserDto.getElements().stream().filter(permissionInfoDto -> permissionInfoDto.getCode().contains("COPYRIGHT")).collect(Collectors.toList());
				String userMediaIds = loginUserService.getMediaIdsBasedMediaName(copyright);
				if (StringUtils.isEmpty(userMediaIds)){
					return new CallBackMessage(result);
				}
				redisTemplate.opsForValue().set("userMedia-"+ frontUserDto.getUsername(),userMediaIds);

//          	String rightIds = "";
				String rightIds = authService.getRightIdsByRightvalues(rightvalues);
				log.info("defaultRightIds:{}",defaultRightIds==null?"null":defaultRightIds);
				log.info("rightIds:{}",rightIds==null?"null":rightIds);
				// 当获取到权限为空时  使用默认权限防止前端报错
				if (StringUtils.isBlank(rightIds)){
					rightIds=StringUtils.join(defaultRightIds.split(","), "_");
				}else {
					rightIds = StringUtils.join(rightIds.split(","), "_");
				}
				log.info("rightIds:{}",rightIds==null?"null":rightIds);
				log.info("token:{}",token==null?"null":token);

				res.put("rightIds", rightIds);

				// 将refresh_token缓存到redis
				String refreshToken=result.getString("refresh_token");

				if (StringUtils.isNotBlank(refreshToken)&&StringUtils.isNotBlank(token)){
					String redisKey="data:center:auth:logout:";
					stringRedisTemplate.opsForValue().set(redisKey+token,refreshToken,1, TimeUnit.DAYS);
				}

				// 记录登录日志
				MySchema mySchema = new MySchema();
				mySchema.setSchemaname(user.getSchemaname());
				mySchema.setTenantid(user.getTenantid());
				MySchemaHolder.setCurrentMySchema(mySchema);
				sysLogService.addSafetyLog(user, request);
				return new CallBackMessage(res);
			} catch (Exception e) {
				log.error(e.getMessage());
				log.info("sendRedirect4:{}",Configuration.getLoginUrl());
				response.sendRedirect(Configuration.getLoginUrl());
			}
		} else {
			log.info("sendRedirect4:{}",Configuration.getLoginUrl());
			response.sendRedirect(Configuration.getLoginUrl());
		}
		return new CallBackMessage("失败");
	}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【自学C++】C++变量初始化

C变量初始化 C变量初始化教程 变量 的初始化就是在定义变量的同时&#xff0c;给变量设置一个初始值&#xff0c;在 C 中&#xff0c;如果定义变量没有初始化&#xff0c;那么变量有可能会被赋值也有可能不会赋值。 如果是定义的 全局变量 或者 静态变量&#xff0c;未初始化…

2022年语音合成(TTS)和语音识别(ASR)年度总结

论文统计每月更新一次&#xff0c;主要跟踪语音合成和语音识别的发展状况(很多文章都是在会议后才发出&#xff0c;但不影响统计。统计过程难免存在疏漏&#xff0c;因此统计结果仅供参考。所有文章语音合成领域统计列表请访问http://yqli.tech/page/tts_paper.html&#xff0c…

绝大多数人远远低估了软件开发的难度

给你付钱了&#xff0c;你应该把软件做好&#xff01; 这个话相当于&#xff1a; 给你付钱了&#xff0c;你应该把月亮摘下来&#xff01; 趣讲大白话&#xff1a;臣妾做不到 ********** 软件是特殊商品服务 可以说很难有标准 开发的难度取决于需求多少&#xff0c;技术难度&a…

Java Map集合的介绍和使用

什么是Map类型的集合 介绍 1.用于保存具有映射关系的数据&#xff08;key——value&#xff09;。 2.Map中的key和value可以是任意的类型的数据。 3.Map中的key值不允许重复。 4.Map中的value值可以重复。 5.一般常用string作为value的key。 6.key和value之间存在一一对…

如何进行地图SDK开发(二)——示例文档

概述 前面的文章文章我们写到了SDK的开发以及ak认证的实现&#xff0c;在本文我们继续讲讲地图SDK开发中的示例文档的实现。 技术点 vue3viteelement-plusmonaco-editor 实现后效果 实现 1. 工程初始化 1.1 搭建工程 搭建工程的过程请参照博文(使用vite搭建vue3项目&…

javaEE初阶 — 线程池

文章目录线程池1 什么是线程池2 标准库中的线程池2.1 什么是工厂模式2.2 如何使用标准库中的线程池完成任务2.3 ThreadPoolExecutor 构造方法的解释3 实现一个线程池线程池 1 什么是线程池 随着并发程度的提高&#xff0c;随着对性能要求标准的提高会发现&#xff0c;好像线程…

[cpp进阶]C++异常

文章目录C语言传统处理错误的方式C异常概念C异常使用异常的抛出和捕获异常的重新抛出异常安全异常规范自定义异常体系C标准库的异常体系异常的优缺点C语言传统处理错误的方式 传统的错误处理机制&#xff1a; 终止程序。assert断言直接终止程序。缺点&#xff1a;过于粗暴&am…

Fiddler抓取手机APP报文

Http协议代理工具有很多&#xff0c;比如Burp Suite、Charles、Jmeter、Fiddler等&#xff0c;它们都可以用来抓取APP报文&#xff0c;其中charles和Burp Suite是收费的&#xff0c;Jmeter主要用来做接口测试&#xff0c;而Fiddler提供了免费版&#xff0c;本文记录一下在Windo…

位运算做加法,桶排序找消失元素,名次与真假表示,杨氏矩阵,字符串左旋(外加两道智力题)

Tips 1. 2. 3. 大小端字节序存储这种顺序只有在放进去暂时存储的时候是这样的&#xff0c;但是一旦我里面的数据需要参与什么运算之类的&#xff0c;会“拿出来”先恢复到原先的位置再参与运算&#xff0c;因此&#xff0c;大小端字节序存储的什么顺序不影响移位运算等等…

【案例教程】CLUE模型构建方法、模型验证及土地利用变化情景预测实践技术

【前沿】&#xff1a;土地利用/土地覆盖数据是生态、环境和气象等领域众多模型的重要输入参数之一。基于遥感影像解译&#xff0c;可获取历史或当前任何一个区域的土地利用/土地覆盖数据&#xff0c;用于评估区域的生态环境变化、评价重大生态工程建设成效等。借助CLUE模型&…

声音产生感知简记

声音产生 人的发音器官包括:肺、气管、声带、喉、咽、鼻腔、口腔、唇。肺部产生的气流冲击声带,产生震动。 声带每开启和闭合一次的时间是基音周期(Pitch period,T),其到数为基音频率(F.=1/T,基频),范围在70-450Hz。基频越高,声音越尖细,如小孩的声音比大人尖,就是…

编译错误2

本文迁移自本人网易博客&#xff0c;写于2015年11月25日&#xff0c;编译错误2 - lysygyy的日志 - 网易博客 (163.com)1、error C2059:语法错误&#xff1a;“<L_TYPE_RAW>”error C2238:意外的标记位于“;”之前.错误代码定位于&#xff1a;BOOL TreeView_GetCheckState…

excel函数公式:常用高频公式应用总结 上篇

公式1&#xff1a;条件计数条件计数在Excel的应用中十分常见&#xff0c;例如统计人员名单中的女性人数&#xff0c;就是条件计数的典型代表。条件计数需要用到COUNTIF函数&#xff0c;函数结构为COUNTIF(统计区域,条件)&#xff0c;在本例第一个公式COUNTIF(B:B,G2)中&#xf…

《栈~~队列~~优先级队列》

目录 前言&#xff1a; 1.stack 1.stack的介绍 2.stack的使用&#xff1a; 3.stack的模拟实现 4.有关stack的oj笔试题 2.queue 1.队列的介绍 2.队列的使用 3.队列的模拟实现 4.有关队列的oj笔试题 3.priority_queue 1.优先级队列的介绍 2.优先级队列的使用 3.优先级队列的模拟实…

挥别2022,坦迎2023。

第一章&#xff1a;CSDN&#xff0c;我来啦&#xff01;第一节&#xff1a;初遇&#xff01;2022-08-13&#xff0c;我和CSDN相遇啦&#xff01;CSDN&#xff0c;你好呀&#xff01;2022年8月13日&#xff0c;是我与你相遇的日子。这是一个值得纪念的时刻。从此之后&#xff0c…

English Learning - L1-10 时态(下) 2023.1.5 周四

English Learning - L1-10 时态&#xff08;下&#xff09; 2023.1.5 周四8 时态8.3 完成时态核心思想&#xff1a;回首往事&#xff08;一&#xff09;现在完成时核心思想用法延续动作延续时间 “动作一直持续了。。。”延续动&#xff08;无延续时间&#xff09; “做过。。…

AtCoder Beginner Contest 284 A - E

题目地址&#xff1a;AtCoder Beginner Contest 284 - AtCoder 一个不知名大学生&#xff0c;江湖人称菜狗 original author: jacky Li Email : 3435673055qq.com Time of completion&#xff1a;2023.1.8 Last edited: 2023.1.8 目录 题目地址&#xff1a;AtCoder Beginner C…

基于FPGA的UDP 通信(一)

引言手头的FPGA开发板上有一个千兆网口&#xff0c;最近准备做一下以太网通信的内容。本文先介绍基本的理论知识。FPGA芯片型号&#xff1a;xc7a35tfgg484-2网口芯片&#xff08;PHY&#xff09;&#xff1a;RTL8211网络接口&#xff1a;RJ45简述以太网什么以太网&#xff1f;以…

k8s之实战小栗子

写在前面 本文一起看一个基于k8s的实战小栗子&#xff0c;在这篇文章 中我们基于docker搭建了一个WordPress网站。本文就通过k8s再来实现一遍。架构图如下&#xff1a; ![在这里插入图片描述](https://img-blog.csdnimg.cn/9c73ac0c183a429a8f4b1a2feb363527.png 从上图可以…

使用Origin计算数据的上升\下降时间

使用Origin计算上升/下降时间计算上升时间1导入数据&#xff0c;做图2、选择合适的数据范围3、选择上升时间和上升范围两个测量参数&#xff0c;获得结果4、更改区间&#xff0c;并导出数据计算下降时间1、将感兴趣区域移动到下降沿2、更改为测量下降沿参数获得结果上升时间小工…