深入剖析Spring Web源码(二十) - Spring安全 - 横向剖析

 

6 Spring安全

 

Spring安全是一个功能强大的高可插拔的认证和赋权框架。它是Spring应用程序的领域内的标准的安全策略。

 

Spring安全是最成熟的并且被广泛应用的Spring项目之一。自从2003年创建以来,它是一个独立于Spring框架的一个单独项目,这个项目同样是通过阿帕奇2.0协议发布,所以,它可以应用到任何项目,这包括开源项目,免费项目和商业项目。

 

这一章,我们将深入的彻底剖析Spring安全框架的流程和架构。

 

6.1 横向剖析

 

Spring安全框架主要应用在需要认证与赋权的Web应用程序上。它使用Servlet标准中的过滤器拦截所有需要安全认证与赋权的HTTP请求,并且根据HTTP请求的URL,参数以及其他信息自动识别登录的流程,这个包括首次登录流程,登录失败流程,登录成功流程,退出登录流程,识别不同的流程,它会作出不同的处理操作。

 

这一节,我们将针对不同的流程进行深入的分析,了解每个流程是如何被识别以及如何被处理的。

 

既然Spring安全框架是通过Servlet标准中的过滤器来实现的,在开始流程分析之前,我们首先介绍过滤器的功能和实现。

 

过滤器技术是Servlet 2.3新增加的功能, 它不同于Servlet,它的设计不是用来产生响应, 但是,它能够在一个请求到达Servlet之前预处理请求,也可以在离开Servlet时处理响应。

 

一个过滤器包括如下功能:

 

1. Servlet被调用之前截获;

2. Servlet被调用之前检查Servlet请求;

3. 根据需要修改请求头和请求数据;

4. 根据需要修改响应头和响应数据;

5. Servlet被调用之后截获.

 

你能够配置一个过滤器到一个或多个Servlet,单个ServletServlet组能够被多个过滤器使用,过滤器也可以通过URL进行配置。

 

既然过滤器能够在请求到达一个特定的Servlet之前处理请求和相应,那么,在Spring安全框架中正式使用这一特点来完成认证和赋权的流程的。

 

Spring安全框架首先需要配置一个过滤器,这个过滤器应用在所有需要安全保护的请求URL上,通常应用到Web应用程序的跟环境上。如下web.xml文件配置所示,

 

<filter>

    <filter-name>springSecurity</filter-name>

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

    <init-param>

        <param-name>targetBeanName</param-name>

        <param-value>springSecurityFilterChain</param-value>

    </init-param>

</filter>

 

<filter-mapping>

    <filter-name>springSecurity</filter-name>

    <url-pattern>/*</url-pattern>

</filter-mapping>

 

从配置代码中我们可以看到,转接过滤器代理在拦截了每个Web应用程序的请求后,将会把控制权转接给Spring应用程序环境中的一个特殊的Bean, 它的名字是springSecurityFilterChain。如下代码注释,

 

public class DelegatingFilterProxy extends GenericFilterBean {

    // 是否使用专用的Spring跟环境

    private String contextAttribute;

 

    // 代理过滤器的Bean名字

    private String targetBeanName;

 

    // 是否代理生命周期方法到代理过滤器Bean, 这包括初始化过滤器和卸载过滤器事先

    private boolean targetFilterLifecycle = false;

 

    // 代理目标过滤器

    private Filter delegate;

 

    // 同步对象

    private final Object delegateMonitor = new Object();

 

    @Override

    protected void initFilterBean() throws ServletException {

        // If no target bean name specified, use filter name.

        // 这个Bean名字通常是springSecurityFilterChain,它是Spring自动生成的一个Bean

        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.

        // 如果这个过滤器定义在web.xml中,它将在过滤器的初始化的时候调用,如果这个过滤器定义在Web应用程序环境做,并且作为一个Bean, 它是在Bean初始化中调用,因此很可能被重复调用,所以这里需要同步

        synchronized (this.delegateMonitor) {

            WebApplicationContext wac = findWebApplicationContext();

           

            // 如果过滤器初始化时或者过滤器Bean初始化是,Web应用程序环境还没有初始化,则使用懒惰加载

            if (wac != null) {

                this.delegate = initDelegate(wac);

            }

        }

    }

 

    public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)

            throws ServletException, IOException {

 

        // Lazily initialize the delegate if necessary.

        // 如果代理不存在,则懒惰加载

        Filter delegateToUse = null;

        synchronized (this.delegateMonitor) {

            if (this.delegate == null) {

                WebApplicationContext wac = findWebApplicationContext();

                if (wac == null) {

                    throw new IllegalStateException("No WebApplicationContext found: no ContextLoaderListener registered?");

                }

                this.delegate = initDelegate(wac);

            }

            delegateToUse = this.delegate;

        }

 

        // Let the delegate perform the actual doFilter operation.

        invokeDelegate(delegateToUse, request, response, filterChain);

    }

 

    @Override

    public void destroy() {

        // 卸载过滤器事件处理

        Filter delegateToUse = null;

        synchronized (this.delegateMonitor) {

            delegateToUse = this.delegate;

        }

        if (delegateToUse != null) {

            destroyDelegate(delegateToUse);

        }

    }

 

    protected WebApplicationContext findWebApplicationContext() {

        // 找到共享跟环境或者专用跟环境,如果是专用跟环境,需要有客户化监听器或者Servlet初始化专用跟环境

        String attrName = getContextAttribute();

        if (attrName != null) {

            return WebApplicationContextUtils.getWebApplicationContext(getServletContext(), attrName);

        }

        else {

            return WebApplicationContextUtils.getWebApplicationContext(getServletContext());

        }

    }

 

    protected Filter initDelegate(WebApplicationContext wac) throws ServletException {

        Filter delegate = wac.getBean(getTargetBeanName(), Filter.class);

       

        // 如果配置代理过滤器生命周期方法,则代理过滤器初始化方法

        if (isTargetFilterLifecycle()) {

            delegate.init(getFilterConfig());

        }

        return delegate;

    }

 

    protected void invokeDelegate(

            Filter delegate, ServletRequest request, ServletResponse response, FilterChain filterChain)

            throws ServletException, IOException {

 

        delegate.doFilter(request, response, filterChain);

    }

 

    protected void destroyDelegate(Filter delegate) {

        // 如果配置代理过滤器生命周期方法,则代理过滤器卸载方法

        if (isTargetFilterLifecycle()) {

            delegate.destroy();

        }

    }

}

 

由此可以看到,转接过滤器代理的最重要的功能就是把过滤器拦截到的请求转交给代理目标的过滤器的实现,目标过滤器是配置在Spring根共享环境的一个实现过滤器接口的Bean

 

另外,转接过滤器代理还根据配置进行代理过滤器的初始化和卸载事件。

 

转接过滤器代理将截获的HTTP请求代理给目标过滤器Bean的实现,这个实现是过滤器链代理类。过滤器链代理类首先调用Web应用程序环境中配置的Spring安全相关的过滤器,然后,再调用过滤器链中的其他过滤器。

 

Web应用程序环境中配置了多个Spring安全相关的过滤器,这些过滤器用来处理登录,登录失败,登录成功以及退出登录等事件。如下Web应用程序环境配置所示,

 

<http auto-config="true">

    <!-- Authentication policy --> 

    <form-login login-page="/users/login" login-processing-url="/users/login/authenticate" default-target-url="/main" authentication-failure-url="/users/login?login_error=1"/>

    <logout logout-url="/users/logout" logout-success-url="/users/logoutSuccess"/>

   

    <intercept-url pattern="/static/**" access="IS_AUTHENTICATED_ANONYMOUSLY"/>

    <intercept-url pattern="/users/login" access="IS_AUTHENTICATED_ANONYMOUSLY"/>

    <intercept-url pattern="/users/logoutSuccess" access="IS_AUTHENTICATED_ANONYMOUSLY"/>

    <intercept-url pattern="/**" access="ROLE_USER" />

</http>

 

<authentication-manager>

    <authentication-provider>

      <user-service>

        <user name="jimi" password="jimispassword" authorities="ROLE_USER, ROLE_ADMIN" />

        <user name="bob" password="bobspassword" authorities="ROLE_USER" />

      </user-service>

    </authentication-provider>

</authentication-manager>

 

我们看到上面的Web应用程序环境的配置,包含了登录页面URL, 登录失败页面URL, 登录成功页面URL等信息,它也包含了用来处理登录事件的URL和用于推出登录事件的URL,它同样包含了哪些URL需要什么样的角色才能访问和是否匿名访问等信息。

 

Spring安全框架在Web应用程序环境初始化的过程中根据上述信息生成相应的安全过滤器,这些安全过滤器按照顺序对截获的HTTP请求进行检查和处理,这些检查和处理操作如下,

 

l         如果当前HTTP请求是首次登录请求,则匿名过滤器则将请求转发到登录页面。

l         如果当前HTTP请求是登录请求,则用户名密码校验过滤器根据用户细节服务对当前用户进行认证和校验。如果认证和校验通过,则显示登录成功URL或者用户初始请求URL。如果不成功,则显示登录失败页面,通常登录失败页面也是登录页面,因此,用户能够修改登录信息进行重试。

l         如果当前HTTP请求是退出登录请求,则推出登录过滤器进行推出登录操作。

 

我们会在下面的小节里详细的剖析不同的流程是如何实现的,这里只对过滤器链代理类代码进行解析,过滤器链代理类根据各种过滤器的顺序,逐个调用相应的过滤器,来完成安全认证的过程。如下代码注释,

 

public class FilterChainProxy extends GenericFilterBean {

    // 原始的安全过滤器链集合,一个路径Pattern对应多个过滤器映射,在web.xml中配置的一个过滤器映射对应这里的一个入口项

    private Map<String, List<Filter>> uncompiledFilterChainMap;

 

    // 对路径Pattern处理过后的过滤器链映射

    private Map<Object, List<Filter>> filterChainMap;

 

    // 蚂蚁路径匹配器

    private UrlMatcher matcher = new AntUrlPathMatcher();

   

    // 匹配是是否使用包含参数的路径

    private boolean stripQueryStringFromUrls = true;

   

    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)

            throws IOException, ServletException {

   

    // 构造一个包含请求,相应和过滤器链的对象

        FilterInvocation fi = new FilterInvocation(request, response, chain);

   

        // 根据当前请求的URL找到所有应该处理的安全过滤器,这包含匿名过滤器,用户名密码校验过滤器,退出登录过滤器等等

        List<Filter> filters = getFilters(fi.getRequestUrl());

 

        // 如果没有找到任何关联到当前URL的安全过滤器

        if (filters == null || filters.size() == 0) {

            if (logger.isDebugEnabled()) {

                logger.debug(fi.getRequestUrl() +

                        filters == null ? " has no matching filters" : " has an empty filter list");

            }

 

            chain.doFilter(request, response);

 

            return;

        }

   

        // 虚拟过滤器链先对附加的过滤器进行调用,然后再调用原来的过滤器链

        VirtualFilterChain virtualFilterChain = new VirtualFilterChain(fi, filters);

        virtualFilterChain.doFilter(fi.getRequest(), fi.getResponse());

    }

 

    public List<Filter> getFilters(String url)  {

    // 如果配置移除查询串,则从URl中移除?之后的所有参数信息

        if (stripQueryStringFromUrls) {

            // String query string - see SEC-953

            int firstQuestionMarkIndex = url.indexOf("?");

 

            if (firstQuestionMarkIndex != -1) {

                url = url.substring(0, firstQuestionMarkIndex);

            }

        }

 

        for (Map.Entry<Object, List<Filter>> entry : filterChainMap.entrySet()) {

            Object path = entry.getKey();

            // 根据情况决定是否使用小写路径匹配

            if (matcher.requiresLowerCaseUrl()) {

                url = url.toLowerCase();

 

                if (logger.isDebugEnabled()) {

                    logger.debug("Converted URL to lowercase, from: '" + url + "'; to: '" + url + "'");

                }

            }

 

            boolean matched = matcher.pathMatchesUrl(path, url);

 

            if (logger.isDebugEnabled()) {

                logger.debug("Candidate is: '" + url + "'; pattern is " + path + "; matched=" + matched);

            }

                               

            // 找到匹配额过滤器集合

            if (matched) {

                return entry.getValue();

            }

        }

 

        return null;

    }

 

    // Web应用程序初始化的时候,安全框架根据配置的Bean信息生成过滤器链集合

    @SuppressWarnings("unchecked")

    public void setFilterChainMap(Map filterChainMap) {

    // 检查过滤器映射集合是否包含合法的内容

        checkContents(filterChainMap);

        uncompiledFilterChainMap = new LinkedHashMap<String, List<Filter>>(filterChainMap);

                    

        // 检查过滤器映射集合的路径是否有正确的顺序

        checkPathOrder();

        createCompiledMap();

    }

 

    @SuppressWarnings("unchecked")

    private void checkContents(Map filterChainMap) {

        for (Object key : filterChainMap.keySet()) {

          // 路径必须是字符串值

            Assert.isInstanceOf(String.class, key, "Path key must be a String but found " + key);

            Object filters = filterChainMap.get(key);

                               

            // 过滤器集合必须是列表类型

            Assert.isInstanceOf(List.class, filters, "Value must be a filter list");

            // Check the contents

            Iterator filterIterator = ((List)filters).iterator();

                                

            // 过滤器必须实现过滤器接口

            while (filterIterator.hasNext()) {

                Object filter = filterIterator.next();

                Assert.isInstanceOf(Filter.class, filter, "Objects in filter chain must be of type Filter. ");

            }

        }

    }

 

    private void checkPathOrder() {

        // Check that the universal pattern is listed at the end, if at all

    // 万能匹配路径必须在最后一个

        String[] paths = (String[]) uncompiledFilterChainMap.keySet().toArray(new String[0]);

        String universalMatch = matcher.getUniversalMatchPattern();

 

        for (int i=0; i < paths.length-1; i++) {

            if (paths[i].equals(universalMatch)) {

                throw new IllegalArgumentException("A universal match pattern " + universalMatch + " is defined " +

                        " before other patterns in the filter chain, causing them to be ignored. Please check the " +

                        "ordering in your <security:http> namespace or FilterChainProxy bean configuration");

            }

        }

    }

 

    private void createCompiledMap() {

        filterChainMap = new LinkedHashMap<Object, List<Filter>>(uncompiledFilterChainMap.size());

                     

        // 对路径Pattern进行转换,缺省实现下没有转换

        for (String path : uncompiledFilterChainMap.keySet()) {

            filterChainMap.put(matcher.compile(path), uncompiledFilterChainMap.get(path));

        }

    }

 

    private static class VirtualFilterChain implements FilterChain {

        private FilterInvocation fi;

        private List<Filter> additionalFilters;

        private int currentPosition = 0;

 

        private VirtualFilterChain(FilterInvocation filterInvocation, List<Filter> additionalFilters) {

            this.fi = filterInvocation;

            this.additionalFilters = additionalFilters;

        }

 

        public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {

          // 如果附加的安全过滤器全部处理完毕,则调用原来的过滤器链上的过滤器

            if (currentPosition == additionalFilters.size()) {

                if (logger.isDebugEnabled()) {

                    logger.debug(fi.getRequestUrl()

                        + " reached end of additional filter chain; proceeding with original chain");

                }

 

                fi.getChain().doFilter(request, response);

            } else {

                currentPosition++;

 

                // 对附加的安全过滤器逐个进行调用

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

 

                if (logger.isDebugEnabled()) {

                    logger.debug(fi.getRequestUrl() + " at position " + currentPosition + " of "

                        + additionalFilters.size() + " in additional filter chain; firing Filter: '"

                        + nextFilter + "'");

                }

 

               nextFilter.doFilter(request, response, this);

            }

        }

    }

}

 

猜你喜欢

转载自blog.csdn.net/robertleepeak/article/details/5993301
今日推荐