Sa-Token实现网关统一鉴权和内部服务外网隔离

🎈 1 参考文档

网管关统一鉴权 | sa-token.cc


🥩2 微服务中使用Sa-Token依赖引入说明

2.1 Sa-Token依赖

对于网关服务,大体来讲分为两种:

  • 一种是基于Servlet模型的,如:Zuul,我们需要引入的是:sa-token-spring-boot-starter
  • 一种是基于Reactor模型的,如:SpringCloud Gateway、ShenYu 等等,我们需要引入的是:sa-token-reactor-spring-boot-starter并且注册全局过滤器!

注意:切不可直接在一个项目里同时引入这两个依赖,否则会造成项目无法启动

2.1.1 基于Servlet模型

<!-- Sa-Token 权限认证,在线文档:https://sa-token.cc -->
<dependency><groupId>cn.dev33</groupId><artifactId>sa-token-spring-boot-starter</artifactId><version>1.35.0.RC</version>
</dependency>

注意:如果你使用的是 SpringBoot 3.x,只需要将 sa-token-spring-boot-starter 修改为 sa-token-spring-boot3-starter 即可。

2.1.2 基于Reactor模型

<!-- Sa-Token 权限认证(Reactor响应式集成),在线文档:https://sa-token.cc -->
<dependency><groupId>cn.dev33</groupId><artifactId>sa-token-reactor-spring-boot-starter</artifactId><version>1.35.0.RC</version>
</dependency>

注意:如果你使用的是 SpringBoot 3.x,只需要将 sa-token-reactor-spring-boot-starter 修改为 sa-token-reactor-spring-boot3-starter 即可。

2.2 Redis依赖

无论使用哪种序列化方式,你都必须为项目提供一个 Redis 实例化方案,因为我们需要和各个服务通过Redis来同步数据。

2.2.1 jdk 默认序列化方式

优点:兼容性好,缺点:Session 序列化后基本不可读,对开发者来讲等同于乱码。

<!-- Sa-Token 整合 Redis (使用 jdk 默认序列化方式) -->
<dependency><groupId>cn.dev33</groupId><artifactId>sa-token-redis</artifactId><version>1.35.0.RC</version>
</dependency>
<!-- 提供Redis连接池 -->
<dependency><groupId>org.apache.commons</groupId><artifactId>commons-pool2</artifactId>
</dependency>

2.2.2 jackson 序列化方式

优点:Session 序列化后可读性强,可灵活手动修改,缺点:兼容性稍差。

<!-- Sa-Token 整合 Redis (使用 jackson 序列化方式) -->
<dependency><groupId>cn.dev33</groupId><artifactId>sa-token-redis-jackson</artifactId><version>1.35.0.RC</version>
</dependency>
<!-- 提供Redis连接池 -->
<dependency><groupId>org.apache.commons</groupId><artifactId>commons-pool2</artifactId>
</dependency>

🚀3 和jwt集成

3.1 引入依赖

<!-- Sa-Token 整合 jwt -->
<dependency><groupId>cn.dev33</groupId><artifactId>sa-token-jwt</artifactId><version>1.35.0.RC</version>
</dependency>
  1. 注意: sa-token-jwt 显式依赖 hutool-jwt 5.7.14 版本,保险起见:你的项目中要么不引入 hutool,要么引入版本 >= 5.7.14 的 hutool 版本。
  2. hutool 5.8.13 和 5.8.14 版本下会出现类型转换问题,关联issue。

3.2 配置秘钥

application.yml 配置文件中配置 jwt 生成秘钥:

sa-token:# jwt秘钥 jwt-secret-key: asdasdasifhueuiwyurfewbfjsdafjk

3.3 注入jwt实现

根据不同的整合规则,插件提供了三种不同的模式,你需要选择其中一种注入到你的项目中

@Configuration
public class SaTokenConfigure {// Sa-Token 整合 jwt (Simple 简单模式)@Beanpublic StpLogic getStpLogicJwt() {return new StpLogicJwtForSimple();}// Sa-Token 整合 jwt (Mixin 混入模式)@Beanpublic StpLogic getStpLogicJwt() {return new StpLogicJwtForMixin();}// Sa-Token 整合 jwt (Stateless 无状态模式)@Beanpublic StpLogic getStpLogicJwt() {return new StpLogicJwtForStateless();}
}

🚀4 实现鉴权接口

关于数据的获取,建议以下方案三选一:

  1. 在网关处集成ORM框架,直接从数据库查询数据。
  2. 先从Redis中获取数据,获取不到时走ORM框架查询数据库。
  3. 先从Redis中获取缓存数据,获取不到时走RPC调用子服务 (专门的权限数据提供服务) 获取。

放到登录服务中:

/*** 自定义权限验证接口扩展*/
@Component
public class StpInterfaceImpl implements StpInterface {@Autowiredprivate MenuService menuService;@Autowiredprivate RoleService roleService;@Autowiredprivate ThreadPoolConfig threadPoolConfig;/*** 返回一个账号所拥有的权限码集合** @param loginId* @param loginType* @return*/@Overridepublic List<String> getPermissionList(Object loginId, String loginType) {List<String> res = (List<String>) StpUtil.getTokenSession().get("PERMISSION-LIST");if (res == null) {CompletableFuture<List<String>> permissionFuture = CompletableFuture.supplyAsync(() -> {List<MenuVO> menuVOList = menuService.getPermissionList(Convert.toLong(loginId),null);return menuVOList.stream().map(MenuVO::getPermission).collect(Collectors.toList());}, threadPoolConfig.USER_ROLE_PERM_THREAD_POOL);try {return permissionFuture.get();} catch (InterruptedException | ExecutionException e) {throw new RuntimeException(e);}}return res;}/*** 返回一个账号所拥有的角色标识集合** @param loginId* @param loginType* @return*/@Overridepublic List<String> getRoleList(Object loginId, String loginType) {List<String> res = (List<String>) StpUtil.getTokenSession().get("ROLE-LIST");if (res == null) {CompletableFuture<List<String>> roleFuture = CompletableFuture.supplyAsync(() -> {// 返回此 loginId 拥有的权限列表List<RoleVO> roles = roleService.getRoleByUserId(Convert.toLong(loginId));return roles.stream().map(RoleVO::getRoleKey).collect(Collectors.toList());}, threadPoolConfig.USER_ROLE_PERM_THREAD_POOL);try {return roleFuture.get();} catch (InterruptedException | ExecutionException e) {throw new RuntimeException(e);}}return res;}
}

其他子服务:

/*** 自定义权限验证接口扩展*/
@Component
public class StpInterfaceImpl implements StpInterface {/*** 返回一个账号所拥有的权限码集合** @param loginId* @param loginType* @return*/@Overridepublic List<String> getPermissionList(Object loginId, String loginType) {return (List<String>) StpUtil.getTokenSession().get("PERMISSION-LIST");}/*** 返回一个账号所拥有的角色标识集合** @param loginId* @param loginType* @return*/@Overridepublic List<String> getRoleList(Object loginId, String loginType) {return (List<String>) StpUtil.getTokenSession().get("ROLE-LIST");}
}

🚀5 注册全局过滤器

然后在网关处注册全局过滤器进行鉴权操作

@Configuration
public class SaTokenConfigure{/*** Sa-Token 整合 jwt (Simple 简单模式)** @return*/@Beanpublic StpLogic getStpLogicJwt() {return new StpLogicJwtForSimple();}/*** 注册 [Sa-Token全局过滤器]** @return*/@Beanpublic SaReactorFilter getSaReactorFilter() {return new SaReactorFilter()// 拦截地址 - 拦截全部path.addInclude("/**")// 开放地址.addExclude("/favicon.png")// 鉴权方法:每次访问进入// 全局认证函数.setAuth(obj -> {SaRouter// 拦截的所有接口.match("/**")// 忽略所有登陆相关接口.notMatch("/account/**")// 忽略所有个人信息相关接口.notMatch("/manage/profile/**")// 忽略获得文件下载链接相关接口.notMatch("/file/fileTransfer/getFileUrl/**")// 忽略获取站点信息列表相关接口.notMatch("/manage/config/get")// 忽略获取所有的公告相关接口.notMatch("/manage/notice/list")// 忽略所有接口文档相关接口.notMatch("/doc.html","/doc.html*","/doc.html/*","/webjars/**","/img.icons/**","/swagger-resources/**","/**/v2/api-docs")// 要执行的校验动作,可以写完整的 lambda 表达式.check(r -> StpUtil.checkLogin());})// 异常处理函数.setError(e -> {return ResultResponse.fail().message(e.getMessage());})// 前置函数:在每次认证函数之前执行.setBeforeAuth(obj -> {// ---------- 设置跨域响应头 ----------SaHolder.getResponse()// 允许指定域访问跨域资源.setHeader("Access-Control-Allow-Origin", "*")// 允许所有请求方式.setHeader("Access-Control-Allow-Methods", "POST, GET, PUT, DELETE, OPTIONS")// 有效时间.setHeader("Access-Control-Max-Age", "3600")// 允许的header参数.setHeader("Access-Control-Allow-Headers", "*");// 如果是预检请求,则立即返回到前端SaRouter.match(SaHttpMethod.OPTIONS).free(r -> System.out.println("--------OPTIONS预检请求,不做处理--------")).back();});}
}

🚀6 使用Same-Token模块提供的身份校验能力,完成服务间的权限认证

6.1 网关处添加Same-Token

为网关添加全局过滤器:此过滤器会为Request请求头追加 Same-Token 参数,这个参数会被转发到子服务。

/*** 全局过滤器,为请求添加 Same-Token */
@Component
public class ForwardAuthFilter implements GlobalFilter {@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {ServerHttpRequest newRequest = exchange.getRequest().mutate()// 为请求追加 Same-Token 参数 .header(SaSameUtil.SAME_TOKEN, SaSameUtil.getToken()).build();ServerWebExchange newExchange = exchange.mutate().request(newRequest).build();return chain.filter(newExchange);}
}

6.2 在子服务里校验参数

在子服务添加过滤器校验参数。

/*** Sa-Token 权限认证 配置类 */
@Configuration
public class SaTokenConfigure implements WebMvcConfigurer {// 注册 Sa-Token 全局过滤器 @Beanpublic SaServletFilter getSaServletFilter() {return new SaServletFilter().addInclude("/**").addExclude("/favicon.ico").setAuth(obj -> {// 校验 Same-Token 身份凭证     —— 以下两句代码可简化为:SaSameUtil.checkCurrentRequestToken(); String token = SaHolder.getRequest().getHeader(SaSameUtil.SAME_TOKEN);SaSameUtil.checkToken(token);}).setError(e -> {return SaResult.error(e.getMessage());});}
}

启动网关与子服务,访问测试:

如果通过网关转发,可以正常访问,直接访问子服务会提示:无效Same-Token:xxx

6.3 服务间内部调用鉴权

有时候我们需要在一个服务调用另一个服务的接口,这也是需要添加Same-Token作为身份凭证的

在服务里添加 Same-Token 流程与网关类似,我们以RPC框架 Feign 为例:

6.3.1 首先在调用方添加 FeignInterceptor

/*** feign拦截器, 在feign请求发出之前,加入一些操作 */
@Component
public class FeignInterceptor implements RequestInterceptor {// 为 Feign 的 RCP调用 添加请求头Same-Token @Overridepublic void apply(RequestTemplate requestTemplate) {requestTemplate.header(SaSameUtil.SAME_TOKEN, SaSameUtil.getToken());// 如果希望被调用方有会话状态,此处就还需要将 satoken 添加到请求头中// requestTemplate.header(StpUtil.getTokenName(), StpUtil.getTokenValue());}
}

6.3.2 在调用接口里使用此Interceptor

/*** 在调用接口里使用 Interceptor*/
@FeignClient(name = "netdisk-account", configuration = FeignInterceptor.class)
public interface AccountFeignService {/*** 返回一个账号所拥有的权限码集合** @return*/@RequestMapping("/menu/getPermissionList/")ResultResponse<List<MenuVO>> getPermissionList();/*** 根据用户ID获取详细信息** @param userId* @return*/@RequestMapping("/role/getRoleByUserId/{userId}")ResultResponse<List<RoleVO>> getRoleByUserId(@PathVariable Long userId);
}

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

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

相关文章

整理mongodb文档:分页

个人博客 整理mongodb文档:分页 个人博客&#xff0c;求关注&#xff0c;如果文章不够清晰&#xff0c;麻烦指出。 文章概叙 本文主要讲下在聚合以及crud的find方法中如何使用limit还有skip进行排序。 分页的情况很经常出现&#xff0c;这也是这篇博客诞生的理由。 数据准备…

Vue组件之间传值

聊一聊vue里面组件之间的传值 首先总结一下vue里面传值的几种关系&#xff1a; 如上图所示, A与B、A与C、B与D、C与F组件之间是父子关系&#xff1b; B与C之间是兄弟关系&#xff1b;A与D、A与E之间是隔代关系&#xff1b; D与F是堂兄关系&#xff0c;针对以上关系 我们把组件…

Redis 缓存穿透击穿和雪崩

一、说明 Redis 缓存的使用&#xff0c;极大的提升了应用程序的性能和效率&#xff0c;特别是数据查询方面。但同时&#xff0c;它也带来了一些问题。其中&#xff0c;最要害的问题&#xff0c;就是数据的一致性问题&#xff0c;从严格意义上讲&#xff0c;这个问题无解。如果对…

c++ 回调函数,std::function,std::bind

回调函数 回调函数的创建步骤大概为&#xff1a; 声明一个函数指针类型。拟写使用回调函数的函数&#xff0c;将函数指针类型及变量名声明作为参数传递。拟写符合函数指针类型的实现函数&#xff0c;将实现函数的指针作为参数传递给使用它的函数。 定义回调函数的指针类型&a…

【CPP_Primer_Plus】学习助手

学习网站推荐 cppreference learncpp cplusplus tutorialspoint awesomecpp stackoverflow 视频课程推荐 码农论坛 cpp primer plus

Nginx基础+高级(2022版):待更新

1. 文章说明 说明&#xff1a;目前讲的是第一部分nginx核心技术篇&#xff0c;后需篇章会以第一部分为核心技术篇为基础来展开深度讲解&#xff0c;详情关注后续课程的发布。 2. 介绍和准备环境 2.1 介绍 Nginx (engine x) 是一个高性能的HTTP和反向代理web服务器&#xf…

动态维护直径 || 动态维护树上路径 || 涉及LCA点转序列 || 对欧拉环游序用数据结构维护:1192B

https://www.luogu.com.cn/problem/CF1192B 对于直径的求法&#xff0c;常用dp或两次dfs&#xff0c;但如果要动态维护似乎都不太方面&#xff0c;那么可以维护树上路径最大值。 树上路径为&#xff1a; d e p u d e p v − 2 d e p l c a ( u , v ) dep_udep_v-2\times de…

iPhone 15 Pro展示设计:7项全新变化呈现

我们不应该再等iPhone 15 Pro在苹果9月12日的“Wonderlust”活动上发布了&#xff0c;而且可能会有很多升级。有传言称&#xff0c;iPhone 15 Pro将是自iPhone X以来最大的飞跃&#xff0c;这要归功于大量的新变化&#xff0c;从带有更薄边框的新钛框架到顶级A17仿生芯片和动作…

[管理与领导-70]:IT基层管理者 - 辅助技能 - 4- 职业发展规划 - 个人的能力盘点

目录 前言&#xff1a; 一、什么是能力&#xff08;What&#xff09; 1.1 什么是能力 1.2 能力类型 1.3 技能矩阵 二、优势与劣势模型 2.1 优势与劣势 2.2 SWOT模型 三、人才结构模型 3.1 广度优先&#xff1a;一字型/全面型人才 3.2 深度优先&#xff1a;I型人才、…

Django框架中使用drf框架开发

一、drf框架特点&#xff1a; 全称 Django REST framework 两大部分&#xff1a;序列化/反序列化 和 增删改查序列化&#xff1a;把数据库数据提取出来变成python常用格式的过程&#xff0c;例如转成json格式这种反序列化&#xff1a;把数据写入到数据库的过程&#xff0c…

浅谈一下酒吧和酒馆的不同

相信有很多朋友还不怎么清楚酒吧和酒馆的区别是什么&#xff0c;这里为大家简单介绍一下两者的不同&#xff0c;个人见解&#xff0c;如有错漏&#xff0c;欢迎指出。一、首先是他们的经营范围不同酒馆经营通常包含酒水和餐饮&#xff0c;适合朋友聚会或者是和商业伙伴聊天。而…

230903文本docx

处理文本 块级项目,每次文本超出右边界时都会添加一行.对段落,边界一般是页边距,但如果按列布局页,则也可是列边界,如果表格单元格内有段,则也可是单元格边界. 块级项属性指定其在页上的位置,如缩进项及段落前后间距.内联项属性一般指定显示内容的如字样,字体大小,粗体和斜体…

QLoRA:量化LLM的高效微调策略与实践

如果你对这篇文章感兴趣&#xff0c;而且你想要了解更多关于AI领域的实战技巧&#xff0c;可以关注「技术狂潮AI」公众号。在这里&#xff0c;你可以看到最新最热的AIGC领域的干货文章和案例实战教程。 一、前言 在大型语言模型&#xff08;LLM&#xff09;领域&#xff0c;微…

JavaScript单例模式

JavaScript单例模式 1 什么是单例模式2 实现一个基础的单例模式3 透明的单例模式4 用代理实现单例模式5 JavaScript 中的单例模式6 惰性单例 1 什么是单例模式 保证一个类只有一个实例&#xff0c;并提供一个访问它的全局访问点&#xff0c;这就是单例模式。 单例模式是一种常…

(位运算) 剑指 Offer 15. 二进制中1的个数 ——【Leetcode每日一题】

❓ 剑指 Offer 15. 二进制中1的个数 难度&#xff1a;简单 编写一个函数&#xff0c;输入是一个无符号整数&#xff08;以二进制串的形式&#xff09;&#xff0c;返回其二进制表达式中数字位数为 ‘1’ 的个数&#xff08;也被称为 汉明重量).&#xff09;。 提示&#xff…

计算机网络自顶向下-web页面请求历程

1. 准备: DHCP、 UDP、 IP 和以太网 假定 Bob 启动他的便携机&#xff0c;然后将其用一根以太网电缆连接到学校的以太网交换机 &#xff0c; 交换机与学校的路由器相连。学校的路由器与一个 ISP 连接&#xff0c; 本例中 ISP 为 comcast.net &#xff0c;为学校提供了 DNS 服务…

开发前期准备工作

开发前期准备工作 文章目录 开发前期准备工作0 代码规范0.1 强制0.2 推荐0.3 参考dao&#xff1a;跟数据库打交道service&#xff1a;业务层&#xff0c;人类思维解决controller&#xff1a;抽象化 0.4 注释规范0.5 日志规范0.6 专有名词0.7 控制层统一异常统一结构体控制层提示…

java线程和go协程

一、线程的实现 线程的实现方式主要有三种&#xff1a;内核线程实现、用户线程实现、用户线程加轻量级进程混合实现。因为自己只对java的线程比较熟悉一点&#xff0c;所以主要针对java线程和go的协程之间进行一个对比。 线程模型主要有三种&#xff1a;1、内核级别线程&#…

Unittest自动化测试框架vs Pytest自动化测试框架

引言   前面一篇文章Python单元测试框架介绍已经介绍了python单元测试框架&#xff0c;大家平时经常使用的是unittest&#xff0c;因为它比较基础&#xff0c;并且可以进行二次开发&#xff0c;如果你的开发水平很高&#xff0c;集成开发自动化测试平台也是可以的。而这篇文章…

如何培养潜在客户?看完这篇你就懂了

图片来源于&#xff1a;SaleSmartly官网 有效的潜在客户培育策略将帮助您将更多潜在客户转化为付费客户。 但是&#xff0c;这并不总是那么容易——您必须与其他公司争夺受众的注意力&#xff0c;并向您的领导证明为什么值得投资您的产品或服务。在本文中&#xff0c;我将向您展…