详解spring security filter的工作原理

这篇文章介绍filter的工作原理。配置方式为xml。

Filter如何进入执行逻辑的

初始配置:

 <filter>
  <filter-name>springSecurityFilterChain</filter-name>
  <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
 </filter>

 <filter-mapping>
  <filter-name>springSecurityFilterChain</filter-name>
  <url-pattern>/*</url-pattern>
 </filter-mapping>

DelegatingFilterProxy这个类继承了GenericFilterBean,GenericFilterBean实现了Filter接口。

这个配置是一切的开始,配置完这个之后,在启动项目的时候会执行Filterd的初始化方法:

@Override
  public final void init(FilterConfig filterConfig) throws ServletException {
    Assert.notNull(filterConfig, "FilterConfig must not be null");
    if (logger.isDebugEnabled()) {
      logger.debug("Initializing filter '" + filterConfig.getFilterName() + "'");
    }

    this.filterConfig = filterConfig;

    // Set bean properties from init parameters.
    PropertyValues pvs = new FilterConfigPropertyValues(filterConfig, this.requiredProperties);
    if (!pvs.isEmpty()) {
      try {
        BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
        ResourceLoader resourceLoader = new ServletContextResourceLoader(filterConfig.getServletContext());
        Environment env = this.environment;
        if (env == null) {
          env = new StandardServletEnvironment();
        }
        bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, env));
        initBeanWrapper(bw);
        bw.setPropertyValues(pvs, true);
      }
      catch (BeansException ex) {
        String msg = "Failed to set bean properties on filter '" +
          filterConfig.getFilterName() + "': " + ex.getMessage();
        logger.error(msg, ex);
        throw new NestedServletException(msg, ex);
      }
    }

    // Let subclasses do whatever initialization they like.
    initFilterBean(); // 这个方法

    if (logger.isDebugEnabled()) {
      logger.debug("Filter '" + filterConfig.getFilterName() + "' configured successfully");
    }
  }

在初始化方法中,会执行初始化Filter的方法initFilterBean。这个方法的实现在DelegatingFilterProxy中:

protected void initFilterBean() throws ServletException {
    synchronized (this.delegateMonitor) {
      if (this.delegate == null) {
        // If no target bean name specified, use filter name.
        if (this.targetBeanName == null) {
          this.targetBeanName = getFilterName();
        }
        // Fetch Spring root application context and initialize the delegate early,
        // if possible. If the root application context will be started after this
        // filter proxy, we'll have to resort to lazy initialization.
        WebApplicationContext wac = findWebApplicationContext();
        if (wac != null) {
          this.delegate = initDelegate(wac); //这个方法
        }
      }
    }
  }

在这个初始化方法中又调用initDelegate方法进行初始化:

protected Filter initDelegate(WebApplicationContext wac) throws ServletException {
    String targetBeanName = getTargetBeanName();
    Assert.state(targetBeanName != null, "No target bean name set");
    Filter delegate = wac.getBean(targetBeanName, Filter.class);
    if (isTargetFilterLifecycle()) {
      delegate.init(getFilterConfig());
    }
    return delegate;
  }

在这个方法中,先获取targetBeanName,这个名字是构造方法中赋值的:

public DelegatingFilterProxy(String targetBeanName, @Nullable WebApplicationContext wac) {
    Assert.hasText(targetBeanName, "Target Filter bean name must not be null or empty");
    this.setTargetBeanName(targetBeanName);
    this.webApplicationContext = wac;
    if (wac != null) {
      this.setEnvironment(wac.getEnvironment());
    }
  }

这个名字就是web.xml中配置的名字springSecurityFilterChain:

 <filter>
  <filter-name>springSecurityFilterChain</filter-name>
  <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
 </filter>

springSecurityFilterChain是固定不能改的,如果改了启动时就会报错,这是spring 启动时内置的一个bean,这个bean实际是FilterChainProxy。

这样一个Filter就初始化话好了,过滤器chain也初始化好了。

当一个请求进来的时候,会进入FilterChainProxy执行doFilter方法:

public void doFilter(ServletRequest request, ServletResponse response,
      FilterChain chain) throws IOException, ServletException {
    boolean clearContext = request.getAttribute(FILTER_APPLIED) == null;
    if (clearContext) {
      try {
        request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
        doFilterInternal(request, response, chain);
      }
      finally {
        SecurityContextHolder.clearContext();
        request.removeAttribute(FILTER_APPLIED);
      }
    }
    else {
      doFilterInternal(request, response, chain);
    }
  }

先获取所有的Filter,然后执行doFilterInternal方法:

private void doFilterInternal(ServletRequest request, ServletResponse response,
      FilterChain chain) throws IOException, ServletException {

    FirewalledRequest fwRequest = firewall
        .getFirewalledRequest((HttpServletRequest) request);
    HttpServletResponse fwResponse = firewall
        .getFirewalledResponse((HttpServletResponse) response);

    List<Filter> filters = getFilters(fwRequest);

    if (filters == null || filters.size() == 0) {
      if (logger.isDebugEnabled()) {
        logger.debug(UrlUtils.buildRequestUrl(fwRequest)
            + (filters == null ? " has no matching filters"
                : " has an empty filter list"));
      }

      fwRequest.reset();

      chain.doFilter(fwRequest, fwResponse);

      return;
    }

    // 最终执行下面的这些代码
    VirtualFilterChain vfc = new VirtualFilterChain(fwRequest, chain, filters);
    vfc.doFilter(fwRequest, fwResponse);
  }

VirtualFilterChain是一个匿名内部类:

private static class VirtualFilterChain implements FilterChain {
    private final FilterChain originalChain;
    private final List<Filter> additionalFilters;
    private final FirewalledRequest firewalledRequest;
    private final int size;
    private int currentPosition = 0;

    private VirtualFilterChain(FirewalledRequest firewalledRequest,
        FilterChain chain, List<Filter> additionalFilters) {
      this.originalChain = chain;
      this.additionalFilters = additionalFilters;
      this.size = additionalFilters.size();
      this.firewalledRequest = firewalledRequest;
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response)
        throws IOException, ServletException {
      if (currentPosition == size) {
        if (logger.isDebugEnabled()) {
          logger.debug(UrlUtils.buildRequestUrl(firewalledRequest)
              + " reached end of additional filter chain; proceeding with original chain");
        }

        // Deactivate path stripping as we exit the security filter chain
        this.firewalledRequest.reset();

        originalChain.doFilter(request, response);
      }
      else {
        currentPosition++;

        Filter nextFilter = additionalFilters.get(currentPosition - 1);

        if (logger.isDebugEnabled()) {
          logger.debug(UrlUtils.buildRequestUrl(firewalledRequest)
              + " at position " + currentPosition + " of " + size
              + " in additional filter chain; firing Filter: '"
              + nextFilter.getClass().getSimpleName() + "'");
        }

        nextFilter.doFilter(request, response, this);
      }
    }
  }

filter集合执行的逻辑在VirtualFilterChain的doFilter方法中。

filter是如何执行的

上面说了怎么才能进入filter的执行逻辑,下面说一下filter到底怎么执行,为什么一个

在VirtualFilterChain的doFilter方法可以执行所有的filter。

下面写一个例子,模拟filter的执行逻辑。
定义FilterChain接口、Filter接口:

public interface Filter {

  void doFilter(String username, int age, FilterChain filterChain);
}
public interface FilterChain {

  void doFilter(String username, int age);
}

定义两个Filter实现:

public class NameFilter implements Filter {

  @Override
  public void doFilter(String username, int age, FilterChain filterChain) {

    username = username + 1;
    System.out.println("username: " + username + "  age: " + age);
    System.out.println("正在执行:NameFilter");
    filterChain.doFilter(username, age);
  }
}
public class AgeFilter implements Filter {

  @Override
  public void doFilter(String username, int age, FilterChain filterChain) {

    age += 10;
    System.out.println("username: " + username + "  age: " + age);
    System.out.println("正在执行:AgeFilter");
    filterChain.doFilter(username, age);
  }
}

定义一个FilterChain实现:

public class FilterChainProxy implements FilterChain {

  private int position = 0;
  private int size = 0;
  private List<Filter> filterList = new ArrayList<>();

  public void addFilter(Filter filter) {

    filterList.add(filter);
    size++;
  }

  @Override
  public void doFilter(String username, int age) {

    if (size == position) {
      System.out.println("过滤器链执行结束");
    } else {

      Filter filter = filterList.get(position);
      position++;
      filter.doFilter(username, age, this);
    }
  }
}

测试Filter实现:

public class FilterTest {

  public static void main(String[] args) {

    FilterChainProxy proxy = new FilterChainProxy();
    proxy.addFilter(new NameFilter());
    proxy.addFilter(new AgeFilter());

    proxy.doFilter("张三", 0);
  }
}
=======
username: 张三1  age: 0
正在执行:NameFilter
username: 张三1  age: 10
正在执行:AgeFilter
过滤器链执行结束

在这个执行逻辑中,最重要的是【this】,this就是初始化的好的FilterChain实例,在这个测试实例中,this就是FilterChainProxy。

执行FilterChainProxy的doFilter方法的时候,传入了初始参数username和age,进入这个方法后,根据position取出相应的Filter,初次进入position是0,执行Filter的doFilter方法,注意,此时Filter的doFilter方法额外传入了一个this参数,这个参数就是初始化的好的FilterChain实例,在Filter中的doFilter的方法中最后又会执行FilterChain的doFilter方法,相当于第二次调用FilterChain实例的doFilter方法,此时posotion是1,然后再执行Filter的doFilter方法,直到所有的Filter执行完,整个执行过程结束。

VirtualFilterChain的doFilter方法的执行逻辑和这个测试实例中的执行逻辑基本一致。

这样就完成了整个过滤器链的执行。

总结

以前用Filter的时候就非常疑惑过滤器怎么执行的,直到今天才算解决了这个疑惑。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • spring boot 配置Filter过滤器的方法

    Filter 过滤器是web开发中很重要的一个组件,下面以一个session登陆的例子介绍下spring boot中如何使用Filter 首先要准备一个实现了Filter的接口的类 SessionFilter: import org.slf4j.LoggerFactory; import javax.servlet.*; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRespo

  • 详解SpringCloud Gateway之过滤器GatewayFilter

    在Spring-Cloud-Gateway之请求处理流程文中我们了解最终网关是将请求交给过滤器链表进行处理,接下来我们阅读Spring-Cloud-Gateway的整个过滤器类结构以及主要功能 通过源码可以看到Spring-Cloud-Gateway的filter包中吉接口有如下三个,GatewayFilter,GlobalFilter,GatewayFilterChain,下来我依次阅读接口的主要实现功能. GatewayFilterChain 类图 代码 /** * 网关过滤链表接口 * 用

  • SpringBoot中使用Filter和Interceptor的示例代码

    一.Filter(过滤器) Filter接口定义在javax.servlet包中,是Servlet规范定义的,作用于Request/Response前后,被Servlet容器调用,当Filter被Sring管理后可以使用Spring容器资源. 实现一个Filter 自定义的过滤器需要实现javax.servlet.Filter,Filter接口中有三个方法: init(FilterConfig filterConfig):过滤器初始化的被调用. doFilter(ServletRequest s

  • springboot中filter的用法详解

    一.在spring的应用中我们存在两种过滤的用法,一种是拦截器.另外一种当然是过滤器.我们这里介绍过滤器在springboot的用法,在springmvc中的用法基本上一样,只是配置上面有点区别. 二.filter功能,它使用户可以改变一个 request和修改一个response. Filter 不是一个servlet,它不能产生一个response,它能够在一个request到达servlet之前预处理request,也可以在离开 servlet时处理response.换种说法,filter

  • Spring Boot 编写Servlet、Filter、Listener、Interceptor的方法

    前言 在编写过滤器.监听器.拦截器之前我们需要在spring-boot启动的类上加上注解@ServletComponentScan: @SpringBootApplication @ServletComponentScan public class MySpringbootApplication { public static void main(String[] args) { SpringApplication.run(MySpringbootApplication.class, args)

  • Spring Boot的filter(过滤器)简单使用实例详解

    过滤器(Filter)的注册方法和 Servlet 一样,有两种方式:代码注册或者注解注册 1.代码注册方式 通过代码方式注入过滤器 @Bean public FilterRegistrationBean indexFilterRegistration() { FilterRegistrationBean registration = new FilterRegistrationBean(new IndexFilter()); registration.addUrlPatterns("/&quo

  • SpringBoot如何注册Servlet、Filter、Listener的几种方式

    在Servlet 3.0之前都是使用web.xml文件进行配置,需要增加Servlet.Filter或者Listener都需要在web.xml增加相应的配置.Servlet 3.0之后可以使用注解进行配置Servlet.Filter或者Listener:springboot也提供了使用代码进行注册Servlet.Filter或者Listener.所以springboot有两种方式进行Servlet.Filter或者Listener配置. 方式一:使用注解 (1)注册Servlet 使用@WebS

  • 详解spring security filter的工作原理

    这篇文章介绍filter的工作原理.配置方式为xml. Filter如何进入执行逻辑的 初始配置: <filter> <filter-name>springSecurityFilterChain</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> </filter> <filter-mapp

  • 详解spring security四种实现方式

    spring security实现方式大致可以分为这几种: 1.配置文件实现,只需要在配置文件中指定拦截的url所需要权限.配置userDetailsService指定用户名.密码.对应权限,就可以实现. 2.实现UserDetailsService,loadUserByUsername(String userName)方法,根据userName来实现自己的业务逻辑返回UserDetails的实现类,需要自定义User类实现UserDetails,比较重要的方法是getAuthorities()

  • 详解Spring Security中权限注解的使用

    目录 1. 具体用法 2. SpEL 3. @PreAuthorize 最近有个小伙伴在微信群里问 Spring Security 权限注解的问题: 很多时候事情就是这么巧,松哥最近在做的 tienchin 也是基于注解来处理权限问题的,所以既然大家有这个问题,咱们就一块来聊聊这个话题. 当然一些基础的知识我就不讲了,对于 Spring Security 基本用法尚不熟悉的小伙伴,可在公众号后台回复 ss,有原创的系列教程. 1. 具体用法 先来看看 Spring Security 权限注解的具

  • 详解Spring Security如何在权限中使用通配符

    目录 前言 1. SpEL 2. 自定义权限该如何写 3. 权限通配符 4. TienChin 项目怎么做的 前言 小伙伴们知道,在 Shiro 中,默认是支持权限通配符的,例如系统用户有如下一些权限: system:user:add system:user:delete system:user:select system:user:update … 现在给用户授权的时候,我们可以像上面这样,一个权限一个权限的配置,也可以直接用通配符: system:user:* 这个通配符就表示拥有针对用户的

  • 详解C++虚函数的工作原理

    静态绑定与动态绑定 讨论静态绑定与动态绑定,首先需要理解的是绑定,何为绑定?函数调用与函数本身的关联,以及成员访问与变量内存地址间的关系,称为绑定. 理解了绑定后再理解静态与动态. 静态绑定:指在程序编译过程中,把函数调用与响应调用所需的代码结合的过程,称为静态绑定.发生在编译期. 动态绑定:指在执行期间判断所引用对象的实际类型,根据实际的类型调用其相应的方法.程序运行过程中,把函数调用与响应调用所需的代码相结合的过程称为动态绑定.发生于运行期. C++中动态绑定 在C++中动态绑定是通过虚函数

  • 详解Spring Security 中的四种权限控制方式

    Spring Security 中对于权限控制默认已经提供了很多了,但是,一个优秀的框架必须具备良好的扩展性,恰好,Spring Security 的扩展性就非常棒,我们既可以使用 Spring Security 提供的方式做授权,也可以自定义授权逻辑.一句话,你想怎么玩都可以! 今天松哥来和大家介绍一下 Spring Security 中四种常见的权限控制方式. 表达式控制 URL 路径权限 表达式控制方法权限 使用过滤注解 动态权限 四种方式,我们分别来看.  1.表达式控制 URL 路径权

  • 详解Python描述符的工作原理

    一.前言 其实,在开发过程中,虽然我们没有直接使用到描述符,但是它在底层却无时不刻地被使用到,例如以下这些: function.bound method.unbound method 装饰器property.staticmethod.classmethod 是不是都很熟悉? 这些都与描述符有着千丝万缕的关系,这篇文章我们就来看一下描述符背后的工作原理. 二.什么是描述符? 在解释什么是「描述符」之前,我们先来看一个简单的例子. 这个例子非常简单,我们在类 A 中定义了一个类属性 x,然后打印它的

  • 详解Spring Security中获取当前登录用户的详细信息的几种方法

    目录 在Bean中获取用户信息 在Controller中获取用户信息 通过 Interface 获取用户信息 在JSP页面中获取用户信息 在Bean中获取用户信息 Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); if (!(authentication instanceof AnonymousAuthenticationToken)) { String currentU

  • 一文详解Spring Security的基本用法

    目录 1.引入依赖 2.用户名和密码在哪里设置 3.UserDetailsService接口详解 3.1JdbcDaoImpl实现类 3.2InMemoryUserDetailsManager实现类 3.3自定义实现类实现UserDetailsService接口 4.如何修改登录页面 Spring Security是一个功能强大且高度可定制的身份验证和访问控制框架, 提供了完善的认证机制和方法级的授权功能.是一款非常优秀的权限管理框架.它的核心是一组过滤器链,不同的功能经由不同的过滤器. 今天通

  • 详解spring security 配置多个AuthenticationProvider

    前言 发现很少关于spring security的文章,基本都是入门级的,配个UserServiceDetails或者配个路由控制就完事了,而且很多还是xml配置,国内通病...so,本文里的配置都是java配置,不涉及xml配置,事实上我也不会xml配置 spring security的大体介绍 spring security本身如果只是说配置,还是很简单易懂的(我也不知道网上说spring security难,难在哪里),简单不需要特别的功能,一个WebSecurityConfigurerA

随机推荐