【Sa-Token|4】Sa-Token微服务项目应用

news2024/12/28 5:36:01

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

在这里插入图片描述

实现方案

为了实现这个目标,同时尽量减少对现有系统的改动,可以在网关和用户中心进行必要的改动,以确保用户在登录后能够在多个系统中无缝访问。

下面是详细的方案和步骤:

一、用户中心改造

用户中心需要提供两个接口:

  1. 登录接口:用户登录并生成原有业务系统的 Token。
  2. 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); // 生成原有业务系统的 Token
            result.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) {
        // 生成原有业务系统的 Token
        return "original-token";
    }
}
2. Token 验证和 Sa-Token 生成接口
@RestController
@RequestMapping("/auth")
public class AuthController {

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    @GetMapping("/check")
    public ResponseEntity<SaResult> checkToken(@RequestParam String token) {
        // 验证原有 Token
        if (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) {
        // 验证原有业务系统的 Token
        return true; // 假设校验成功
    }

    private String getUserIdFromOriginalToken(String token) {
        // 从原有业务系统的 Token 中解析出用户ID
        return "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: localhost
    port: 6379
    password: ""
3. 网关服务 Token 校验过滤器
@Component
public class TokenFilter implements GlobalFilter, Ordered {

    @Autowired
    private RestTemplate restTemplate;

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    @Override
    public 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-Token
                saToken = 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-Token
        String 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;
    }

    @Override
    public int getOrder() {
        return -100;
    }
}

三、工作流程

  1. 用户登录

    • 用户通过用户中心的登录接口进行登录,用户中心生成原有业务系统的 Token 并返回给前端。
  2. 请求网关

    • 前端在请求其他服务时,将原有的 Token 添加到请求头中,发送请求到网关。
  3. 网关处理

    • 网关拦截请求,从请求头中获取原有的 Token。
    • 网关调用用户中心的 Token 验证接口来验证原有 Token 并生成 Sa-Token。
    • 将原有 Token 和 Sa-Token 的映射关系存储在 Redis 中。
    • 将 Sa-Token 添加到请求头中,转发请求到后端服务。
  4. 后端服务

    • 后端服务可以继续保持现有的 Token 逻辑,不需要改动。所有的 Token 验证和生成逻辑都在网关和用户中心处理。

模块详细介绍

在上述方案中,实现单点登录的关键步骤包括:

  1. 用户中心提供接口验证原有 Token 并生成 Sa-Token:通过 /auth/check 接口,用户中心接收原有业务系统的 Token,验证其有效性并生成新的 Sa-Token。
  2. 网关服务拦截所有请求,验证和生成 Sa-Token:网关服务在接收到请求时,首先检查请求头中的原有 Token,并调用用户中心的 /auth/check 接口来验证 Token 并生成 Sa-Token,将生成的 Sa-Token 存储在 Redis 中,并添加到请求头中转发给后端服务。

这些步骤确保了用户在登录后,可以使用原有 Token 进行验证,并通过 Sa-Token 进行单点登录的验证,从而实现了单点登录的效果。

具体的实现过程

  1. 用户登录生成原有业务系统的 Token

    • 用户通过用户中心的登录接口进行登录。
    • 用户中心生成原有业务系统的 Token 并返回给前端。
  2. 网关服务处理用户请求

    • 前端在请求其他服务时,将原有的 Token 添加到请求头中,发送请求到网关。
    • 网关服务拦截请求,获取请求头中的原有 Token。
  3. 网关服务验证和生成 Sa-Token

    • 网关服务调用用户中心的 /auth/check 接口,验证原有 Token 的有效性。
    • 用户中心验证原有 Token 有效后,生成 Sa-Token 并返回给网关服务。
    • 网关服务将原有 Token 和 Sa-Token 的映射关系存储在 Redis 中。
    • 网关服务将生成的 Sa-Token 添加到请求头中,转发请求到后端服务。
  4. 后端服务处理请求

    • 后端服务可以继续保持现有的 Token 验证逻辑,不需要改动。
    • 所有的 Token 验证和生成逻辑都在网关和用户中心处理。

实现细节

1. 用户中心新增 Token 验证和生成 Sa-Token 接口
@RestController
@RequestMapping("/auth")
public class AuthController {

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    @GetMapping("/check")
    public ResponseEntity<SaResult> checkToken(@RequestParam String token) {
        // 验证原有 Token
        if (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) {
        // 验证原有业务系统的 Token
        return true; // 假设校验成功
    }

    private String getUserIdFromOriginalToken(String token) {
        // 从原有业务系统的 Token 中解析出用户ID
        return "parsed-user-id";
    }
}
2. 网关服务 Token 校验过滤器
@Component
public class TokenFilter implements GlobalFilter, Ordered {

    @Autowired
    private RestTemplate restTemplate;

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    @Override
    public 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-Token
                saToken = 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-Token
        String 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;
    }

    @Override
    public int getOrder() {
        return -100;
    }
}

工作流程总结

  1. 用户登录并获取原有业务系统的 Token。
  2. 用户在请求服务时,携带原有 Token 发送请求到网关。
  3. 网关验证原有 Token,并通过用户中心生成 Sa-Token。
  4. 网关将 Sa-Token 存储在 Redis 中,并添加到请求头中转发给后端服务。
  5. 后端服务保持现有逻辑不变,网关和用户中心负责所有的 Token 验证和生成。

通过以上步骤,达成了在现有系统中实现的目标,同时最大限度地减少了对现有系统的改动。

五、总结

通过上述详细的改造步骤和代码示例,可以在不改动后端服务的情况下,实现单点登录。所有的 Token
验证和生成逻辑都集中在网关和用户中心,实现了 Token 的统一管理和验证。这样既实现了项目之间跳转的目标,又最大限度地减少了对现有系统的改造。

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

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

相关文章

Springboot整合Mongodb(含使用案例)

基础语法 插入 插入单条 // 插入一条数据到 "Books" 集合 db.Books.insertOne({title: "如何使用MongoDB",author: "IT小辉同学",year: 2023 })插入多条数据 // 插入十条数据到 "Books" 集合 db.Books.insertMany([{ title: "…

面试必备:10种分布式ID的生成方案

前言 日常工作中&#xff0c;我们开发的系统、或者中间件&#xff0c;都是分布式部署的。比如你的订单数据库&#xff0c;做了分库分表&#xff0c;这时候&#xff0c;你需要一个唯一的ID来标记一条数据。这时候&#xff0c;就需要分布式ID。分布式ID是在分布式系统下使用的ID…

冻干食品市场飙升至新高度,预计到 2030 年将达到 717 亿美元

冻干食品市场&#xff0c;近年来经历了显著增长&#xff0c;2021 年价值 372 亿美元&#xff0c;预计到 2030 年将达到 717 亿美元。 从2022年到2030年&#xff0c;这一强劲的扩张是由7.7%的复合年增长率推动的&#xff0c;这是由于多种因素造成的&#xff0c;包括食品加工行…

2023 联邦推荐系统综述

本博客结合2023年发表的综述文章&#xff0c;对近期一些联邦推荐文章进行总结&#xff0c;综述原文&#xff1a; SUN Z, XU Y, LIU Y, et al. A Survey on Federated Recommendation Systems[J]. 2023.https://doi.org/10.48550/arXiv.2301.00767 引言 最近&#xff0c;已有许多…

基于SpringBoot小区物业智能卡管理设计和实现(源码+LW+调试文档+讲解等)

&#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN作者、博客专家、全栈领域优质创作者&#xff0c;博客之星、平台优质作者、专注于Java、小程序技术领域和毕业项目实战✌&#x1f497; &#x1f31f;文末获取源码数据库&#x1f31f;感兴趣的可以先收藏起来&#xff0c;还…

金融科技在智能投研领域的革新与未来趋势

金融科技作为当今金融行业的重要驱动力&#xff0c;其在智能投研领域的应用正逐渐改变传统的投资研究模式&#xff0c;引领着行业向更加智能、高效的方向发展。本文将从不同角度探讨金融科技在智能投研领域的革新及未来趋势。 一、技术革新&#xff1a;从数据分析到智能决策 金…

shell条件测试语句

条件测试&#xff1a; $&#xff1f; 返回码 判断命令或者脚本是否执行成功 为0 true 为真就是成功 非0 fales 失败或者异常 test命令 可以进行条件测试&#xff0c;然后根据返回值来判断条件是否成立 -e &#xff1a;测试文件或者目录是否存在 -d &#xff1a;只能测试…

酸纯化器酸蒸馏器提取系统亚沸腾高纯酸蒸馏硝酸盐酸氢氟酸

一、产品简介 酸纯化器&#xff1a;亦称高纯酸蒸馏纯化器、酸蒸馏器、亚沸腾蒸馏装置&#xff0c;简称酸纯化器(南京-瑞尼克-科技)是超净化实验产品&#xff0c;后期提取的高纯酸、高纯水可以配套我们公司Teflon系列的器皿使用。 二、工作原理 酸纯化器是利用热辐射原理&…

Mac清理系统数据小技巧,告别卡顿烦恼 苹果电脑清理内存怎么清理

任何使用Mac的用户都会同意&#xff1a;没有什么比一台运行缓慢的电脑更能消磨人的耐心了。那些无休止的彩球旋转、程序响应迟缓、突然的系统冻结&#xff0c;这一切都让人想抓狂&#xff01;但别担心&#xff0c;这里有一些简单的Mac清理系统数据小技巧和CleanMyMac X的神助攻…

Codeforces Round 954 (Div.3)

传送门 A. X Axis 时间限制&#xff1a;2秒 空间限制&#xff1a;256MB 输入&#xff1a;标准输入 输出&#xff1a;标准输出 问题描述 你有三个位于 X 轴上的整数坐标点 , 和 &#xff08;其中 &#xff09;。你可以选择 X 轴上任意一个整数坐标点 …

Windows环境如何ssh远程连接本地局域网内的Archcraft系统

文章目录 前言1. 本地SSH连接测试2. Archcraft安装Cpolar3. 配置 SSH公网地址4. 公网远程SSH连接小结 5. 固定SSH公网地址6. SSH固定地址连接 前言 本文主要介绍如何在Archcraft系统中安装Cpolar内网穿透工具,并以实现Windows环境ssh远程连接本地局域网Archcraft系统来说明使用…

区块链实验室(37) - 交叉编译百度xuperchain for arm64

纠结了很久&#xff0c;终于成功编译xuperchain for arm64。踩到1个坑&#xff0c;说明如下。 1、官方文档是这么说的&#xff1a;go语言版本推荐1.5-1.8 2、但是同一个页面&#xff0c;又是这么说的&#xff1a;不推荐使用1.11之前的版本。 3、问题来了&#xff1a;用什么版本…

数据归档与清理功能大幅升级,NineData重磅升级!

NineData 的数据归档与清理功能已经发布将近半年时间&#xff0c;在这段时间里&#xff0c;解决了一大批企业在过期数据处理方面的大麻烦&#xff0c;可以相对自动化地定期执行如下数据归档与清理工作。 识别业务库中的归档数据→移动到归档库→清理业务库的已归档数据→整理业…

【力扣C++】爬楼梯

假设你正在爬楼梯。需要 n 阶你才能到达楼顶。 每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢&#xff1f; 示例 1&#xff1a; 输入&#xff1a;n 2 输出&#xff1a;2 解释&#xff1a;有两种方法可以爬到楼顶。 1. 1 阶 1 阶 2. 2 阶 示例 2&#x…

解决Playwright在Ubuntu下启动报错的问题:从环境到依赖的全面优化

在Ubuntu环境中使用Python进行web自动化测试时,Playwright是一个非常强大的工具。然而,在具体实践中,我们常常会遇到各种错误,尤其是在不同Python版本和依赖版本之间切换时。本文将详细介绍如何应对这些问题,并提供一些解决方法。 问题背景 在使用Playwright时,我们有时…

docker-compose离线安装harbor

1、下载harbor goharbor下载&#xff1a;Releases goharbor/harbor GitHub harbor-offline-installer-v2.11.0.tgz 2、解压 tar -xvf harbor-offline-installer-v2.11.0.tgz 3、创建一个卷目录&#xff0c;并复制一份配置文件 cd harbor; mkdir data;cp harbor.yml.tmp…

Linux 安装、配置Tomcat 的HTTPS

Linux 安装 、配置Tomcat的HTTPS 安装Tomcat 这里选择的是 tomcat 10.X ,需要Java 11及更高版本 Binary Distributions ->Core->选择 tar.gz包 下载、上传到内网服务器 /opt 目录tar -xzf 解压将解压的根目录改名为 tomat-10 并移动到 /opt 下, 形成个人习惯的路径 /…

[Linux]缓冲区

一、概念 缓冲区&#xff0c;也称为缓存&#xff0c;是内存空间的一部分。也就是说&#xff0c;在内存空间中预留了一定的存储空间&#xff0c;用来缓冲输入或输出的数据。这个保留的空间称为缓冲区。 缓冲区的主要作用就是提高效率&#xff1a; 提高使用者的效率&#xff0…

【吊打面试官系列-Mysql面试题】NULL 是什么意思?

大家好&#xff0c;我是锋哥。今天分享关于 【NULL 是什么意思&#xff1f;】面试题&#xff0c;希望对大家有帮助&#xff1b; NULL 是什么意思? 答&#xff1a;NULL 这个值表示 UNKNOWN(未知):它不表示“”(空字符串)。对 NULL 这个值的任何比较都会生产一个 NULL 值。您不能…

Qt项目天气预报(6) - 引入QMap: debug+更新天气图片

QMAP 解决bug (&#xff09; bug描述 由于json文件中有的地方不带市&#xff0c;有的地方带县&#xff0c;有的地方是区&#xff0c;我们匹配不上这个地方&#xff0c;我们可以使用QString进行字符串拼接来实现。 另外&#xff0c;我们之前的 getCityCodeFromName()函数写得…