Controller层自定义注解拦截request请求校验

一、背景

笔者工作中遇到一个需求,需要开发一个注解,放在controller层的类或者方法上,用以校验请求参数中(不管是url还是body体内,都要检查,有token参数,且符合校验规则就放行)是否传了一个token的参数,并且token符合一定的生成规则,符合就不予拦截,放行请求,否则拦截请求。

用法如下图所示

可以看到 @TokenCheck 注解既可以放在类上,也可以放在方法上 ,放在类上则对该类中的所有的方法进行拦截校验。

注意:是加了注解才会校验是否拦截,不加没有影响。

整个代码都是使用的最新springboot版本开发的,所以servlet相关的类都是使用jakarta

如果你的springboot版本比较老 ,请使用javax

先引入以下依赖(javax不飘红不用引入)

<dependency>

      <groupId>javax.servlet</groupId>

      <artifactId>javax.servlet-api</artifactId>

      <version>4.0.1</version>

      <scope>provided</scope>

</dependency>

 

我用到的第三方依赖

<dependency><groupId>org.apache.commons</groupId><artifactId>commons-lang3</artifactId><version>3.12.0</version>
</dependency>
<dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.8.24</version>
</dependency>
<dependency><groupId>org.springframework</groupId><artifactId>spring-webmvc</artifactId><version>6.0.11</version>
</dependency>

二、TokenCheck注解

package com.example.demo.interceptorToken;import java.lang.annotation.*;/*** 是否有token*/
@Documented
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface TokenCheck {
}

三、请求包装器RequestWrapper

主要是对request请求包装下,因为拦截器会拦截request,会读取其中的参数流,而流只能读一次,后续再用到流的读取会报错,所以用一个包装器类处理下,把流以字节形式读出来,重写了getInputStream(),后续可以重复使用。

package com.example.demo.interceptorToken;import jakarta.servlet.ReadListener;
import jakarta.servlet.ServletInputStream;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequestWrapper;
import org.apache.commons.lang3.StringUtils;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;/*** @author hulei* @date 2024/1/11 19:48* @Description 由于 request中getReader()和getInputStream()只能调用一次 导致在Controller @ResponseBody的时候获取不到 null 或 Stream closed* 在项目中,可能会出现需要针对接口参数进行校验等问题* 构建可重复读取inputStream的request*/
public class RequestWrapper extends HttpServletRequestWrapper {// 将流保存下来private final byte[] requestBody;public RequestWrapper(HttpServletRequest request) throws IOException {super(request);requestBody = readBytes(request.getReader());}@Overridepublic ServletInputStream getInputStream() {final ByteArrayInputStream basic = new ByteArrayInputStream((requestBody != null && requestBody.length >0) ? requestBody : new byte[]{});return new ServletInputStream() {@Overridepublic int read() {return basic.read();}@Overridepublic boolean isFinished() {return false;}@Overridepublic boolean isReady() {return false;}@Overridepublic void setReadListener(ReadListener readListener) {}};}@Overridepublic BufferedReader getReader() {return new BufferedReader(new InputStreamReader(getInputStream()));}/*** 通过BufferedReader和字符编码集转换成byte数组*/private byte[] readBytes(BufferedReader br) throws IOException {String str;StringBuilder retStr = new StringBuilder();while ((str = br.readLine()) != null) {retStr.append(str);}if (StringUtils.isNotBlank(retStr.toString())) {return retStr.toString().getBytes(StandardCharsets.UTF_8);}return null;}
}

四、过滤器RequestFilter

自定义请求过滤器,把请求用自定义的包装器RequestWrapper包装下,往调用下文传递,也是为了让request请求的流能多次读取

package com.example.demo.interceptorToken;
import jakarta.servlet.*;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.NonNull;
import org.springframework.core.annotation.Order;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import org.springframework.web.multipart.support.StandardServletMultipartResolver;
import java.io.IOException;/*** @author hulei* @date 2024/1/11 19:48* 自定义请求过滤器*/
//排序优先级,最先执行的过滤器
@Order(0)
public class RequestFilter extends OncePerRequestFilter {//spring6.0版本后删除了CommonsMultipartResolver,使用StandardServletMultipartResolver//如果是spring6.0版本,此行代码不报错请使用如下// private CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver();private final StandardServletMultipartResolver multipartResolver = new StandardServletMultipartResolver();/****/@Overrideprotected void doFilterInternal(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull FilterChain filterChain) throws ServletException, IOException {//请求参数有form_data的话,防止request.getHeaders()报已使用,单独处理if (request.getContentType().contains("multipart/form-data")) {MultipartHttpServletRequest multiReq = multipartResolver.resolveMultipart(request);filterChain.doFilter(multiReq, response);}else{ServletRequest requestWrapper;requestWrapper = new RequestWrapper(request);filterChain.doFilter(requestWrapper, response);}}}

五、请求过滤器配置类TokenFilterConfig

这个很好理解,把自定义配置类注入spring容器

package com.example.demo.interceptorToken;import jakarta.servlet.Filter;
import jakarta.servlet.FilterConfig;
import jakarta.servlet.ServletContext;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;import java.util.Enumeration;/*** @author hulei* @date 2024/1/11 19:48* 将过滤器注入spring容器中*/
@Configuration
public class TokenFilterConfig implements FilterConfig {@BeanFilter bodyFilter() {return new RequestFilter();}@Beanpublic FilterRegistrationBean<RequestFilter> filters() {FilterRegistrationBean<RequestFilter> filterRegistrationBean = new FilterRegistrationBean<>();filterRegistrationBean.setFilter((RequestFilter) bodyFilter());filterRegistrationBean.addUrlPatterns("/*");filterRegistrationBean.setName("requestFilter");//多个filter的时候order的数值越小 则优先级越高//filterRegistrationBean.setOrder(0);return filterRegistrationBean;}@Overridepublic String getFilterName() {return null;}@Overridepublic ServletContext getServletContext() {return null;}@Overridepublic String getInitParameter(String s) {return null;}@Overridepublic Enumeration<String> getInitParameterNames() {return null;}
}

 六、核心类RequestInterceptor拦截器

注意如果你的springboot版本也是低于3.0,请继承HandlerInterceptorAdapter类,实现其中方法,基本不用改动类中的内容,只需要 把implements HandlerInterceptor 改为extends HandlerInterceptorAdapter即可。

package com.example.demo.interceptorToken;import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import jakarta.servlet.ServletInputStream;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.NonNull;
import org.apache.commons.lang3.StringUtils;
import org.springframework.util.StreamUtils;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import org.springframework.web.multipart.support.StandardServletMultipartResolver;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Method;
import java.net.URLDecoder;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;/*** @author hulei* @date 2024/1/11 19:48* 自定义请求拦截器(spring boot 3.0以下的版本,需要继承HandlerInterceptorAdapter类,de方法)*/public class RequestInterceptor implements HandlerInterceptor {/*** 需要从请求里验证的关键字参数名*/private static final String TOKEN_STR = "token";/*** 进入拦截的方法前触发* 这里主要从打了注解请求中查找有没有token关键字,并且token的值是否符合一定的生成规则,是就放行,不是就拦截*/@Overridepublic boolean preHandle(@NonNull HttpServletRequest request,@NonNull HttpServletResponse response,@NonNull Object handler) throws Exception {if(handler instanceof HandlerMethod handlerMethod){//获取token注解TokenCheck tokenCheck = getTokenCheck(handlerMethod);//请求参数有form_data的话,防止request.getHeaders()或request.getInputStream()报已使用错误,单独处理if( request.getContentType() != null && request.getContentType().contains("multipart/form-data")){//判断当前注解是否存在if(tokenCheck != null){final StandardServletMultipartResolver multipartResolver = new StandardServletMultipartResolver();MultipartHttpServletRequest multipartHttpServletRequest = multipartResolver.resolveMultipart(request);//获取全部参数,不管是params里的还是form_data里的//Map<String,String[]> bodyParam = multipartHttpServletRequest.getParameterMap();//直接获取token参数String token = multipartHttpServletRequest.getParameter(TOKEN_STR);if(!StringUtils.isEmpty(token)){boolean tokenRuleValidation = tokenRuleValidation(token);if(!tokenRuleValidation){returnJson(response, "token校验失败");return false;}return true;}returnJson(response, "token校验失败");return false;}}else{//判断当前注解是否存在if (tokenCheck != null) {// 获取请求方式//String requestMethod = request.getMethod();// 获取请求参数Map<String,String> paramMap;//token关键字,分别是来自url的token或者来自body中的tokenString tokenFromUrl,tokenFromBody = "";request = new RequestWrapper(request);String bodyParamsStr = this.getPostParam(request);tokenFromBody = getTokenFromBody(bodyParamsStr,tokenFromBody);paramMap = getUrlQueryMap(request);tokenFromUrl = paramMap.get(TOKEN_STR);if(tokenRuleValidation(tokenFromUrl)|| tokenRuleValidation(tokenFromBody)){return true;}else {returnJson(response, "token校验失败");return false;}}}return true;}return true;}private static TokenCheck getTokenCheck(HandlerMethod handler) {Method method = handler.getMethod();//获取方法所属的类,并获取类上的@TokenCheck注解Class<?> clazz = method.getDeclaringClass();TokenCheck tokenCheck = null;if(clazz.isAnnotationPresent(TokenCheck.class)){tokenCheck = clazz.getAnnotation(TokenCheck.class);}//类上没有注解,则从方法上再获取@TokenChecktokenCheck = tokenCheck == null ? method.getAnnotation(TokenCheck.class) : tokenCheck;return tokenCheck;}/*** 从请求体获取token参数*/private String getTokenFromBody(String bodyParamsStr,String tokenFromBody){//判断是否是json数组boolean isJsonArray = JSONUtil.isTypeJSONArray(bodyParamsStr);if(!isJsonArray){tokenFromBody = JSONUtil.parseObj(bodyParamsStr).getStr(TOKEN_STR);}else{JSONArray jsonArray = JSONUtil.parseArray(bodyParamsStr);Set<String> tokenSet = new HashSet<>();for (int i = 0; i < jsonArray.size(); i++) {JSONObject jsonObject = jsonArray.getJSONObject(i);if(StringUtils.isNotEmpty(jsonObject.getStr(TOKEN_STR))){tokenSet.add(jsonObject.getStr(TOKEN_STR));}}if(!tokenSet.isEmpty()){tokenFromBody = tokenSet.stream().filter(this::tokenRuleValidation).findFirst().orElse("");}}return tokenFromBody;}/*** token 规则校验* @param token token关键字*/private boolean tokenRuleValidation(String token){return "AAABBB".equals(token);}/*** 如果是get请求,则把url中的请求参数获取到,转换为map*/public static Map<String, String> getUrlQueryMap(HttpServletRequest request) throws UnsupportedEncodingException {//获取当前请求的编码方式,用于参数value解码String encoding = request.getCharacterEncoding();String urlQueryString = request.getQueryString();Map<String, String> queryMap = new HashMap<>();String[] arrSplit;if (urlQueryString == null) {return queryMap;} else {//每个键值为一组arrSplit = urlQueryString.split("&");for (String strSplit : arrSplit) {String[] arrSplitEqual = strSplit.split("=");//解析出键值if (arrSplitEqual.length > 1) {queryMap.put(arrSplitEqual[0],URLDecoder.decode(arrSplitEqual[1], encoding));} else {if (!"".equals(arrSplitEqual[0])) {queryMap.put(arrSplitEqual[0], "");}}}}return queryMap;}/*** 离开拦截的方法后触发*/@Overridepublic void postHandle(@NonNull HttpServletRequest request,@NonNull HttpServletResponse response,@NonNull Object handler, ModelAndView modelAndView) {}/*** 返回*/private void returnJson(HttpServletResponse response, String json) throws IOException {response.setCharacterEncoding("UTF-8");response.setContentType("text/html; charset=utf-8");try (PrintWriter writer = response.getWriter()) {writer.print(json);}}private String getPostParam(HttpServletRequest request) throws Exception{RequestWrapper readerWrapper = new RequestWrapper(request);return StringUtils.isEmpty(getBodyParams(readerWrapper.getInputStream(), request.getCharacterEncoding())) ?"{}":getBodyParams(readerWrapper.getInputStream(), request.getCharacterEncoding());}/*** 获取POST、PUT、DELETE请求中Body参数**/private String getBodyParams(ServletInputStream inputStream, String charset) throws Exception {String body = StreamUtils.copyToString(inputStream, Charset.forName(charset));if (StringUtils.isEmpty(body)) {return "";}return body;}
}

七、拦截器注册InterceptorRegister

一个配置类,把自定义的拦截器注入spring

package com.example.demo.interceptorToken;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;/*** @author hulei* @date 2024/1/11 19:48* 将拦截注入spring容器*/
@Configuration
public class InterceptorRegister implements WebMvcConfigurer {@Beanpublic RequestInterceptor tokenInterceptor() {return new RequestInterceptor();}@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(tokenInterceptor());}
}

 八、总结

本例主要是自定义注解,完成请求参数的拦截校验,实际中可根据需求进行修改,如记录日志,拦截校验其他参数,修改RequestInterceptor中的拦截前方法和拦截后方法的逻辑即可

gitee地址: Token-Check-Demo: 自定义注解拦截request请求

注: 创作不易,转载请标明原作地址

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

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

相关文章

BigDecimal中使用ROUND_HALF_UP进行四舍五入

一、BigDecimal 简介 BigDecimal 类位于 java.math 包中&#xff0c;它提供了更加精确的算术运算&#xff0c;使用户完全控制舍入行为。 如果未指定舍入模式&#xff0c;并且无法表示确切的结果&#xff0c;则抛出异常; 否则&#xff0c;可以通过操作提供适当的 MathContext 对…

学生党有必要买台灯吗?央视公认最好的护眼灯

我认为学生党还是很有必要买台灯的&#xff01;现在的孩子学业压力都比较大&#xff0c;白天光线亮度比较充足&#xff0c;对眼睛没有太大影响。不过夜晚的时候周围环境的光线都逐渐暗下来&#xff0c;如果单靠室内的灯光来学习&#xff0c;那肯定是远远不够的&#xff01;不仅…

Pandas加载大数据集

Scaling to large datasets — pandas 2.1.4 documentationhttps://pandas.pydata.org/docs/user_guide/scale.html#use-efficient-datatypes官方文档提供了4种方法&#xff1a;只加载需要的列、转化数据类型、使用chunking&#xff08;转化文件存储格式&#xff09;、使用Dask…

山海鲸:助力企业实现内外数据整合与价值挖掘

作为山海鲸的开发者&#xff0c;我们深知数字化转型对于企业发展的重要性。在不断钻研如何提升山海鲸可视化这款免费产品的实用性同时&#xff0c;也在不断推出各行实用解决方案&#xff0c;本文将介绍山海鲸企业数字化转型发展解决方案&#xff0c;探讨如何通过数据驱动创新&a…

类和对象特性

#include<iostream> #include<string> using namespace std; class peron{ public:peron(string person){cout << "peron调用构造函数" << endl;tperson person;}~peron(){cout << "peron调用析构函数" << endl;}//手…

compose部署

目录 本章目标&#xff1a; 自定义网络数据库 正文&#xff1a; 注&#xff1a;创建两个网络mynet和mynetwork 1. 自定义网络-mynet 创建自定义网络&#xff1a; docker network create --subnet172.33.0.0/16 mynet 查看网络信息 docker network list 查看指定网络的详细信…

电脑提示“ureg.dll文件丢失”解决方法,ureg.dll文件下载修复安装教程

ureg.dll是Windows操作系统中的一个动态链接库文件&#xff0c;主要与Microsoft Office软件相关&#xff0c;它基本上是为了支持和启动与Office相关的程序和功能。 如果ureg.dll文件丢失或损坏&#xff0c;可能会导致与Microsoft Office相关的程序或功能无法正常工作。对于用户…

CSS中的width与height

CSS中的width与height 1 display: inline-block2 width: auto2.1 外部尺寸与流体特性2.1.1 正常流宽度2.1.2 格式化宽度 2.2 内部尺寸与流体特性2.2.1 包裹性2.2.2 首选最小宽度2.2.3 最大宽度 3 height: 100%3.1 如何让元素支持height: 100%效果 1 display: inline-block 我们…

基于振弦采集仪的地下工程振动监测技术研究

基于振弦采集仪的地下工程振动监测技术研究 地下工程振动监测技术是为了监测地下工程施工过程中产生的振动而进行的研究。振弦采集仪是一种常用的地下工程振动监测设备&#xff0c;它通过固定在地下工程附近的振弦仪来实时采集工程施工过程中产生的振动信号。 基于振弦采集仪的…

@Transactional注解导致@DS切换数据源失效

原因 spring 的Transactional声明式事务管理时通过动态代理实现的。 删除事物的注解 增加其他数据库的事务注解 Transactional(rollbackFor Exception.class, propagation Propagation.REQUIRES_NEW)

如何使用iPad通过Code App+cpolar实现公网地址远程访问vscode

&#x1f308;个人主页: Aileen_0v0 &#x1f525;热门专栏: 华为鸿蒙系统学习|计算机网络|数据结构与算法 ​&#x1f4ab;个人格言:“没有罗马,那就自己创造罗马~” 文章目录 1. 在iPad下载Code APP2.安装cpolar内网穿透2.1 cpolar 安装2.2 创建TCP隧道 3. iPad远程vscode4. …

ED UV灯FCC认证的辐射与传导整改实例

摘要&#xff1a;某型LED UV灯出口美国&#xff0c;因此需要满足美国FCC标准要求。常规来说这个UV灯是需要测试FCC PART18标准要求的。但是&#xff0c;这个虽然是uv灯&#xff0c;但是利用的紫外线图层改变led的发光&#xff0c;而不是标准里面的定义的uv灯是放电灯&#xff0…

推荐一款低成本半桥驱动器集成电路 SIC631CD-T1-GE3

SIC631CD-T1-GE3 是经过优化的集成功率级解决方案用于同步降压应用&#xff0c;提供大电流、高电压效率高&#xff0c;功率密度高。使电压调节器设计能够提供高达50 A的电流每相持续电流。内部功率MOSFET利用Vishay的最先进的第四代TrenchFET技术行业基准绩效将显著降低开关和传…

JS-DOM树和DOM对象

作用和分类 作用&#xff1a;就是使用JS去操作html和浏览器 分类&#xff1a;DOM&#xff08;文档对象模型&#xff09;、BOM&#xff08;浏览器对象模型&#xff09; 什么是DOM DOM&#xff08;Document Object Model--文档对象模型&#xff09;是用来呈现以及与任意HTML或…

动手搓一个kubernetes管理平台(1)-需求和框架

先拍个脑袋 市面上对于kubernetes集群的管理平台其实不算少&#xff0c;但常用的就那么几个&#xff0c;比如厚重的rancher&#xff0c;比如老而弥坚的kube-dashboard&#xff0c;以及集成了很多其他功能的kubeSphere等&#xff0c;但和其他开源项目一样&#xff0c;为了满足大…

使用Python编写一个渗透测试探测工具

本篇将会涉及&#xff1a; 资源探测一个有用的字典资源第一个暴力探测器 资源探测 资源探测在渗透测试中还是属于资源的映射和信息的收集阶段。 主要有以下三个类型&#xff1a; 字典攻击暴力破解模糊测试 字典攻击&#xff0c;在破解密码或密钥的时候&#xff0c;通过自定…

Spark---RDD依赖关系

文章目录 1.1 RDD依赖关系1.2 血缘关系1.3 依赖关系分类1.3.1 窄依赖1.3.2 宽依赖 1.4 RDD阶段划分和任务划分1.4.1 RDD阶段划分1.4.2 RDD任务划分 1.1 RDD依赖关系 在Spark中&#xff0c;一个RDD的形成依赖于另一个RDD&#xff0c;则称这两个RDD具有依赖关系(一般指相邻的两个…

创始人记|2023「年终总结」暨 WorkfineV6版本发布

大家好&#xff0c;我是Workfine黄坤&#xff0c;很高兴能够再次为大家进行分享。衷心感谢大家一直以来对我们的厚爱和支持。新的一年&#xff0c;祝愿大家财源滚滚&#xff0c;身体健康&#xff0c;阖家幸福。 这是我们创业的第七个年头&#xff0c;进入这一行也有整整十年。…

减肥或者运动真的能改变面相吗?

减肥或运动真的能改变面相吗&#xff1f; 在我们日常生活中&#xff0c;经常可以听到一些关于面相的说法&#xff0c;比如“胖的人更有福气”、“瘦的人更有灵气”等等。那么&#xff0c;减肥或运动是否真的能够改变一个人的面相呢&#xff1f;下面我们就来探讨一下这个问题。…

高精度磁导航传感器MGS系列RS232|RS485|CANBUS通讯连线方法

高精度磁导航传感器MGS系列&#xff0c;包含&#xff1a;CNS-MGS-080N、CNS-MGS-160N等&#xff0c;具有1mm的检测精度&#xff0c;特别适应于⾼精度磁条导航。利⽤检测磁场相对位置来进⾏AGV的辅助定位对接&#xff0c;获得更⾼的导航、定位、驻⻋精度。 MGS系列磁导航传感器⽀…