美文网首页
Spring Security的过滤器

Spring Security的过滤器

作者: buzzerrookie | 来源:发表于2019-04-01 23:24 被阅读0次

    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;
        }
        // 省略一些代码
    }
    
    • 在initFilterBean方法初始化DelegatingFilterProxy对象或者第一次执行doFilter方法时会调用findWebApplicationContext方法查找上下文;
    • targetBeanName表示被代理的过滤器bean的名称;
    • delegate保存被代理的过滤器实例,该实例即是initDelegate方法从上下文中查找的名为targetBeanName的过滤器bean;
    • delegateMonitor是初始化时用的锁。

    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);
        }
    
        // 省略一些代码
    }
    
    • filterChains字段可以看成保存了所有与Spring Security相关的过滤器,这些过滤器由SecurityFilterChain组织起来。
      public interface SecurityFilterChain {
      
          boolean matches(HttpServletRequest request);
      
          List<Filter> getFilters();
      }
      

    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;
    }
    
    • getFilters方法根据请求查找第一个匹配的SecurityFilterChain,因此SecurityFilterChain的添加顺序非常重要;
    • VirtualFilterChain依次调用匹配的SecurityFilterChain中的每个过滤器。

    Spring Security的过滤器

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

    • SecurityContextPersistenceFilter;
    • CorsFilter;
    • LogoutFilter;
    • UsernamePasswordAuthenticationFilter、CasAuthenticationFilter和BasicAuthenticationFilter等认证过滤器;
    • RememberMeAuthenticationFilter;
    • AnonymousAuthenticationFilter;
    • ExceptionTranslationFilter;
    • FilterSecurityInterceptor。

    更多过滤器可以参见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响应之间的桥梁,并不做任何实际的安全认证。

    • 处理AuthenticationException异常:该过滤器会委托给配置的AuthenticationEntryPoint处理;
    • 处理AccessDeniedException异常:如果是匿名用户则委托给配置的AuthenticationEntryPoint处理,否则委托给配置的AccessDeniedHandler。

    6. FilterSecurityInterceptor

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

    参考文献

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

    相关文章

      网友评论

          本文标题:Spring Security的过滤器

          本文链接:https://www.haomeiwen.com/subject/kktsvqtx.html