相关文章推荐
痴情的海龟  ·  springboot jsp编译时,抛出 ...·  2 月前    · 
知识渊博的热带鱼  ·  这才是 SpringBoot ...·  1 月前    · 
豪气的双杠  ·  Spring Webflux - aop ...·  4 周前    · 
从未表白的遥控器  ·  如何在Java服务中实现自动化的健康检查与自 ...·  1 周前    · 
爱听歌的凉面  ·  spring启动时只执行一次的方法实现_sp ...·  5 天前    · 
失眠的烤地瓜  ·  Libtorch报错:terminate ...·  7 月前    · 
豪爽的皮带  ·  【报错】AttributeError: ...·  10 月前    · 
爱旅游的遥控器  ·  ASP.NET Core 中的会话 | ...·  1 年前    · 
很酷的蚂蚁  ·  在Node.js中如何逐行读取文件_node ...·  1 年前    · 
大力的馒头  ·  mybatis 利用 ...·  1 年前    · 
Code  ›  Spring Security addFilter() 顺序问题_addfilterat_猫吻鱼的博客
token spring框架
https://blog.csdn.net/qq_36882793/article/details/102869583
眉毛粗的柳树
2 年前
  • 一、 分析
    • 0. FilterComparator
    • 1. HttpSecurity#addFilterAt
      • 1.1 `this.comparator.registerAt(filter.getClass(), atFilter);`
      • 1.2 `HttpSecurity#addFilter(Filter filter)`
    • 二、总结:

    一、 分析

    在上面 Spring Security + Jwt 项目 的搭建过程中,我一直有一个问题。我们假设我们使用addFilterAt(A, B.class)。 即将A拦截器添加到B拦截器的位置。那么addFilterAt 既然没有覆盖原先的拦截器,那么A不是在B拦截器前面就是在B拦截器后面,那么岂不是和addFilterBefore 或者 addFilterAfter 重复了?

    然而元芳告诉我没那么简单。 最下面有总结,不想看源码的小伙伴可以直接看总结。

    通过断点看到。我们通过添加拦截器的代码是这个样子的。
    在这里插入图片描述
    那么我们看源码:

    0. FilterComparator

    为了更好的理解后面说的内容,我们需要先了解一下 FilterComparator 这个类。顾名思义,这个类是一个过滤器比较器。可以看到如下
    在这里插入图片描述

    其比较的规则如下, getOrder是根据类名来获取到过滤器的序号。可以看到过滤器的排序是根据排序序号的大小来排序的,序号小的在前,大的灾后。

    	public int compare(Filter lhs, Filter rhs) {
    		Integer left = getOrder(lhs.getClass());
    		Integer right = getOrder(rhs.getClass());
    		return left - right;
    

    我们这里拿 addFilterAt 举例,addFilterBefore 和 addFilterAfter 类似

    1. HttpSecurity#addFilterAt

    在这里插入图片描述
    一句一句看,先看第一句

    1.1 this.comparator.registerAt(filter.getClass(), atFilter);

    这里的 方法是 在 FilterComparator#registerAt 中
    这里可以看到,当我们使用addFilterAt 添加过滤器时,他添加的排序序号是和我们指定的拦截器相同的。()

    public void registerAt(Class<? extends Filter> filter, Class<? extends Filter> atFilter) { // 获取 atFilter 的顺序 Integer position = getOrder(atFilter); if (position == null) { throw new IllegalArgumentException( "Cannot register after unregistered Filter " + atFilter); // 将filter放入 filterToOrder 中, filterToOrder 是一个map集合 put(filter, position); .... private void put(Class<? extends Filter> filter, int position) { String className = filter.getName(); filterToOrder.put(className, position); // 调用 addFilterBefore 时使用这个方法注册,这里可以看到排序序号比指定过滤器要小1 public void registerBefore(Class<? extends Filter> filter, Class<? extends Filter> beforeFilter) { Integer position = getOrder(beforeFilter); if (position == null) { throw new IllegalArgumentException( "Cannot register after unregistered Filter " + beforeFilter); put(filter, position - 1); // 调用 addFilterAfter 时使用这个方法注册,这里可以看到排序序号比指定过滤器要大1 public void registerAfter(Class<? extends Filter> filter, Class<? extends Filter> afterFilter) { Integer position = getOrder(afterFilter); if (position == null) { throw new IllegalArgumentException( "Cannot register after unregistered Filter " + afterFilter); put(filter, position + 1);

    简单来说,就是我们 添加的 jwtLoginFilter 拦截器和 UsernamePasswordAuthenticationFilter 的序号是相同的。

    1.2 HttpSecurity#addFilter(Filter filter)

    代码很简单,如下,将添加的过滤器加到 filters 集合中

    	public HttpSecurity addFilter(Filter filter) {
    		Class<? extends Filter> filterClass = filter.getClass();
    		if (!comparator.isRegistered(filterClass)) {
    			throw new IllegalArgumentException(
    					"The Filter class "
    							+ filterClass.getName()
    							+ " does not have a registered order and cannot be added without a specified order. Consider using addFilterBefore or addFilterAfter instead.");
    		this.filters.add(filter);
    		return this;
    

    通过断点我们还可以发现,我们手动添加的过滤器是先于大部分系统过滤器被加入到filters集合中的。这一点可以从断点中看到
    在这里插入图片描述

    另外从 HttpSecurity#performBuild 方法中我们可以知道,filters 在添加完所有过滤器后,使用 FilterComparator 比较器进行了比较。结果呼之欲出。

    二、总结:

    1. FilterComparator 比较器中初始化了Spring Security 自带的Filter 的顺序,即在创建时已经确定了默认Filter的顺序。并将所有过滤器保存在一个 filterToOrder Map中。key值是Filter的类名,value是过滤器的顺序号。
    2. 当我们调用 HttpSecurity#addFilterAt(A, B.class) 方法时(其中B一定是先于A添加,或者B本身就是默认的过滤器),他会将我们的添加的过滤器A在 FilterComparator ,并给给我们一个和B相同的序号(addFilterBefore(A, B.class) 给A的序号比B小1,addFilterAfter(A, B.class) 给A的序号比B大1)。同时,HttpSecurity#addFilter(Filter filter) 会将我们添加的过滤器添加在 filters List集合中, 而在List集合汇总我们手动添加的拦截器在除了 WebAsyncManagerIntegrationFilter 之外的所有系统默认的拦截器之前。
    3. 最后Spring Security 会调用HttpSecurity#performBuild 方法,在这里会使用 FilterComparator 比较器对 filters进行比较排序,序号小的在前,序号大的在后,序号相等则按照原先的filters中的顺序。
    4. 由于在 filters List集合中,我们自己添加的过滤器要在除了 WebAsyncManagerIntegrationFilter 之外的所有系统默认的拦截器之前。导致了当我们调用了 HttpSecurity#addFilterAt(A, B.class) 方法时,A拦截器要先于B拦截器执行。

    举例(为了好理解,纯属假设):

    1. 假设Spring Security 里面默认拦截器 有A、B、C 三个(在强调一遍,假设有这三个),那么 FilterComparator 构造函数中就有这三个拦截器的顺序值,假设Map值为{“AcName” : 100, “BcName” : 200, “CcName” : 300}。其中AcName是A拦截器的类名,BC同理,100,200,300是他们的序号,用于确定顺序。
    2. 我们添加一个拦截器 F 要调用addFilterAt(F, B.class)。那么它首先会在 FilterComparator 中注册F (保存在排序Map中),排序序号和B相同,也为 200,这时候Map 就变成了{“AcName” : 100, “BcName” : 200, “CcName” : 300, “FcName” : 200}。(如果调用addFilterBefore F的序号就会减1,变成199; 如果调用addFilterAfter F的序号就会加1,变成201)
    3. 在 完成上述步骤后,HttpSecurity 中也会把 F拦截器添加到一个待排序的List集合L中,然后在添加其他系统默认过滤器(相当于这个List保存了所有的过滤器,但是其调用顺序未确定,还需要经过排序后才能确定)。最终List集合L就变成了{ A, F, B, C} (之前写成了F,A,B,C了,经评论区指正,已修改),注意这个是未经排序的过滤器集合,排序后才是真正的调用顺序。
      4.在调用 HttpSecurity#performBuild 方法时,会将HttpSecurity 中的过滤器集合L进行排序,排序比较器就是FilterComparator ,排序规则就是谁的排序序号小谁在前,序号大的在后,序号相同的保持 L 中的顺序。然后,得出最后的过滤器顺序,也就是最终调用顺序。

    (讲的略乱,我已经尽力了。。。)

    以上:内容部分木有参考
    如有侵扰,联系删除。 内容仅用于自我记录学习使用。如有错误,欢迎指正

    文章目录1. 分析0. FilterComparator1. HttpSecurity#addFilterAt1.1 `this.comparator.registerAt(filter.getClass(), atFilter);`1.2 `HttpSecurity#addFilter(Filter filter)`2. 总结:1. 分析在上面Spring Security + Jwt...
    一:RestApi接口增加JWT认证功能<br/> 用户填入用户名密码后,与数据库里存储的用户信息进行比对,如果通过,则认证成功。传统的方法是在认证通过后,创建sesstion,并给客户端返回cookie。 现在我们采用JWT来处理用户名密码的认证。区别在于,认证通过后,服务器生成一个token,将token返回给客户端,客户端以后的所有请求都需要在http头中指定该token。 服务器接收的请求后,会对token的合法性进行验证。验证的内容包括: 内容是一个正确的JWT格式 检查claims 创建一个类JWTLoginFilter,核心功能是在验证用户名密码正确后,生成一个token,并将token返回给客户端: 该类继承自UsernamePasswordAuthenticationFilter,重写了其中的2个方法: attemptAuthentication :接收并解析用户凭证。 successfulAuthentication :用户成功登录后,这个方法会被调用,我们在这个方法里生成token。 二:授权验证 用户一旦登录成功后,会拿到token,后续的请求都会带着这个token,服务端会验证token的合法性。 创建JwtAuthenticationFilter类,我们在这个类中实现token的校验功能。 该类继承自BasicAuthenticationFilter,在doFilterInternal方法中,从http头的Authorization 项读取token数据,然后用Jwts包提供的方法校验token的合法性。 如果校验通过,就认为这是一个取得授权的合法请求。 三:SpringSecurity配置 通过SpringSecurity的配置,将上面的方法组合在一起。 这是标准的SpringSecurity配置内容,就不在详细说明。注意其中的 .addFilter(new JWTLoginFilter(authenticationManager())) .addFilter(new JwtAuthenticationFilter(authenticationManager())) 这两行,将我们定义的JWT方法加入SpringSecurity的处理流程中。 四:简单测试 下面对我们的程序进行简单的验证:<br/> 1.请求获取用户列表接口:http://localhost:8080/users/userList接口,会收到403错误<br/> "timestamp": 1518333248079, "status": 403, "error": "Forbidden", "message": "Access Denied", "path": "http://localhost:8080/users/userList" curl http://localhost:8080/users/userList<br/> 原因就是因为这个url没有授权,所以返回403<br/> ![输入图片说明](https://gitee.com/uploads/images/2018/0211/154022_8d9806ae_130820.png "jwt-1.png") 2.注册一个新用户<br/> curl -H "Content-Type: application/json" -X POST -d '{<br/> "username": "admin",<br/> "password": "password"<br/> }' http://localhost:8080/users/signup<br/> ![输入图片说明](https://gitee.com/uploads/images/2018/0211/154042_74fb2aa6_130820.png "jwt-2.png") 3.登录,会返回token,在http header中,Authorization: Bearer 后面的部分就是token<br/> curl -i -H "Content-Type: application/json" -X POST -d '{<br/> "username": "admin",<br/> "password": "password"<br/> }' http://localhost:8080/login<br/> 温馨提醒:这里的login方法是spring specurity框架提供的默认登录url ![输入图片说明](https://gitee.com/uploads/images/2018/0211/154308_9576ce90_130820.png "jwt-3.png") 4.用登录成功后拿到的token再次请求/users/userList接口<br/> 4.1将请求中的XXXXXX替换成拿到的token<br/> 4.2这次可以成功调用接口了<br/> curl -H "Content-Type: application/json"<br/> -H "Authorization: Bearer XXXXXX"<br/> "http://localhost:8080/users/userList" ![输入图片说明](https://gitee.com/uploads/images/2018/0211/154315_241cd6b2_130820.png "jwt-4.png")
      在认证过程和访问授权前必须了解spring Security如何知道我们要求所有用户都经过身份验证? Spring Security如何知道我们想要支持基于表单的身份验证?因此必须了解WebSecurityConfigurerAdapter配置类如何工作的。而且也必须了解清楚filter的顺序,才能更好了解其调用工作流程。 1. WebSecurityConfigurerAdapter
    Spring Security中的内置过滤器顺序是怎么维护的?我想很多开发者都对这个问题感兴趣。本篇我和大家一起探讨下这个问题。 HttpSecurity包含了一个成员变量FilterOrderRegistration,这个类是一个内置过滤器注册表。至于这些过滤器的作用,不是本文介绍的重点,有兴趣可以去看看FilterOrderRegistration的源码。 内置过滤器的顺序 FilterOrderRegistration维护了一个变量filterToOrder,它记录了类之间的顺序和上下之间的间隔
    add_filter()(添加过滤器): apply_filters( string $tag, mixed $value ) Call the functions added to a filter hook. Description The callback functions attached to filter hook $tag are invoked by calling t...
    错就错在addFilterAfter那里,我不应该用AuthenticationFilter.class作为第二个参数。点进addFilterAfter查看内部源码: public HttpSecurity addFilterAfter(Filter filter, Class&lt...
 
推荐文章
痴情的海龟  ·  springboot jsp编译时,抛出 bytes 超过limit错误_springboot 65535 bytes limit
2 月前
知识渊博的热带鱼  ·  这才是 SpringBoot 统一登录鉴权、异常处理、数据格式 的正确姿势_springboot controlleradvice+拦截器失效接口鉴权
1 月前
豪气的双杠  ·  Spring Webflux - aop -如何获取请求/响应正文 -
4 周前
从未表白的遥控器  ·  如何在Java服务中实现自动化的健康检查与自愈机制 - 省赚客开发者团队
1 周前
爱听歌的凉面  ·  spring启动时只执行一次的方法实现_springboottest只启动一次
5 天前
失眠的烤地瓜  ·  Libtorch报错:terminate called after throwing an instance of ‘c10::Error‘ what():isTensor()INTERNAL ASS
7 月前
豪爽的皮带  ·  【报错】AttributeError: ‘NoneType‘ object has no attribute ‘pyplot_show‘(已解决)_attributeerror: 'nonetype'
10 月前
爱旅游的遥控器  ·  ASP.NET Core 中的会话 | Microsoft Learn
1 年前
很酷的蚂蚁  ·  在Node.js中如何逐行读取文件_nodejs 按行读取_雪域迷影的博客-CSDN博客
1 年前
大力的馒头  ·  mybatis 利用 association 或 collection 传递多参数给子查询 - 简书
1 年前
今天看啥   ·   Py中国   ·   codingpro   ·   小百科   ·   link之家   ·   卧龙AI搜索
删除内容请联系邮箱 2879853325@qq.com
Code - 代码工具平台
© 2024 ~ 沪ICP备11025650号