天天看點

Spring Security3學習-過濾器鍊

Spring Security3是目前使用非常廣泛的java web安全架構,我經曆的項目中有很多在使用它。盡管有shiro等使用更友善、更容易了解、應用範圍更廣的安全架構開始流行,但Spring Security3在Java web領域無疑是更強大、更容易擴充的。

對Spring Security3配置、應用的blog很多,我就不再介紹這些了。此系列文章主要根據我學習Spring Security3的過程,沿着Filter鍊介紹Spring Security3的原理。

啟用Spring Security3需要做如下三件事:

    1、在web.xml配置配置過濾器DelegatingFilterProxy

<filter> 
  <filter-name>springSecurityFilterChain</filter-name> 
  <filterclass> 
    org.springframework.web.filter.DelegatingFilterProxy 
  </filter-class> 
</filter> 
<filter-mapping> 
  <filter-name>springSecurityFilterChain</filter-name> 
  <url-pattern>/*</url-pattern> 
</filter-mapping>
           

    這裡filter的名字必須是springSecurityFilterChain,原因稍後解釋。

  2、建立spring-sucirity.xml

<?xml version="1.0" encoding="UTF-8"?> 
<beans:beans xmlns="http://www.springframework.org/schema/security" 
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
       xmlns:beans="http://www.springframework.org/schema/beans" 
       xsi:schemaLocation=" 
       http://www.springframework.org/schema/beans 
       http://www.springframework.org/schema/beans/spring-beans.xsd 
       http://www.springframework.org/schema/security 
       http://www.springframework.org/schema/security/  
              spring-security-3.0.xsd"> 
  <http auto-config="true"> 
    <intercept-url pattern="/*" access="ROLE_USER"/> 
  </http> 
  <authentication-manager alias="authenticationManager"> 
    <authentication-provider> 
      <user-service> 
        <user authorities="ROLE_USER" name="guest" password="guest"/> 
      </user-service> 
    </authentication-provider> 
  </authentication-manager>  
</beans:beans>
           

    3、添加spring-security.xml到web.xml 

<context-param>
	<param-name>contextConfigLocation</param-name>
	<param-value>classpath*:/spring-security.xml</param-value>
</context-param>
           

下面開始從入DelegatingFilterProxy手:

從名字上可以看出,這個過濾器其實沒做什麼,隻是委派給了其他過濾器。它并不在spring-security包中,而是在spring-web包

Spring Security3學習-過濾器鍊

 看它的doFilter方法

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?");
				}
                                //初始化FilterChain代理
				this.delegate = initDelegate(wac);
			}
			delegateToUse = this.delegate;
		}

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

 分析initDelegate方法 

protected Filter initDelegate(WebApplicationContext wac) throws ServletException {
		Filter delegate = wac.getBean(getTargetBeanName(), Filter.class);
		if (isTargetFilterLifecycle()) {
			delegate.init(getFilterConfig());
		}
		return delegate;
	}
           

 getTargetBeanName()其實擷取到的是Filter名字springSecurityFilterChain:在initFilterBean()方法中對targetBeanName做了指派

protected void initFilterBean() throws ServletException {
		// If no target bean name specified, use filter name.
		if (this.targetBeanName == null) {
                        //targetBeanName被設定為filterName
			this.targetBeanName = getFilterName();
		}
                ......
	}
           

 initFilterBean方法在父類GenericFilterBean的init方法中被執行。 這裡根據我們在web.xml設定filter的名字去擷取Spring Seucurity3的代理過濾器。後面回看到這個名字是springSecurityFilterChain,是以在web.xml的filter名字也必須是這個。  再分析spring-scurity.xml的解析: org.springframework.security.config.SecurityNamespaceHandler負責解析namespace為security的xml文檔,即我們的spring-security.xml檔案,它在spring-security-config包:

Spring Security3學習-過濾器鍊

SecurityNamespaceHandler繼承了NamespaceHandler接口,有三個方法:init初始化,parse解析,decorate裝飾,在SecurityNamespaceHandler的init方法進行了每個解析類的注冊

private void loadParsers() {
        // Parsers
        parsers.put(Elements.LDAP_PROVIDER, new LdapProviderBeanDefinitionParser());
        parsers.put(Elements.LDAP_SERVER, new LdapServerBeanDefinitionParser());
        parsers.put(Elements.LDAP_USER_SERVICE, new LdapUserServiceBeanDefinitionParser());
        parsers.put(Elements.USER_SERVICE, new UserServiceBeanDefinitionParser());
        parsers.put(Elements.JDBC_USER_SERVICE, new JdbcUserServiceBeanDefinitionParser());
        parsers.put(Elements.AUTHENTICATION_PROVIDER, new AuthenticationProviderBeanDefinitionParser());
        parsers.put(Elements.GLOBAL_METHOD_SECURITY, new GlobalMethodSecurityBeanDefinitionParser());
        parsers.put(Elements.AUTHENTICATION_MANAGER, new AuthenticationManagerBeanDefinitionParser());

        // Only load the web-namespace parsers if the web classes are available
        if (ClassUtils.isPresent("org.springframework.security.web.FilterChainProxy", getClass().getClassLoader())) {
            parsers.put(Elements.HTTP, new HttpSecurityBeanDefinitionParser());
            parsers.put(Elements.HTTP_FIREWALL, new HttpFirewallBeanDefinitionParser());
            parsers.put(Elements.FILTER_INVOCATION_DEFINITION_SOURCE, new FilterInvocationSecurityMetadataSourceParser());
            parsers.put(Elements.FILTER_SECURITY_METADATA_SOURCE, new FilterInvocationSecurityMetadataSourceParser());
            filterChainMapBDD = new FilterChainMapBeanDefinitionDecorator();
        }
    }
           

 HttpSecurityBeanDefinitionParser時解析http節點的解析類

parsers.put(Elements.HTTP, new HttpSecurityBeanDefinitionParser());
           

 看一下HttpSecurityBeanDefinitionParser是如何解析http節點的,上parse

public BeanDefinition parse(Element element, ParserContext pc) {
        CompositeComponentDefinition compositeDef =
            new CompositeComponentDefinition(element.getTagName(), pc.extractSource(element));
        pc.pushContainingComponent(compositeDef);
        final Object source = pc.extractSource(element);

        final String portMapperName = createPortMapper(element, pc);
        final UrlMatcher matcher = createUrlMatcher(element);

        HttpConfigurationBuilder httpBldr = new HttpConfigurationBuilder(element, pc, matcher, portMapperName);

        httpBldr.parseInterceptUrlsForEmptyFilterChains();
        httpBldr.createSecurityContextPersistenceFilter();
        httpBldr.createSessionManagementFilters();

        ManagedList<BeanReference> authenticationProviders = new ManagedList<BeanReference>();
        BeanReference authenticationManager = createAuthenticationManager(element, pc, authenticationProviders, null);

        httpBldr.createServletApiFilter();
        httpBldr.createChannelProcessingFilter();
        httpBldr.createFilterSecurityInterceptor(authenticationManager);

        AuthenticationConfigBuilder authBldr = new AuthenticationConfigBuilder(element, pc,
                httpBldr.isAllowSessionCreation(), portMapperName);
        //http節點的預設的11個過濾器
        authBldr.createAnonymousFilter();
        authBldr.createRememberMeFilter(authenticationManager);
        authBldr.createRequestCache();
        authBldr.createBasicFilter(authenticationManager);
        authBldr.createFormLoginFilter(httpBldr.getSessionStrategy(), authenticationManager);
        authBldr.createOpenIDLoginFilter(httpBldr.getSessionStrategy(), authenticationManager);
        authBldr.createX509Filter(authenticationManager);
        authBldr.createLogoutFilter();
        authBldr.createLoginPageFilterIfNeeded();
        authBldr.createUserServiceInjector();
        authBldr.createExceptionTranslationFilter();

        List<OrderDecorator> unorderedFilterChain = new ArrayList<OrderDecorator>();
        //将過濾器加入過濾器鍊
        unorderedFilterChain.addAll(httpBldr.getFilters());
        unorderedFilterChain.addAll(authBldr.getFilters());//這裡加入的過濾器和我們設定auto-config="true"有關

        authenticationProviders.addAll(authBldr.getProviders());

        BeanDefinition requestCacheAwareFilter = new RootBeanDefinition(RequestCacheAwareFilter.class);
        requestCacheAwareFilter.getPropertyValues().addPropertyValue("requestCache", authBldr.getRequestCache());
        unorderedFilterChain.add(new OrderDecorator(requestCacheAwareFilter, REQUEST_CACHE_FILTER));

        unorderedFilterChain.addAll(buildCustomFilterList(element, pc));
        //對過濾器進行排序
        Collections.sort(unorderedFilterChain, new OrderComparator());
        checkFilterChainOrder(unorderedFilterChain, pc, source);
        //排好序的過濾器鍊
        List<BeanMetadataElement> filterChain = new ManagedList<BeanMetadataElement>();

        for (OrderDecorator od : unorderedFilterChain) {
            filterChain.add(od.bean);
        }

        ManagedMap<Object, List<BeanMetadataElement>> filterChainMap = httpBldr.getFilterChainMap();
        filterChainMap.put(matcher.getUniversalMatchPattern(), filterChain);
        //注冊過濾器鍊proxy
        registerFilterChainProxy(pc, filterChainMap, matcher, source);

        pc.popAndRegisterContainingComponent();
        return null;
    }
           

    http節點的11個過濾器不是都要加入過濾器鍊的,這個和我們的auto-config有關。使用auto-config="true"會自動提供以下三個認證相關的功能:     HTTP基本認證     Form登入認證     退出 這一點從源碼中可以找到:AuthenticationConfigBuilder類的createLogoutFilter、createBasicFilter和createFormLoginFilter三個方法

void createLogoutFilter() {
        Element logoutElt = DomUtils.getChildElementByTagName(httpElt, Elements.LOGOUT);
        if (logoutElt != null || autoConfig) {
            logoutFilter = new LogoutBeanDefinitionParser(rememberMeServicesId).parse(logoutElt, pc);
        }
    }
           
void createBasicFilter(BeanReference authManager) {
        ......
        if (basicAuthElt != null || autoConfig) {
            ......
        }
      ......
    }
           
void createFormLoginFilter(BeanReference sessionStrategy, BeanReference authManager) {
      ......
        if (formLoginElt != null || autoConfig) {
          ......
        }
       ......
    }
           

 可見隻有autoConfig=true 時才會加入這仨過濾器。 在對unorderedFilterChain進行排序時,這11個過濾器怎麼排序的呢?org.springframework.core.OrderComparator的排序邏輯是根據org.springframework.core.Ordered的getOrder方法的比較。在AuthenticationConfigBuilder的getFilters方法傳回的事List<OrderDecorator>集合,OrderDecorator的構造方法:

public OrderDecorator(BeanMetadataElement bean, SecurityFilters filterOrder) {
        this.bean = bean;
        this.order = filterOrder.getOrder();
    }
           

 繼續看SecurityFilters枚舉類

enum SecurityFilters {
    FIRST (Integer.MIN_VALUE),
    CHANNEL_FILTER,
    CONCURRENT_SESSION_FILTER,
    SECURITY_CONTEXT_FILTER,
    LOGOUT_FILTER,
    X509_FILTER,
    PRE_AUTH_FILTER,
    CAS_FILTER,
    FORM_LOGIN_FILTER,
    OPENID_FILTER,
    LOGIN_PAGE_FILTER,
    DIGEST_AUTH_FILTER,
    BASIC_AUTH_FILTER,
    REQUEST_CACHE_FILTER,
    SERVLET_API_SUPPORT_FILTER,
    REMEMBER_ME_FILTER,
    ANONYMOUS_FILTER,
    SESSION_MANAGEMENT_FILTER,
    EXCEPTION_TRANSLATION_FILTER,
    FILTER_SECURITY_INTERCEPTOR,
    SWITCH_USER_FILTER,
    LAST (Integer.MAX_VALUE);

    private static final int INTERVAL = 100;
    private final int order;

    private SecurityFilters() {
        order = ordinal() * INTERVAL;
    }

    private SecurityFilters(int order) {
        this.order = order;
    }

    public int getOrder() {
       return order;
    }
}
           

 可見是根據枚舉的定義進行排序的,在http節點的配置中,我們将自定一個的過濾器用before、after、position等關鍵字插入到這11個過濾器前後。 再看注冊過濾器鍊:

private void registerFilterChainProxy(ParserContext pc, Map<Object, List<BeanMetadataElement>> filterChainMap, UrlMatcher matcher, Object source) {
        if (pc.getRegistry().containsBeanDefinition(BeanIds.FILTER_CHAIN_PROXY)) {
            pc.getReaderContext().error("Duplicate <http> element detected", source);
        }
        //定義一個FilterChainProxy
        BeanDefinitionBuilder fcpBldr = BeanDefinitionBuilder.rootBeanDefinition(FilterChainProxy.class);
        fcpBldr.getRawBeanDefinition().setSource(source);
        fcpBldr.addPropertyValue("matcher", matcher);
        fcpBldr.addPropertyValue("stripQueryStringFromUrls", Boolean.valueOf(matcher instanceof AntUrlPathMatcher));
        fcpBldr.addPropertyValue("filterChainMap", filterChainMap);//設定過濾器鍊集合
        BeanDefinition fcpBean = fcpBldr.getBeanDefinition();
        //注冊bean
        pc.registerBeanComponent(new BeanComponentDefinition(fcpBean, BeanIds.FILTER_CHAIN_PROXY));
        //注冊的别名為springSecurityFilterChain,在web.xml配置的filter名也要是這個
        pc.getRegistry().registerAlias(BeanIds.FILTER_CHAIN_PROXY, BeanIds.SPRING_SECURITY_FILTER_CHAIN);
    }
           

 那麼在DelegatingFilterProxy的initDelegate方法就可以擷取到這個過濾器鍊了,然後就是doFilter了。   好了,囫囵吞棗的列出這麼多,也不是很連貫,但大概應該能說明白原理了。下一篇開始分析過濾器,是以再把過濾器列出來:

過濾器名稱 描述
o.s.s.web.context.SecurityContextPersistenceFilter 負責從SecurityContextRepository擷取或存儲SecurityContext。SecurityContext代表了使用者安全和認證過的session。
o.s.s.web.authentication.logout.LogoutFilter 監控一個實際為退出功能的URL(預設為/j_spring_security_logout),并且在比對的時候完成使用者的退出功能。
o.s.s.web.authentication.UsernamePasswordAuthenticationFilter 監控一個使用使用者名和密碼基于form認證的URL(預設為/j_spring_security_check),并在URL比對的情況下嘗試認證該使用者。
o.s.s.web.authentication.ui.DefaultLoginPageGeneratingFilter 監控一個要進行基于forn或OpenID認證的URL(預設為/spring_security_login),并生成展現登入form的HTML
o.s.s.web.authentication.www.BasicAuthenticationFilter 監控HTTP 基礎認證的頭資訊并進行處理

o.s.s.web.savedrequest.

RequestCacheAwareFilter

用于使用者登入成功後,重新恢複因為登入被打斷的請求。

o.s.s.web.servletapi.

SecurityContextHolderAwareRequest

Filter

用一個擴充了HttpServletRequestWrapper的子類(o.s.s.web. servletapi.SecurityContextHolderAwareRequestWrapper)包裝HttpServletRequest。它為請求處理器提供了額外的上下文資訊。

o.s.s.web.authentication.

AnonymousAuthenticationFilter

如果使用者到這一步還沒有經過認證,将會為這個請求關聯一個認證的token,辨別此使用者是匿名的。

o.s.s.web.session.

SessionManagementFilter

根據認證的安全實體資訊跟蹤session,保證所有關聯一個安全實體的session都能被跟蹤到。

o.s.s.web.access.

ExceptionTranslationFilter

解決在處理一個請求時産生的指定異常

o.s.s.web.access.intercept.

FilterSecurityInterceptor

簡化授權和通路控制決定,委托一個AccessDecisionManager完成授權的判斷