这次也是依然在学习开源项目Tduck-填鸭收集器的时,阿昌在研究这项目是如何进行安全校验的,我一开始在项目里面查Shiro/SpringSecurity,我以为他使用了市面主流的安全框架,但是发现,他根本没有使用,而是自定义了一系列的 拦截器&过滤器 来实现安全的校验。
比如,通过自定义注解来决定这个资源是否需要用户登录才能够访问。
在项目中我发现的自定义注解有三个
- @Login
- @LoginUser
- @NoRepeatSubmit
在开始前,放上SpringMVC的执行流程 镇场: (●′ω`●)!!!

•́ . •̀) 下面是我简单的记录我在项目中,学习到的内容,且我对于这个项目的这3个注解的理解!!!*
@Login
com.tduck.cloud.api.annotation.Login
我先看的是@Login这个注解,通过这个注解去控制这个controller资源是需要在需要登录才能调用的接口使用
给他定义的是:
描述方法/运行时生效
1 | /** |
我在想他是如何生效的呢???
很容易让我想到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
通过这个注解去控制这个某个参数封装用户信息
给他定义的是:
描述参数/运行时生效
1 | /** |
在项目中查找@LoginUser注解,发现在com.tduck.cloud.api.web.resolver.LoginUserHandlerMethodArgumentResolver中,他实现了HandlerMethodArgumentResolver参数解析器,他就是解析请求发来的参数并解析成对应我们controller中对应方法参数,比如@RequestBody、@RequestParam等
【 HandlerMethodArgumentResolver的小文章 •‾̑⌣‾̑•) 】

需要获取到请求来的参数,并封装。
但是这里需要自定义,因为SpringMVC只封装对应的请求体,这里就直接自定义写了一个封装器
1 |
|
最后写完了肯定要加入到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
最后一个注解,通过它来控制不允许重复提交
给他定义的是:
描述方法、描述类&接口/运行时生效
1 | /** |
如果你观察仔细,你在上面就会已经发现了这个注解的拦截器跟@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