【Sa-Token|4】Sa-Token微服务项目应用
若微服务数量多,如果每个服务都改动,
工作量大,则可以只在网关和用户中心进行改动,也是可以实现服务之间的跳转。
这种方式可以通过在网关服务中生成和验证 Sa-Token,并将其与现有的 Token关联存储在 Redis 中。用户中心提供额外的接口来验证和生成新的 Sa-Token。
这样只需要改造网关和用户服务,其他服务保持现状,即可实现各个应用之间的跳转

实现方案
为了实现这个目标,同时尽量减少对现有系统的改动,可以在网关和用户中心进行必要的改动,以确保用户在登录后能够在多个系统中无缝访问。
下面是详细的方案和步骤:
一、用户中心改造
用户中心需要提供两个接口:
- 登录接口:用户登录并生成原有业务系统的 Token。
- Token 验证和 Sa-Token 生成接口:验证原有 Token 并生成 Sa-Token。
1. 登录接口
@RestController
@RequestMapping("/auth")
public class AuthController {@PostMapping("/login")public ResponseEntity<Map<String, Object>> login(@RequestParam String username, @RequestParam String password) {Map<String, Object> result = new HashMap<>();// 进行用户名和密码校验if (checkCredentials(username, password)) {String originalToken = generateOriginalToken(username); // 生成原有业务系统的 Tokenresult.put("originalToken", originalToken);return ResponseEntity.ok(result);}result.put("error", "登录失败");return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(result);}private boolean checkCredentials(String username, String password) {// 检查用户名和密码逻辑return true; // 假设校验成功}private String generateOriginalToken(String username) {// 生成原有业务系统的 Tokenreturn "original-token";}
}
2. Token 验证和 Sa-Token 生成接口
@RestController
@RequestMapping("/auth")
public class AuthController {@Autowiredprivate RedisTemplate<String, String> redisTemplate;@GetMapping("/check")public ResponseEntity<SaResult> checkToken(@RequestParam String token) {// 验证原有 Tokenif (validateOriginalToken(token)) {String userId = getUserIdFromOriginalToken(token);StpUtil.login(userId);String saToken = StpUtil.getTokenValue();// 存储原有 Token 和 Sa-Token 的映射关系redisTemplate.opsForValue().set("sa-token:" + token, saToken);redisTemplate.opsForValue().set("original-token:" + saToken, token);return ResponseEntity.ok(SaResult.ok("Token 有效").set("saToken", saToken));}return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(SaResult.error("Token 无效"));}private boolean validateOriginalToken(String token) {// 验证原有业务系统的 Tokenreturn true; // 假设校验成功}private String getUserIdFromOriginalToken(String token) {// 从原有业务系统的 Token 中解析出用户IDreturn "parsed-user-id";}
}
二、网关服务改造
网关服务需要在所有请求到达后端服务前,进行原有 Token 的验证和 Sa-Token 的生成和验证。
1. 引入 Redis 依赖
确保网关服务的 pom.xml 文件中包含 Redis 依赖:
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2. 配置 Redis
在 application.yml 文件中配置 Redis 连接信息:
spring:redis:host: localhostport: 6379password: ""
3. 网关服务 Token 校验过滤器
@Component
public class TokenFilter implements GlobalFilter, Ordered {@Autowiredprivate RestTemplate restTemplate;@Autowiredprivate RedisTemplate<String, String> redisTemplate;@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {String originalToken = exchange.getRequest().getHeaders().getFirst("Authorization");if (originalToken != null) {String saToken = redisTemplate.opsForValue().get("sa-token:" + originalToken);if (saToken == null) {// 调用用户中心的接口生成 Sa-TokensaToken = generateSaToken(originalToken);if (saToken == null) {exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);return exchange.getResponse().setComplete();}}exchange.getRequest().mutate().header("Sa-Token", saToken).build();return chain.filter(exchange);}exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);return exchange.getResponse().setComplete();}private String generateSaToken(String originalToken) {// 调用用户中心的接口生成 Sa-TokenString url = "http://user-center/auth/check?token=" + originalToken;ResponseEntity<SaResult> response = restTemplate.getForEntity(url, SaResult.class);if (response.getStatusCode() == HttpStatus.OK && response.getBody().getCode() == 200) {return response.getBody().get("saToken").toString();}return null;}@Overridepublic int getOrder() {return -100;}
}
三、工作流程
-
用户登录:
- 用户通过用户中心的登录接口进行登录,用户中心生成原有业务系统的 Token 并返回给前端。
-
请求网关:
- 前端在请求其他服务时,将原有的 Token 添加到请求头中,发送请求到网关。
-
网关处理:
- 网关拦截请求,从请求头中获取原有的 Token。
- 网关调用用户中心的 Token 验证接口来验证原有 Token 并生成 Sa-Token。
- 将原有 Token 和 Sa-Token 的映射关系存储在 Redis 中。
- 将 Sa-Token 添加到请求头中,转发请求到后端服务。
-
后端服务:
- 后端服务可以继续保持现有的 Token 逻辑,不需要改动。所有的 Token 验证和生成逻辑都在网关和用户中心处理。
模块详细介绍
在上述方案中,实现单点登录的关键步骤包括:
- 用户中心提供接口验证原有 Token 并生成 Sa-Token:通过
/auth/check接口,用户中心接收原有业务系统的 Token,验证其有效性并生成新的 Sa-Token。 - 网关服务拦截所有请求,验证和生成 Sa-Token:网关服务在接收到请求时,首先检查请求头中的原有 Token,并调用用户中心的
/auth/check接口来验证 Token 并生成 Sa-Token,将生成的 Sa-Token 存储在 Redis 中,并添加到请求头中转发给后端服务。
这些步骤确保了用户在登录后,可以使用原有 Token 进行验证,并通过 Sa-Token 进行单点登录的验证,从而实现了单点登录的效果。
具体的实现过程
-
用户登录生成原有业务系统的 Token:
- 用户通过用户中心的登录接口进行登录。
- 用户中心生成原有业务系统的 Token 并返回给前端。
-
网关服务处理用户请求:
- 前端在请求其他服务时,将原有的 Token 添加到请求头中,发送请求到网关。
- 网关服务拦截请求,获取请求头中的原有 Token。
-
网关服务验证和生成 Sa-Token:
- 网关服务调用用户中心的
/auth/check接口,验证原有 Token 的有效性。 - 用户中心验证原有 Token 有效后,生成 Sa-Token 并返回给网关服务。
- 网关服务将原有 Token 和 Sa-Token 的映射关系存储在 Redis 中。
- 网关服务将生成的 Sa-Token 添加到请求头中,转发请求到后端服务。
- 网关服务调用用户中心的
-
后端服务处理请求:
- 后端服务可以继续保持现有的 Token 验证逻辑,不需要改动。
- 所有的 Token 验证和生成逻辑都在网关和用户中心处理。
实现细节
1. 用户中心新增 Token 验证和生成 Sa-Token 接口
@RestController
@RequestMapping("/auth")
public class AuthController {@Autowiredprivate RedisTemplate<String, String> redisTemplate;@GetMapping("/check")public ResponseEntity<SaResult> checkToken(@RequestParam String token) {// 验证原有 Tokenif (validateOriginalToken(token)) {String userId = getUserIdFromOriginalToken(token);StpUtil.login(userId);String saToken = StpUtil.getTokenValue();// 存储原有 Token 和 Sa-Token 的映射关系redisTemplate.opsForValue().set("sa-token:" + token, saToken);redisTemplate.opsForValue().set("original-token:" + saToken, token);return ResponseEntity.ok(SaResult.ok("Token 有效").set("saToken", saToken));}return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(SaResult.error("Token 无效"));}private boolean validateOriginalToken(String token) {// 验证原有业务系统的 Tokenreturn true; // 假设校验成功}private String getUserIdFromOriginalToken(String token) {// 从原有业务系统的 Token 中解析出用户IDreturn "parsed-user-id";}
}
2. 网关服务 Token 校验过滤器
@Component
public class TokenFilter implements GlobalFilter, Ordered {@Autowiredprivate RestTemplate restTemplate;@Autowiredprivate RedisTemplate<String, String> redisTemplate;@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {String originalToken = exchange.getRequest().getHeaders().getFirst("Authorization");if (originalToken != null) {String saToken = redisTemplate.opsForValue().get("sa-token:" + originalToken);if (saToken == null) {// 调用用户中心的接口生成 Sa-TokensaToken = generateSaToken(originalToken);if (saToken == null) {exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);return exchange.getResponse().setComplete();}}exchange.getRequest().mutate().header("Sa-Token", saToken).build();return chain.filter(exchange);}exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);return exchange.getResponse().setComplete();}private String generateSaToken(String originalToken) {// 调用用户中心的接口生成 Sa-TokenString url = "http://user-center/auth/check?token=" + originalToken;ResponseEntity<SaResult> response = restTemplate.getForEntity(url, SaResult.class);if (response.getStatusCode() == HttpStatus.OK && response.getBody().getCode() == 200) {return response.getBody().get("saToken").toString();}return null;}@Overridepublic int getOrder() {return -100;}
}
工作流程总结
- 用户登录并获取原有业务系统的 Token。
- 用户在请求服务时,携带原有 Token 发送请求到网关。
- 网关验证原有 Token,并通过用户中心生成 Sa-Token。
- 网关将 Sa-Token 存储在 Redis 中,并添加到请求头中转发给后端服务。
- 后端服务保持现有逻辑不变,网关和用户中心负责所有的 Token 验证和生成。
通过以上步骤,达成了在现有系统中实现的目标,同时最大限度地减少了对现有系统的改动。
五、总结
通过上述详细的改造步骤和代码示例,可以在不改动后端服务的情况下,实现单点登录。所有的 Token
验证和生成逻辑都集中在网关和用户中心,实现了 Token 的统一管理和验证。这样既实现了项目之间跳转的目标,又最大限度地减少了对现有系统的改造。
相关文章:
【Sa-Token|4】Sa-Token微服务项目应用
若微服务数量多,如果每个服务都改动,工作量大,则可以只在网关和用户中心进行改动,也是可以实现服务之间的跳转。 这种方式可以通过在网关服务中生成和验证 Sa-Token,并将其与现有的 Token关联存储在 Redis 中。用户中心…...
鸿蒙开发系统基础能力:【@ohos.hilog (日志打印)】
日志打印 hilog日志系统,使应用/服务可以按照指定级别、标识和格式字符串输出日志内容,帮助开发者了解应用/服务的运行状态,更好地调试程序。 说明: 本模块首批接口从API version 7开始支持。后续版本的新增接口,采用…...
SpringMVC系列十: 中文乱码处理与JSON处理
文章目录 中文乱码处理自定义中文乱码过滤器Spring提供的过滤器处理中文 处理json和HttpMessageConverter<T>处理JSON-ResponseBody处理JSON-RequestBody处理JSON-注意事项和细节HttpMessageConverter<T\>文件下载-ResponseEntity<T\>作业布置 上一讲, 我们学…...
使用MyBatisPlus进行字段的自动填充
使用MyBatisPlus进行字段的自动填充 需求场景 当我们往数据库里面插入一条数据,或者是更新一条数据时,一般都需要标记创建时间create_time和更新时间update_time的值,但是如果我们每张表的每个请求,在执行sql语句的时候我们都手…...
python爬虫之aiohttp多任务异步爬虫
python爬虫之aiohttp多任务异步爬虫 爬取的flash服务如下: from flask import Flask import timeapp Flask(__name__)app.route(/bobo) def index_bobo():time.sleep(2)return Hello boboapp.route(/jay) def index_jay():time.sleep(2)return Hello jayapp.rout…...
1964springboot VUE小程序在线学习管理系统开发mysql数据库uniapp开发java编程计算机网页源码maven项目
一、源码特点 springboot VUE uniapp 小程序 在线学习管理系统是一套完善的完整信息管理类型系统,结合springboot框架uniapp和VUE完成本系统,对理解vue java编程开发语言有帮助系统采用springboot框架(MVC模式开发),…...
【前端项目笔记】3 用户管理
用户管理相关功能实现 涉及表单、对话框、Ajax数据请求 基本页面 用户列表开发 在router.js中导入Users.vue 解决用户列表小问题 选中(激活)子菜单后刷新不显示高亮 给二级菜单绑定单击事件,点击链接时把对应的地址保存到sessionSto…...
【文献及模型、制图分享】基于SSP-RCP不同情景的京津冀地区土地覆被变化模拟
公众号新功能 目前公众号新增以下等功能 1、处理GIS出图、Python制图、区位图、土地利用现状图、土地利用动态度和重心迁移图等等 2、核密度分析、网络od分析、地形分析、空间分析等等 3、地理加权回归、地理探测器、生态环境质量指数、地理加权回归模型影响因素分析、计算…...
基于单片机的智能台灯控制系统
摘要: 文章设计一款单片机智能台灯控制系统,实现对台灯的手动和自动控制功能,以 STC89C52 单片机作为多功能智能台灯的主控制器,光电检测模块检测坐姿,红外传感器检测人体,光敏电阻检测光强,同…...
PrestaShop的一些使用介绍
目录 PrestaShop 是一个功能丰富的开源电子商务解决方案。 1. 以下是其基本概念和架构的一些要点: 2. PrestaShop 的模块开发是扩展其功能的重要方式。以下是对 PrestaShop 模块开发的详细介绍: 开发环境准备: 3. PrestaShop 的模块开发允…...
零基础女生如何入门人工智能,从哪里下手?学习时间大概要多久?
作为一个理工科早期毕业生,出于近乎本能的敏感,格外关注全网热议的ChatGPT。 本来国内就业环境就不好,各行各业内卷越来越严重,加上人工智能的异军突起,各行各业势必将迎来科技进步跨时代的巨大冲击,在此情…...
简答分享python学习进修网站
一、网战推荐 CodeCombat 是一款网页编程游戏。这款编程游戏借鉴了游戏很多设计元素,游戏剧情十分丰富。Codecombat能够学习Python多种语言,这些语言能够运用到游戏设计、网页应用、app的开发上。 Checkio 是一个基于浏览器的游戏,你需要使…...
linux高级编程(I/O)
fputc int fputc(int c, FILE *stream); 功能: 向流中写入一个字符 参数: c:要写入的字符 stream:文件流指针 返回值: 成功返回写入的字符ASCII码值 失败返回EOF fgetc int fgetc(FILE *stream); 功能: 从流中读取一个字符 参数: stream:文件流…...
Java面试——认证与授权
X、常见面试题汇总 1、Shiro与SpringSecutity对比 1)Shiro的特点: Shiro 是 Apache 下的项目,相对简单、轻巧,更容易上手使用。 Shiro 权限功能基本都能满足,单点登录都可以实现。且不用与任何的框架或者容器绑定, 可…...
【经典算法OJ题讲解】
1.移除元素 经典算法OJ题1: 移除元素 . - 力扣(LeetCode). - 备战技术面试?力扣提供海量技术面试资源,帮助你高效提升编程技能,轻松拿下世界 IT 名企 Dream Offer。https://leetcode.cn/problems/remove-element/desc…...
大数据面试题之Zookeeper面试题
目录 1、介绍下Zookeeper是什么? 2、Zookeeper有什么作用?优缺点?有什么应用场景? 3、Zookeeper的选举策略,leader和follower的区别? 4、介绍下Zookeeper选举算法 5、Zookeeper的节点类型有哪些?分别作用是什么? 6、Zookeeper的节点数怎么设置比较好? …...
JVM 内存区域
一、运行时数据区域 Java 虚拟机在执行 Java 程序的过程中,会把它管理的内存划分成若干个不同的数据区域。 JDK 1.8 和之前的版本略有不同,这里介绍 JDK 1.7 和 JDK 1.8 两个版本。 JDK 1.7: 线程私有: 程序计数器虚拟机栈本地…...
全网最强剖析Spring AOP底层原理
相信各位读者对于Spring AOP的理解都是一知半解,只懂使用,却不懂原理。网上关于Spring AOP的讲解层出不穷,但是易于理解,让人真正掌握原理的文章屈指可数。笔者针对这一痛点需求,决定写一篇关于Spring AOP原理的优质博…...
Vscode中的行尾序列CRLF/LF不兼容问题
最近开发的的时候,打开项目文件经常会出现爆红错误提示信息,显示如下图: 这东西太烦人了,毕竟谁都不希望在遍地都是爆红的代码里写东西,就像能解决这个问题,根据提示可以知道这是vscode中使用的prettier插件…...
常见加密方式:MD5、DES/AES、RSA、Base64
16/32位的数据,最有可能就是使用md5加密的 使用对称加密的时候,双方使用相同的私钥 私钥:单独请求/隐藏在前端的隐藏标签当中 二、RSA非对称密钥加密 公钥加密,私钥解密 私钥是通过公钥计算生成的 加密解密算法都在js源文件当…...
Prompt Tuning、P-Tuning、Prefix Tuning的区别
一、Prompt Tuning、P-Tuning、Prefix Tuning的区别 1. Prompt Tuning(提示调优) 核心思想:固定预训练模型参数,仅学习额外的连续提示向量(通常是嵌入层的一部分)。实现方式:在输入文本前添加可训练的连续向量(软提示),模型只更新这些提示参数。优势:参数量少(仅提…...
脑机新手指南(八):OpenBCI_GUI:从环境搭建到数据可视化(下)
一、数据处理与分析实战 (一)实时滤波与参数调整 基础滤波操作 60Hz 工频滤波:勾选界面右侧 “60Hz” 复选框,可有效抑制电网干扰(适用于北美地区,欧洲用户可调整为 50Hz)。 平滑处理&…...
大型活动交通拥堵治理的视觉算法应用
大型活动下智慧交通的视觉分析应用 一、背景与挑战 大型活动(如演唱会、马拉松赛事、高考中考等)期间,城市交通面临瞬时人流车流激增、传统摄像头模糊、交通拥堵识别滞后等问题。以演唱会为例,暖城商圈曾因观众集中离场导致周边…...
STM32+rt-thread判断是否联网
一、根据NETDEV_FLAG_INTERNET_UP位判断 static bool is_conncected(void) {struct netdev *dev RT_NULL;dev netdev_get_first_by_flags(NETDEV_FLAG_INTERNET_UP);if (dev RT_NULL){printf("wait netdev internet up...");return false;}else{printf("loc…...
iPhone密码忘记了办?iPhoneUnlocker,iPhone解锁工具Aiseesoft iPhone Unlocker 高级注册版分享
平时用 iPhone 的时候,难免会碰到解锁的麻烦事。比如密码忘了、人脸识别 / 指纹识别突然不灵,或者买了二手 iPhone 却被原来的 iCloud 账号锁住,这时候就需要靠谱的解锁工具来帮忙了。Aiseesoft iPhone Unlocker 就是专门解决这些问题的软件&…...
PL0语法,分析器实现!
简介 PL/0 是一种简单的编程语言,通常用于教学编译原理。它的语法结构清晰,功能包括常量定义、变量声明、过程(子程序)定义以及基本的控制结构(如条件语句和循环语句)。 PL/0 语法规范 PL/0 是一种教学用的小型编程语言,由 Niklaus Wirth 设计,用于展示编译原理的核…...
高防服务器能够抵御哪些网络攻击呢?
高防服务器作为一种有着高度防御能力的服务器,可以帮助网站应对分布式拒绝服务攻击,有效识别和清理一些恶意的网络流量,为用户提供安全且稳定的网络环境,那么,高防服务器一般都可以抵御哪些网络攻击呢?下面…...
浅谈不同二分算法的查找情况
二分算法原理比较简单,但是实际的算法模板却有很多,这一切都源于二分查找问题中的复杂情况和二分算法的边界处理,以下是博主对一些二分算法查找的情况分析。 需要说明的是,以下二分算法都是基于有序序列为升序有序的情况…...
蓝桥杯3498 01串的熵
问题描述 对于一个长度为 23333333的 01 串, 如果其信息熵为 11625907.5798, 且 0 出现次数比 1 少, 那么这个 01 串中 0 出现了多少次? #include<iostream> #include<cmath> using namespace std;int n 23333333;int main() {//枚举 0 出现的次数//因…...
关键领域软件测试的突围之路:如何破解安全与效率的平衡难题
在数字化浪潮席卷全球的今天,软件系统已成为国家关键领域的核心战斗力。不同于普通商业软件,这些承载着国家安全使命的软件系统面临着前所未有的质量挑战——如何在确保绝对安全的前提下,实现高效测试与快速迭代?这一命题正考验着…...
