详解spring security filter的工作原理

 更新时间:2019年07月25日 15:04:09   作者:流浪的神明  
这篇文章主要介绍了详解spring security filter的工作原理,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
(福利推荐:【腾讯云】服务器最新限时优惠活动,云服务器1核2G仅99元/年、2核4G仅768元/3年,立即抢购>>>:9i0i.cn/qcloud

(福利推荐:你还在原价购买阿里云服务器?现在阿里云0.8折限时抢购活动来啦!4核8G企业云服务器仅2998元/3年,立即抢购>>>:9i0i.cn/aliyun

这篇文章介绍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的时候就非常疑惑过滤器怎么执行的,直到今天才算解决了这个疑惑。

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

相关文章

  • java获取json中的全部键值对实例

    java获取json中的全部键值对实例

    下面小编就为大家分享一篇java获取json中的全部键值对实例,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2018-03-03
  • java中Class.forName方法的作用详解

    java中Class.forName方法的作用详解

    Class.forName(xxx.xx.xx) 返回的是一个类,但Class.forName方法的作用到底是什么終?下面这篇文章就来给大家详细介绍了关于java中Class.forName方法的作用,文中介绍的非常详细,需要的朋友可以参考借鉴,下面来一起看看吧。
    2017-06-06
  • Spring的异常重试框架Spring Retry简单配置操作

    Spring的异常重试框架Spring Retry简单配置操作

    这篇文章主要介绍了Spring的异常重试框架Spring Retry简单配置操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-09-09
  • SpringBoot与单元测试JUnit的结合操作

    SpringBoot与单元测试JUnit的结合操作

    这篇文章主要介绍了SpringBoot与单元测试JUnit的结合操作,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-10-10
  • Linux安装JDK两种方式详细教程(附图)

    Linux安装JDK两种方式详细教程(附图)

    这篇文章主要给大家介绍了关于Linux安装JDK两种方式详细教程的相关资料,Linux的使用相信大家都要用到java吧,在使用java前我们得先安装jdk以及配置环境变量等工作,需要的朋友可以参考下
    2023-11-11
  • eclipse创建一个基于maven的web项目详细步骤

    eclipse创建一个基于maven的web项目详细步骤

    开始学习maven,并用maven创建了第一个属于自己的web项目,下面这篇文章主要给大家介绍了关于eclipse创建一个基于maven的web项目的相关资料,文中通过图文介绍的非常详细,需要的朋友可以参考下
    2023-12-12
  • 单机redis分布式锁实现原理解析

    单机redis分布式锁实现原理解析

    这篇文章主要介绍了单机redis分布式锁实现原理解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-04-04
  • Java实现在线聊天功能

    Java实现在线聊天功能

    这篇文章主要为大家详细介绍了Java实现在线聊天功能,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-09-09
  • 阿里云发布 Spring Boot 新脚手架工程

    阿里云发布 Spring Boot 新脚手架工程

    这篇文章主要介绍了阿里云发布 Spring Boot 新脚手架的相关资料,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,可以参考下
    2020-04-04
  • Spring boot中filter类不能注入@Autowired变量问题

    Spring boot中filter类不能注入@Autowired变量问题

    这篇文章主要介绍了Spring boot中filter类不能注入@Autowired变量问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-09-09

最新评论

?


http://www.vxiaotou.com