阿昌教你自定义拦截器&自定义参数解析器&自定义包装HttpServletRequest
Posted 阿昌喜欢吃黄桃
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了阿昌教你自定义拦截器&自定义参数解析器&自定义包装HttpServletRequest相关的知识,希望对你有一定的参考价值。
前言
这次也是依然在学习开源项目Tduck-填鸭收集器
的时,阿昌在研究这项目是如何进行安全校验的,我一开始在项目里面查Shiro
/SpringSecurity
,我以为他使用了市面主流的安全框架,但是发现,他根本没有使用,而是自定义了一系列的 拦截器&过滤器 来实现安全的校验。
比如,通过自定义注解
来决定这个资源是否需要用户登录才能够访问。
在项目中我发现的自定义注解有三个
- @Login
- @LoginUser
- @NoRepeatSubmit
在开始前,放上SpringMVC的执行流程
镇场: (●′ω`●)!!!
•́ . •̀) 下面是我
简单的记录我
在项目中,学习到的内容,且我
对于这个项目的这3个注解的理解!!!*
@Login
com.tduck.cloud.api.annotation.Login
我先看的是@Login
这个注解,通过这个注解去控制这个controller资源是需要在需要登录才能调用的接口使用
给他定义的是:
描述方法
/运行时生效
/**
* 登录验证 在需要登录才能调用的接口使用
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Login
我在想他是如何生效的呢???
很容易让我想到AOP的实现,于是我就去翻我 AOP的笔记
都知道AOP需要3个要点 【 1. 额外功能 2. 切⼊点 3. 组装切⾯ 】,但是我在这个项目里面找,发现并没有找到,所以就排除
了他这个注解是通过AOP实现的方式。
那他是通过什么去给这个注解写入对应的判断逻辑呢??? 我找了一圈,发现
在com.tduck.cloud.api.web.interceptor.AuthorizationInterceptor
这里有一个拦截器
他继承了HandlerInterceptorAdapter
,重写了preHandle()
方法,那这里我就纳闷了,HandlerInterceptorAdapter也是 拦截器吗???于是我就按下Ctrl+H
,看一下类关系图。
这里他是通过适配器设计模式
实现,
他们这个AuthorizationInterceptor
就是HandlerInterceptor
接口下的一个实现类,所以就可以重写HandlerInterceptor的一系列方法。
那到了这里,还是没说出来他是如何去根据@Login
注解去判断对应的逻辑,再往下看。
在AuthorizationInterceptor中他重写了preHandle
方法,
他在方法中判断是否是HandlerMethod
,如果是就强转
,并通过getMethodAnnotation(Login.class)
,去获取到他是否有Login.class
,那个Login.class是什么???
看了下方法名,你会马上看出来,这个Login.class就是上面说的@Login
注解
那这里有一个问题,HandlerMethod
是什么东西?他为什么会能有getMethodAnnotation()
方法去拿到给他传入的.class
的注解呢?
那这里就要引出SpringMVC
了,如果你学了它,你就会知道HandlerMethod
,其实就对应Handler
,也就是我们写的那些Controller
那知道了这个之后,根据上面的handler instanceof HandlerMethod
来判断他是不是controller,如果是,那就可以拿到controller上面所标记的注解,
以上就可以看到了@Login
所进行的流程,最后写了以上的还是不行的,我们需要将这个拦截器加入到spring的拦截器链表
中也就是上面的图,让他起作用
查找了下代码,他在com.tduck.cloud.api.config.WebMvcConfig
中实现了WebMvcConfigurer
重写了addInterceptors(InterceptorRegistry registry)
方法,把上面写的登录校验加入拦截器链中,并指定拦截所有请求!
这里的最后,查看他随便
使用到@Login
注解的地方,所以标注了它后会在拦截器中进行对token的校验,来判断用户是否登录
@LoginUser
com.tduck.cloud.api.annotation.LoginUser
通过这个注解去控制这个某个参数封装用户信息
给他定义的是:
描述参数
/运行时生效
/**
* 登录用户信息
*/
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface LoginUser
在项目中查找@LoginUser
注解,发现在com.tduck.cloud.api.web.resolver.LoginUserHandlerMethodArgumentResolver
中,他实现了HandlerMethodArgumentResolver
参数解析器,他就是解析请求发来的参数并解析成对应我们controller中对应方法参数,比如@RequestBody、@RequestParam等
【 HandlerMethodArgumentResolver的小文章 •‾̑⌣‾̑•) 】
需要获取到请求来的参数,并封装。
但是这里需要自定义,因为SpringMVC只封装对应的请求体,这里就直接自定义写了一个封装器
@Component
public class LoginUserHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver
private final UserService userService;
//通过构造依赖,需要的userService
public LoginUserHandlerMethodArgumentResolver(UserService userService)
this.userService = userService;
//这个方法返回的T/F,会直接决定下面的resolveArgument()方法是否能够被执行
@Override
public boolean supportsParameter(MethodParameter parameter)
//拿到这个请求参数的类型
//判断他是不是UserEntity类,并判断他是不是有被注解@LoginUser标注
return parameter.getParameterType().isAssignableFrom(UserEntity.class) && parameter.hasParameterAnnotation(LoginUser.class);
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer container,
NativeWebRequest request, WebDataBinderFactory factory) throws Exception
//从请求域中拿到用户的id
//这个用户的id是在 【@Login的拦截器里面最后放入到请求域中的】
Object object = request.getAttribute(AuthorizationInterceptor.USER_KEY, RequestAttributes.SCOPE_REQUEST);
if (object == null)
return null;
//获取用户信息,并返回,返回后spring会自动给UserEntity类封装上查询到的信息
UserEntity user = userService.getById((Long) object);
return user;
最后写完了肯定要加入到MVC
的ArgumentResolvers参数解析器s
中,
在com.tduck.cloud.api.config.WebMvcConfig
这样子,主要标注了@LoginUser
的注解就会被这个参数解析器解析,并自动去查询获取封装对应的用户数据
同样这里的最后,也查看羡慕使用到@LoginUser
注解的地方,发现他使用的方式跟我们常用的各种封装数据类型的注解
一模一样
com.tduck.cloud.api.web.controller.UserController
@NoRepeatSubmit
com.tduck.cloud.api.annotation.NoRepeatSubmit
最后一个注解,通过它来控制不允许重复提交
给他定义的是:
描述方法、描述类&接口
/运行时生效
/**
* 不允许重复提交注解
*/
@Target(ElementType.METHOD, ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface NoRepeatSubmit
如果你观察仔细,你在上面就会已经发现了这个注解的拦截器跟@Login
的拦截器在同一个包下
com.tduck.cloud.api.web.interceptor.NoRepeatSubmitInterceptor
不同的是,他是直接实现的HandlerInterceptor
,而@Login的拦截器是继承
HandlerInterceptor的一个实现类HandlerInterceptorAdapter
【 HandlerInterceptor&HandlerInterceptorAdapter区别 】 ´•.̫ • `
它先判断是否是HandlerMethod
,就是判断是不是Handler处理器,如果不是,就直接结束返回true;
再强转
,并再判断他是否又被@NoRepeatSubmit
标注,如果有就继续,不然就直接结束返回true;
在这里看到,他通过自定义的方式跟@Login
是一样的 (ฅ´ω`ฅ)!!!
然后判断是否是BodyReaderHttpServletRequestWrapper
那BodyReaderHttpServletRequestWrapper
是什么东西???
关联打开后发现代码很多
!!! 且发现他继承了HttpServletRequestWrapper
那问题就转移了,就是HttpServletRequestWrapper
是什么??? 【 HttpServletRequestWrapper类的作用 】
这里简单的概括就是因为我们很多时候都要改变HttpServletRequest
,但是由于java.util.Map包装的HttpServletRequest
对象的参数是不可改变,所以通过 【装饰者设计模式 】包装来改变其状态,这样子只需要在装饰类HttpServletRequestWrapper
中,按照需要重写其对应的方法即可
com.tduck.cloud.api.web.wrapper.BodyReaderHttpServletRequestWrapper
进行了对原HttpServletRequest包装装饰
,给每一个参数内容都加上了XSS过滤
,
回到之前上面,通过继承HttpServletRequestWrapper
类来,包装进行XSS过滤
,请求Request,
那是什么时机
进行对其XSS过滤呢???
在全项目搜索BodyReaderHttpServletRequestWrapper
,发现在SignAuthFilter
进行了包装过滤XSS
com.tduck.cloud.api.web.filter.SignAuthFilter
再一次回来,然后获取对应的数据,判断redis中是否存在
,有就拦截,没有就给redis设置过期时间为2s
,避免了重复提交
同样,他肯定也需要加入到MVC的拦截器链
中,com.tduck.cloud.api.config.WebMvcConfig
在最后,还是依然的查看他使用到@NoRepeatSubmit
注解的地方,
com.tduck.cloud.api.web.controller.UserProjectResultController
这里是业务逻辑是填写表单等,通过@NoRepeatSubmit,来避免重复提交
总结
以上的内容就全部记录完毕了,感谢你能认认真真看完,一定会有大量的收获!!!
这里涉及到了大量的MVC知识点 •̀∀•́ )!!!
- 适配器设计模式
- MVC如何新增自定义拦截器
- HandlerMethodArgumentResolver 自定义参数解析器
- HandlerInterceptorAdapter 和 HandlerInterceptor区别
- 装饰者设计模式
- HttpServletRequestWrapper 自定义包装HttpServletRequest
以上是关于阿昌教你自定义拦截器&自定义参数解析器&自定义包装HttpServletRequest的主要内容,如果未能解决你的问题,请参考以下文章