基于抽象 HandlerInterceptor 快速实现接口鉴权

欢迎关注公众号:冬瓜白

相关文章:

  • 每天学习一点点之 Spring Web MVC 之抽象 HandlerInterceptor 快速实现常用功能(限流、权限等)

在[每天学习一点点之 Spring Web MVC 之抽象 HandlerInterceptor 快速实现常用功能(限流、权限等)](vscode-file://vscode-app/Applications/Visual Studio Code.app/Contents/Resources/app/out/vs/code/electron-sandbox/workbench/workbench.html)中已经介绍过可以基于抽象的 HandlerInterceptor 来实现很多常见的功能。本文快速实现了类似于 Shiro 的鉴权注解 @RequiresPermissions,并且功能更强大。

定义权限注解:

/*** @author Dongguabai* @description* @date 2024-06-25 10:57*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Documented
public @interface RequiresPermissions {/*** 当前接口需要的权限(如 ADMIN,USER)* @return*/String[] value();/*** 对象id字段属性名称(默认id)*/String key() default "id";
}

在这个例子中使用了 @RequiresPermissions 注解来指定这个接口需要的权限。指定了两种权限:ADMIN 和USER。意味着只有拥有 ADMIN 或 USER 权限的用户才能访问这个接口。还指定了key为"id",也就是说会从请求参数中获取名为"id"的参数,并使用这个参数来进行额外的权限检查。

这里 key 的作用是,比如有的目标对象只能由特定的用户去操作,这里的 key 就提供了这样一种方式。

继承 CustomizedHandlerMethodInterceptor 实现鉴权逻辑:

/*** @author dongguabai* @date 2024-06-25 10:58*/
@Component
public class RequiresPermissionsHandlerMethodInterceptor extends CustomizedHandlerMethodInterceptor<RequiresPermissions> {private static final Logger LOGGER = LoggerFactory.getLogger(RequiresPermissionsHandlerMethodInterceptor.class);@Overrideprotected boolean preHandle(HttpServletRequest request, HttpServletResponse response,HandlerMethod handlerMethod, RequiresPermissions annotation) throws Exception {//获取当前登陆用户BaseUser user = getLogin();if (user == null) {LOGGER.error("Unable to get login information");return false;}String key = annotation.key();String[] value = annotation.value();if (StringUtils.isBlank(key) || ArrayUtils.isEmpty(value)) {return true;}//获取目标idLong id = getId(request, handlerMethod, key);if (id != null) {//用户鉴权return checkUserPermission(response, user, value, id);}return true;}private Long getId(HttpServletRequest request, HandlerMethod handlerMethod, String key) throws IOException {CachingWrapper requestWrapper = new CachingWrapper(request);MethodParameter[] methodParameters = handlerMethod.getMethodParameters();Long id = null;for (MethodParameter methodParameter : methodParameters) {id = getidFromParameter(request, key, methodParameter, requestWrapper);if (id != null) {break;}}return id;}private Long getidFromParameter(HttpServletRequest request, String key,MethodParameter methodParameter, CachingWrapper requestWrapper) throws IOException {String parameterName = methodParameter.getParameterName();if (key.equals(parameterName)) {return Long.valueOf(request.getParameter(parameterName));} else if (methodParameter.getParameterAnnotation(RequestBody.class) != null) {return getIdFromBody(key, requestWrapper);}return null;}private Long getIdFromBody(String key, CachingWrapper requestWrapper) throws IOException {ObjectMapper mapper = new ObjectMapper();JsonNode rootNode = mapper.readTree(requestWrapper.getCachedBody());JsonNode idNode;if (rootNode.isArray() && rootNode.size() > 0) {idNode = rootNode.get(0).path(key);} else {idNode = rootNode.path(key);}if (!idNode.isMissingNode()) {return idNode.asLong();}return null;}private boolean checkUserPermission(HttpServletResponse response, BaseUser user, String[] value, Long id) {// 业务鉴权逻辑return true;}@Overrideprotected void afterCompletion(HttpServletRequest request, HttpServletResponse response,HandlerMethod handlerMethod, RequiresPermissions annotation, Exception ex) {// Do nothing}@Overrideprotected void postHandle(HttpServletRequest request, HttpServletResponse response,HandlerMethod handlerMethod, ModelAndView modelAndView, RequiresPermissions annotation) {// Do nothing}/*** 获取当前登陆用户*/private BaseUser getLogin() {return null;}
}
/*** @author dongguabai* @date 2024-06-25 17:33*/
public class CachingWrapper extends HttpServletRequestWrapper {private byte[] cachedBody;public CachingWrapper(HttpServletRequest request) throws IOException {super(request);InputStream requestInputStream = request.getInputStream();ByteArrayOutputStream cachedBodyOutputStream = new ByteArrayOutputStream();byte[] buffer = new byte[1024];int length;while ((length = requestInputStream.read(buffer)) != -1) {cachedBodyOutputStream.write(buffer, 0, length);}this.cachedBody = cachedBodyOutputStream.toByteArray();}@Overridepublic ServletInputStream getInputStream() throws IOException {return new CachedBodyServletInputStream(this.cachedBody);}public byte[] getCachedBody() {return this.cachedBody;}private static class CachedBodyServletInputStream extends ServletInputStream {private ByteArrayInputStream cachedBodyInputStream;CachedBodyServletInputStream(byte[] cachedBody) {this.cachedBodyInputStream = new ByteArrayInputStream(cachedBody);}@Overridepublic boolean isFinished() {return this.cachedBodyInputStream.available() == 0;}@Overridepublic boolean isReady() {return true;}@Overridepublic void setReadListener(ReadListener readListener) {throw new UnsupportedOperationException();}@Overridepublic int read() throws IOException {return this.cachedBodyInputStream.read();}}
}

在鉴权逻辑中,需要从请求参数中获取目标id。这是因为权限检查可能需要根据这个id来进行。

例如可能需要检查用户是否有权限访问这个id对应的资源。为了获取这个id,需要解析请求参数。这个过程可能会比较复杂,因为请求参数可能以不同的方式传递,例如,它们可能在URL的查询字符串中,或者在POST请求的请求体中。因此这里提供了getId方法来处理这些情况,并尽可能地获取到id。

这里比较麻烦的是从接口中解析 id 参数,这里支持两种方式:

  1. 当前接口调用者需要有目标对象 ID为传入的 id 的 OWNER 权限:
@PostMapping("/count")
@ResponseBody
@RequiresPermissions("OWNER")
public Response count(Long id) {return Response.getSuccess(count(projectId));
}
  1. 当前接口调用者需要有项目ID为传入的 project.id 项目的 USE 权限:
@PostMapping("/list")
@ResponseBody
@RequiresPermissions("USE")
public Response list(@RequestBody Project project) {return Response.getSuccess(list(project);
}

getId 方法用于解析请求中的参数,包括URL的查询参数和POST请求的请求体参数。getIdFromBody 方法专门用于解析POST请求的请求体参数。通过这种方式,可以灵活地控制用户的访问权限。

总结

本文主要探讨了基于抽象的 HandlerInterceptor 来实现鉴权注解 @RequiresPermissions,它可以灵活地控制用户的访问权限。

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

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

相关文章

Numpy的广播机制(用于自动处理不同形状的数组)

NumPy 广播是一种强大的机制&#xff0c;允许 NumPy 在执行元素级运算时自动处理不同形状的数组。广播的规则使得无需显式地创建匹配形状的数组&#xff0c;直接进行运算&#xff0c;大大简化了代码并提高了效率。 基本概念 广播的基本思想是让较小的数组在需要的维度上进行扩…

【MySQL数据库之概念性问题】

1、关系型数据库和非关系型数据库 关系型数据库&#xff08;Relational Database&#xff0c;简称RDBMS&#xff09;和非关系型数据库&#xff08;NoSQL Database&#xff09;是两种不同的数据库类型。SQL本身叫做结构化查询语言1、关系型数据库&#xff1a;&#xff08;MySQL…

Django 更新数据 save()方法

1&#xff0c;添加模型 Test/app11/models.py from django.db import modelsclass Post(models.Model):title models.CharField(max_length200)content models.TextField()pub_date models.DateTimeField(date published)class Book(models.Model):title models.CharFie…

Spring Boot集成grpc快速入门demo

1.什么是GRPC&#xff1f; gRPC 是一个高性能、开源、通用的RPC框架&#xff0c;由Google推出&#xff0c;基于HTTP2协议标准设计开发&#xff0c;默认采用Protocol Buffers数据序列化协议&#xff0c;支持多种开发语言。gRPC提供了一种简单的方法来精确的定义服务&#xff0c…

UE5.3-基础蓝图类整理一

常用蓝图类整理&#xff1a; 1、获取当前关卡名&#xff1a;Get Current LevelName 2、通过关卡名打开关卡&#xff1a;Open Level(by name) 3、碰撞检测事件&#xff1a;Event ActorBeginOverlap 4、获取当前player&#xff1a;Get Player Pawn 5、判断是否相等&#xff1…

深入解析CSS中的!important规则:优先级与最佳实践

先上实践&#xff0c;再讨论设计 在实际工程中&#xff0c;!important 的使用场景通常出现在需要确保某个样式规则具有最高优先级&#xff0c;以覆盖其他可能冲突的样式规则时。以下是一个具体的例子&#xff1a; 场景描述 假设你正在开发一个网站&#xff0c;该网站使用了多…

JavaScript的数组与函数

数组 <script type"text/javascript">/** 知识点&#xff1a;数组* 理解&#xff1a;一维数组的容器* 概念&#xff1a;* 1.数组中的数据叫做元素* 2.元素都有编号叫做下标/索引* 3.下标从0开始* 注意&#xff1a;* 1.数组作为数据的容器…

【JavaScript脚本宇宙】状态管理利器:JavaScript 库全面解析

提升项目效率与可维护性&#xff1a;JavaScript 状态管理库大揭秘 前言 在现代前端开发中&#xff0c;状态管理是一个至关重要的话题。随着复杂性的增加&#xff0c;有效地管理应用程序的状态变得越来越具有挑战性。本文将介绍一些流行的 JavaScript 库&#xff0c;这些库提供…

WEB安全基础:网络安全常用术语

一、攻击类别 漏洞&#xff1a;硬件、软件、协议&#xff0c;代码层次的缺陷。 后⻔&#xff1a;方便后续进行系统留下的隐蔽后⻔程序。 病毒&#xff1a;一种可以自我复制并传播&#xff0c;感染计算机和网络系统的恶意软件(Malware)&#xff0c;它能损害数据、系统功能或拦…

C++语言学习精简笔记(包含C++20特性)

目录 1 C新语法C与CC编译运行String编程范式C基础类型**自动类型推导**统一对象初始化&#xff1a;Uniform Initialization 控制结构if语句for语句switch语句namespace 2 函数函数声明形式参数函数参数传递的选择函数返回值的选择 函数重载 Lambda表达式函数的定义和申明生存期…

磁力猫磁力搜索大全教程,如何使用磁力链接

磁力链接是一种特殊的下载链接&#xff0c;磁力链接可以理解为一个文件识别码&#xff0c;而并非具体的资源地址&#xff0c;下载软件需要拿着这个识别码去整个互联网(DHT网络)去寻找持有该资源的用户(节点)&#xff0c;如果找到则可以进行传输下载。一般年代越久远的磁力链接下…

【一】m2芯片的mac中安装ubuntu24虚拟机集群

文章目录 1. 虚拟机配置2. 复制虚拟机2.1 修改主机名2.2 修改网络 1. 虚拟机配置 在官方网站下载好ubuntu24-arm版镜像开始安装&#xff0c;安装使用VMWare Fusion的社区免费授权版,使用一台m2芯片的mac电脑作为物理机平台。 为什么选择ubuntu24&#xff1f;因为centOS7目前已…

Proteus + Keil单片机仿真教程(五)多位LED数码管的静态显示

Proteus + Keil单片机仿真教程(五)多位LED数码管 上一章节讲解了单个数码管的静态和动态显示,这一章节将对多个数码管的静态显示进行学习,本章节主要难点: 1.锁存器的理解和使用; 2.多个数码管的接线封装方式; 3.Proteus 快速接头的使用。 第一个多位数码管示例 元件…

『C + ⒈』‘\‘

&#x1f942;在反斜杠(\)有⒉种最常用的功能如下所示&#x1f44b; #define _CRT_SECURE_NO_WARNINGS 1 #include<stdio.h> int main(void) {int a 10;int b 20;int c 30;if (a 10 &&\b 20 &&\c 30){printf("Your print\n");}else{prin…

二分查找3

1. 有序数组中的单一元素&#xff08;540&#xff09; 题目描述&#xff1a; 算法原理&#xff1a; 二分查找解题关键就在于去找到数组的二段性&#xff0c;这里数组的二段性是从单个数字a开始出现然后分隔出来的&#xff0c;如果mid落入左半部分那么当mid为偶数时nums[mid1]…

ByteMD富文本编辑器的vue3配置

Git地址&#xff1a;GitHub - bytedance/bytemd: ByteMD v1 repository 控制面板输入 npm install bytemd/vue-next 下载成功后在src/main.ts中引用 import "bytemd/dist/index.css";引入后保存&#xff0c;下面是一些插件&#xff0c;比如说我用到gmf和hightLight&…

java后端向jsp传日期,jsp调用数据错误问题

问题 今天遇到个bug&#xff0c;后端使用request.setAttribute("key", value);将startDate、endDate两个日期字符串传递到jsp中&#xff0c;使jsp可以获取到日期进行查询操作。但接口拼接的参数startDate为2017&#xff0c;endDate为1986&#xff0c;让人百思不得其…

彩色图像(RGB)或灰度图像(Gray)转tensor数据(附img2tensor代码)

&#x1f4aa; 专业从事且热爱图像处理&#xff0c;图像处理专栏更新如下&#x1f447;&#xff1a; &#x1f4dd;《图像去噪》 &#x1f4dd;《超分辨率重建》 &#x1f4dd;《语义分割》 &#x1f4dd;《风格迁移》 &#x1f4dd;《目标检测》 &#x1f4dd;《暗光增强》 &a…

homebrew常用命令

Homebrew 提供了许多命令和选项来管理软件包。以下是一些常用的 Homebrew 命令&#xff1a; ### 常用 Homebrew 命令 1. **安装软件包**&#xff1a; brew install <软件包名称> 2. **卸载软件包**&#xff1a; brew uninstall <软件包名称> 3. **更…

CompletableFuture工具类使用

CompletableFuture工具类可以帮助实现Java并发编程中的任务编排 以上除了join用于阻塞调用该方法的线程并且接受CompletableFuture的返回值以外其它方法皆有Async异步和Executor指定线程池选项 对于supply,run,apply,accept的区别在于函数式编程的接口类型不同: supply: Sup…