spring-security-filter的工作原理

47次阅读

共计 7675 个字符,预计需要花费 20 分钟才能阅读完成。

这篇文章介绍 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