Spring Security的过滤器

2019-04-01  本文已影响0人  buzzerrookie

Spring Security文档的The Security Filter Chain一章指出Spring Security完全基于标准的servlet过滤器,本文结合笔者的看法对该文档进行一些补充说明。

DelegatingFilterProxy类

DelegatingFilterProxy用于代理其他的过滤器,位于spring-web.jar中,这使得Spring可以通过它方便地使用Spring容器管理的过滤器。DelegatingFilterProxy的部分代码如下所示,它继承自GenericFilterBean类,GenericFilterBean的作用与DispatcherServlet的初始化过程这篇文章中介绍的HttpServletBean相似。

public class DelegatingFilterProxy extends GenericFilterBean {
    private String contextAttribute;
    private WebApplicationContext webApplicationContext;
    private String targetBeanName;
    private boolean targetFilterLifecycle = false;
    private volatile Filter delegate;
    private final Object delegateMonitor = new Object();

    // 省略一些代码

    @Override
    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);
                }
            }
        }
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {

        // Lazily initialize the delegate if necessary.
        Filter delegateToUse = this.delegate;
        if (delegateToUse == null) {
            synchronized (this.delegateMonitor) {
                delegateToUse = this.delegate;
                if (delegateToUse == null) {
                    WebApplicationContext wac = findWebApplicationContext();
                    if (wac == null) {
                        throw new IllegalStateException("No WebApplicationContext found: " +
                                "no ContextLoaderListener or DispatcherServlet registered?");
                    }
                    delegateToUse = initDelegate(wac);
                }
                this.delegate = delegateToUse;
            }
        }

        // Let the delegate perform the actual doFilter operation.
        invokeDelegate(delegateToUse, request, response, filterChain);
    }

    protected Filter initDelegate(WebApplicationContext wac) throws ServletException {
        Filter delegate = wac.getBean(getTargetBeanName(), Filter.class);
        if (isTargetFilterLifecycle()) {
            delegate.init(getFilterConfig());
        }
        return delegate;
    }
    // 省略一些代码
}

doFilter方法利用invokeDelegate方法将调用委托给被代理的过滤器执行。

protected void invokeDelegate(
        Filter delegate, ServletRequest request, ServletResponse response, FilterChain filterChain)
        throws ServletException, IOException {

    delegate.doFilter(request, response, filterChain);
}

FilterChainProxy类

文档提到“Spring Security的基础设施只应该委托给一个FilterChainProxy实例,如果在web.xml中配置每个Spring Security的过滤器那就会显得很笨拙”。
FilterChainProxy继承自GenericFilterBean类,可以看成一个过滤器的集合。

public class FilterChainProxy extends GenericFilterBean {
    private static final Log logger = LogFactory.getLog(FilterChainProxy.class);
    private final static String FILTER_APPLIED = FilterChainProxy.class.getName().concat(
            ".APPLIED");
    private List<SecurityFilterChain> filterChains;
    private FilterChainValidator filterChainValidator = new NullFilterChainValidator();
    private HttpFirewall firewall = new DefaultHttpFirewall();

    public FilterChainProxy() {
    }

    public FilterChainProxy(SecurityFilterChain chain) {
        this(Arrays.asList(chain));
    }

    public FilterChainProxy(List<SecurityFilterChain> filterChains) {
        this.filterChains = filterChains;
    }

    @Override
    public void afterPropertiesSet() {
        filterChainValidator.validate(this);
    }

    // 省略一些代码
}

doFilter方法则调用了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);
}

private List<Filter> getFilters(HttpServletRequest request) {
    for (SecurityFilterChain chain : filterChains) {
        if (chain.matches(request)) {
            return chain.getFilters();
        }
    }

    return null;
}

Spring Security的过滤器

众所周知,Spring Security的实现基于servlet的过滤器,以下按照在SecurityFilterChain中的排列顺序列出了Spring Security中主要的过滤器:

更多过滤器可以参见Filter OrderingCore Security Filters,下面简要分析各过滤器的功能。

1. SecurityContextPersistenceFilter

SecurityContextPersistenceFilter从所配置的SecurityContextRepository中获取与该请求关联的SecurityContext,并在请求结束后清除SecurityContextHolder。一般需要将该过滤器配置在其他认证过滤器前,因为认证过滤器如Basic、CAS等要求SecurityContextHolder包含有效的SecurityContext。

2. CorsFilter

CorsFilter根据CORS配置处理预检请求、简单请求和非简单请求,CORS配置由CorsConfiguration类表示。

3. LogoutFilter

若收到注销的请求,LogoutFilter会首先清除认证信息,然后依次执行配置的所有注销处理器LogoutHandler的logout方法,最后执行配置的注销成功处理器LogoutSuccessHandler的onLogoutSuccess方法。

4. UsernamePasswordAuthenticationFilter

若收到登录的请求,UsernamePasswordAuthenticationFilter执行基于表单提交的认证,默认配置下表单的用户名和密码字段分别是username和password,但是可以通过setUsernameParameter和setPasswordParameter进行配置,用户提交的用户名和密码则是通过obtainUsername和obtainPassword方法分别得到。具体的认证过程在该Filter的超类AbstractAuthenticationProcessingFilter中执行。

5. ExceptionTranslationFilter

ExceptionTranslationFilter处理位于它之后的过滤器如FilterSecurityInterceptor抛出的AccessDeniedException或AuthenticationException异常,它只是一个Java异常和HTTP响应之间的桥梁,并不做任何实际的安全认证。

6. FilterSecurityInterceptor

FilterSecurityInterceptor用来保护HTTP资源,若没有认证则抛出AuthenticationException异常,若权限不足则抛出AccessDeniedException异常,这些异常都会被ExceptionTranslationFilter捕获。

参考文献

https://stackoverflow.com/questions/41480102/how-spring-security-filter-chain-works
跨域资源共享 CORS 详解

上一篇下一篇

猜你喜欢

热点阅读