Spring Security的内置过滤器是如何维护的

Spring Security中的内置过滤器顺序是怎么维护的?我想很多开发者都对这个问题感兴趣。本篇我和大家一起探讨下这个问题。

HttpSecurity包含了一个成员变量FilterOrderRegistration,这个类是一个内置过滤器注册表。至于这些过滤器的作用,不是本文介绍的重点,有兴趣可以去看看FilterOrderRegistration的源码。

内置过滤器的顺序

FilterOrderRegistration维护了一个变量filterToOrder,它记录了类之间的顺序和上下之间的间隔步长。我们复制了一个FilterOrderRegistration来直观感受一下过滤器的顺序:

CopyFilterOrderRegistration filterOrderRegistration = new CopyFilterOrderRegistration();
        // 获取内置过滤器  此方法并未提供
        Map<String, Integer> filterToOrder = filterOrderRegistration.getFilterToOrder();
        TreeMap<Integer, String> orderToFilter = new TreeMap<>();
        filterToOrder.forEach((name, order) -> orderToFilter.put(order,name));
        orderToFilter.forEach((order,name) -> System.out.println(" 顺序:" + order+" 类名:" + name ));

打印结果:

我们可以看得出内置过滤器之间的位置是相对固定的,除了第一个跟第二个步长为200外,其它步长为100。

内置过滤器并非一定会生效,仅仅是预置了它们的排位,需要通过HttpSecurity的addFilterXXXX系列方法显式添加才行。

注册过滤器的逻辑

FilterOrderRegistration提供了一个put方法:

void put(Class<? extends Filter> filter, int position) {
  String className = filter.getName();
        // 如果这个类已经注册就忽略
  if (this.filterToOrder.containsKey(className)) {
   return;
  }
        // 如果没有注册就注册顺序。
  this.filterToOrder.put(className, position);
 }

从这个方法我们可以得到几个结论:

  • 内置的34个过滤器是有固定序号的,不可被改变。
  • 新加入的过滤器的类全限定名是不能和内置过滤器重复的。
  • 新加入的过滤器的顺序是可以和内置过滤器的顺序重复的。

获取已注册过滤器的顺序值

FilterOrderRegistration还提供了一个getOrder方法:

Integer getOrder(Class<?> clazz) {
        // 如果类Class 或者 父类Class 名为空就返回null
        while (clazz != null) {
            Integer result = this.filterToOrder.get(clazz.getName());
            // 如果获取到顺序值就返回
            if (result != null) {
                return result;
            }
            // 否则尝试去获取父类的顺序值
            clazz = clazz.getSuperclass();
        }
        return null;
    }

HttpSecurity维护过滤器的方法

接下来我们分析一下HttpSecurity维护过滤器的几个方法。

addFilterAtOffsetOf

addFilterAtOffsetOf是一个HttpSecurity的内置私有方法。Filter是想要注册到DefaultSecurityFilterChain中的过滤器,offset是向右的偏移值,registeredFilter是已经注册到FilterOrderRegistration的过滤器,而且registeredFilter没有注册的话会空指针。

private HttpSecurity addFilterAtOffsetOf(Filter filter, int offset, Class<? extends Filter> registeredFilter) {
        // 首先会根据registeredFilter的顺序和偏移值来计算filter的
  int order = this.filterOrders.getOrder(registeredFilter) + offset;
        // filter添加到集合中待排序
  this.filters.add(new OrderedFilter(filter, order));
        // filter注册到 FilterOrderRegistration
  this.filterOrders.put(filter.getClass(), order);
  return this;
 }

务必记着registeredFilter一定是已注册入FilterOrderRegistration的Filter。

addFilter系列方法

这里以addFilterAfter为例。

@Override
 public HttpSecurity addFilterAfter(Filter filter, Class<? extends Filter> afterFilter) {
  return addFilterAtOffsetOf(filter, 1, afterFilter);
 }

addFilterAfter是将filter的位置置于afterFilter后一位,假如afterFilter顺序值为400,则filter顺序值为401。addFilterBefore和addFilterAt逻辑和addFilterAfter仅仅是偏移值的区别,这里不再赘述。

addFilter的方法比较特殊:

@Override
 public HttpSecurity addFilter(Filter filter) {
  Integer order = this.filterOrders.getOrder(filter.getClass());
  if (order == null) {
   throw new IllegalArgumentException("The Filter class " + filter.getClass().getName()
     + " does not have a registered order and cannot be added without a specified order. Consider using addFilterBefore or addFilterAfter instead.");
  }
  this.filters.add(new OrderedFilter(filter, order));
  return this;
 }

filter必须是已经注册到FilterOrderRegistration的Filter,这意味着它可能是内置的Filter,也可能是先前通过addFilterBefore、addFilterAt或者addFilterAfter注册的非内置Filter。

问题来了

之前看到一个问题,如果HttpSecurity注册两个重复序号的Filter会是怎么样的顺序呢?我们先来看下排序的机制:

// filters
private List<OrderedFilter> filters = new ArrayList<>(); 
//排序
this.filters.sort(OrderComparator.INSTANCE);

看了下OrderComparator源码,其实还是通过数字的自然排序,数字越小越靠前。如果数字相同,索引越小越靠前。也就是同样的序号,谁先add到filters谁就越靠前。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,针对你的要求,我们可以使用Spring Security内置过滤器`ConcurrentSessionFilter`结合`SessionRegistry`和`SessionInformation`来实现调用接口到达1次后限制1小时后才能再次调用的功能。具体实现步骤如下: 1. 在Spring Security的配置类中添加`ConcurrentSessionFilter`过滤器: ```java @Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.addFilterAt(concurrentSessionFilter(), ConcurrentSessionFilter.class); //其他配置 } @Bean public ConcurrentSessionFilter concurrentSessionFilter() { return new ConcurrentSessionFilter(sessionRegistry(), sessionInformationExpiredStrategy()); } @Bean public SessionRegistry sessionRegistry() { return new SessionRegistryImpl(); } @Bean public SessionInformationExpiredStrategy sessionInformationExpiredStrategy() { return new SimpleRedirectSessionInformationExpiredStrategy("/login"); } //其他配置 } ``` 2. 在接口方法中获取当前用户的`SessionInformation`对象,并判断该对象是否存在且距离上次访问时间是否大于1小时: ```java @RestController @RequestMapping("/api") public class ApiController { private Map<String, Integer> userCountMap = new ConcurrentHashMap<>(); @GetMapping("/test") public String test(HttpServletRequest request) { HttpSession session = request.getSession(false); if (session != null) { SessionInformation sessionInformation = sessionRegistry().getSessionInformation(session.getId()); if (sessionInformation != null && System.currentTimeMillis() - sessionInformation.getLastRequest() < 3600000) { throw new RuntimeException("操作太频繁,请稍后再试!"); } } String username = request.getParameter("username"); userCountMap.compute(username, (k, v) -> v == null ? 1 : v + 1); return "Hello " + username; } } ``` 在上述代码中,我们首先通过`HttpServletRequest`对象获取当前用户的`HttpSession`对象,然后通过`sessionRegistry().getSessionInformation(session.getId())`方法获取当前用户的`SessionInformation`对象。如果该对象存在且距离上次访问时间不足1小时,则抛出异常提示用户操作太频繁。否则,我们将当前用户的执行次数记录到`userCountMap`中。 这样,就可以实现调用接口到达1次后限制1小时后才能再次调用的功能了。需要注意的是,这种方式只适用于单机部署的情况,如果是分布式部署,则需要使用分布式锁等机制来实现。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值