1. 程式人生 > >Spring Security4.0.3原始碼分析之FilterChainProxy初始化

Spring Security4.0.3原始碼分析之FilterChainProxy初始化

最近在學習安全框架Spring Security,想弄清楚其中實現的具體步驟,於是下定決心,研究一下Spring Security原始碼,這篇部落格的目的是想把學習過程記錄下來。學習過程中主要參考了http://dead-knight.iteye.com/blog/1511389大神的部落格,然後在其基礎上,進行更詳細的說明

1.Spring Security入口在是在web.xml配置瞭如下的過濾器,那麼這個過濾器到底是怎麼工作的呢

<!-- SpringSecurity需要的filter -->
<filter>
    <filter-name>spring-security</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>spring-security</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>

我們開啟DelegatingFilterProxy類,發現這個類位於org.springframework.web-3.0.1.RELEASE.jar下面,說明這個類本身是和springSecurity無關。

2.研究一下DelegatingFilterProxy類到底是幹嘛的

2.1 DelegatingFilterProxy首先是呼叫了initFilterBean方法,初始化過濾器

@Override
protected void initFilterBean() throws ServletException {
    synchronized (this.delegateMonitor) {
        if (this.delegate == null) {
            // 如果沒配置targetBeanName,那麼預設使用過濾器的名字
            if (this.targetBeanName == null) {
                this.targetBeanName = getFilterName();
            }
            // 獲取Spring上下文,並且初始化Filter
            // 如果可能的話,在Spring上下文初始化前初始化Filter,我們會使用延遲載入策略
            WebApplicationContext wac = findWebApplicationContext();
            if (wac != null) {
                // 初始化springSecurityFilterChain,詳解請移步2.3
                this.delegate = initDelegate(wac);
            }
        }
    }
}

2.2 然後呼叫了doFilter方法,執行過濾器方法

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws ServletException, IOException {
    Filter delegateToUse = this.delegate;
    if (delegateToUse == null) {
        synchronized (this.delegateMonitor) {
            if (this.delegate == null) {
                WebApplicationContext wac = findWebApplicationContext();
                if (wac == null) {
                    throw new IllegalStateException("No WebApplicationContext found: no ContextLoaderListener registered?");
                }
                // 初始化springSecurityFilterChain,詳解請移步2.3
                this.delegate = initDelegate(wac);
            }
            delegateToUse = this.delegate;
        }
    }

    // 讓委託執行實際的doFilter操作
    // 這裡的delegateToUse實際上就是springSecurityFilterChain例項,詳解請移步4
    invokeDelegate(delegateToUse, request, response, filterChain);
}

2.3 接著來看看上面提到的initDelegate(wac)方法

// 初始化FilterChainProxy
protected Filter initDelegate(WebApplicationContext wac) throws ServletException {
    // getTargetBeanName()返回的是Filter的name:springSecurityFilterChain
    // 根據springSecurityFilterChain的bean name直接獲取FilterChainProxy的例項
    Filter delegate = wac.getBean(getTargetBeanName(), Filter.class);
    if (isTargetFilterLifecycle()) {
        delegate.init(getFilterConfig());
    }
    return delegate;
}

這裡大家會產生疑問,springSecurityFilterChain這個bean在哪裡定義的呢?先把這個問題拋開,稍後會作出解答

3. 標籤配置的初始化
在spring-security-config-4.0.3.RELEASE.jar包的META-INF目錄下有2個檔案spring.handlers和spring.schemas。其中spring.schemas檔案主要是標籤的規範,約束;而spring.handlers這個檔案時真正解析自定義標籤的類,這個檔案的內容為:

http\://www.springframework.org/schema/security=org.springframework.security.config.SecurityNamespaceHandler

3.1 標籤解析
從上面可以看出來Spring Security的標籤解析由org.springframework.security.config.SecurityNamespaceHandler來處理。該類實現介面:NamespaceHandler,Spring中自定義標籤都要實現該介面,這個介面有三個方法init、parse、decorate,其中init用於自定義標籤的初始化,parse用於解析標籤,decorate用於裝飾。SecurityNamespaceHandler類的init方法完成了標籤解析類的註冊工作。開啟SecurityNamespaceHandler原始碼。

public void init() {
    loadParsers();
}

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());
    parsers.put(Elements.METHOD_SECURITY_METADATA_SOURCE, new MethodSecurityMetadataSourceBeanDefinitionParser());

    // Only load the web-namespace parsers if the web classes are available
    if (ClassUtils.isPresent(FILTER_CHAIN_PROXY_CLASSNAME, getClass().getClassLoader())) {
        parsers.put(Elements.DEBUG, new DebugBeanDefinitionParser());
        parsers.put(Elements.HTTP, new HttpSecurityBeanDefinitionParser());
        parsers.put(Elements.HTTP_FIREWALL, new HttpFirewallBeanDefinitionParser());
        parsers.put(Elements.FILTER_SECURITY_METADATA_SOURCE, new FilterInvocationSecurityMetadataSourceParser());
        parsers.put(Elements.FILTER_CHAIN, new FilterChainBeanDefinitionParser());
        filterChainMapBDD = new FilterChainMapBeanDefinitionDecorator();
    }

    if (ClassUtils.isPresent(MESSAGE_CLASSNAME, getClass().getClassLoader())) {
        parsers.put(Elements.WEBSOCKET_MESSAGE_BROKER, new WebSocketMessageBrokerSecurityBeanDefinitionParser());
    }
}

上面可以看出SecurityNamespaceHandler類的init方法完成了標籤解析類的註冊工作,並且http的標籤解析類註冊程式碼為:

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

3.2 HttpSecurityBeanDefinitionParser是如何解析HTTP標籤的?

@SuppressWarnings({ "unchecked" })
public BeanDefinition parse(Element element, ParserContext pc) {
    CompositeComponentDefinition compositeDef = new CompositeComponentDefinition(element.getTagName(), pc.extractSource(element));
    pc.pushContainingComponent(compositeDef);
    // 這裡建立了listFactoryBean例項和springSecurityFilterChain例項,詳解移步3.2.1
    registerFilterChainProxyIfNecessary(pc, pc.extractSource(element));
    // 獲取listFactoryBean例項
    BeanDefinition listFactoryBean = pc.getRegistry().getBeanDefinition(BeanIds.FILTER_CHAINS);
    List<BeanReference> filterChains = (List<BeanReference>) listFactoryBean.getPropertyValues().getPropertyValue("sourceList").getValue();
    // 建立過濾器鏈程式碼,詳解移步3.2.2
    filterChains.add(createFilterChain(element, pc));
    pc.popAndRegisterContainingComponent();
    return null;
}

3.2.1 registerFilterChainProxyIfNecessary分析

static void registerFilterChainProxyIfNecessary(ParserContext pc, Object source) {
    // 判斷是否已經註冊了FILTER_CHAIN_PROXY,若已經註冊則直接返回
    if (pc.getRegistry().containsBeanDefinition(BeanIds.FILTER_CHAIN_PROXY)) {
        return;
    }
    // 註冊ListFactoryBean
    BeanDefinition listFactoryBean = new RootBeanDefinition(ListFactoryBean.class);
    listFactoryBean.getPropertyValues().add("sourceList", new ManagedList());
    pc.registerBeanComponent(new BeanComponentDefinition(listFactoryBean, BeanIds.FILTER_CHAINS));

    // 註冊FilterChainProxy
    BeanDefinitionBuilder fcpBldr = BeanDefinitionBuilder.rootBeanDefinition(FilterChainProxy.class);
    fcpBldr.getRawBeanDefinition().setSource(source);
    fcpBldr.addConstructorArgReference(BeanIds.FILTER_CHAINS);
    fcpBldr.addPropertyValue("filterChainValidator", new RootBeanDefinition(DefaultFilterChainValidator.class));
    BeanDefinition fcpBean = fcpBldr.getBeanDefinition();
    pc.registerBeanComponent(new BeanComponentDefinition(fcpBean, BeanIds.FILTER_CHAIN_PROXY));
    // 此處為FILTER_CHAIN_PROXY取別名為springSecurityFilterChain
    pc.getRegistry().registerAlias(BeanIds.FILTER_CHAIN_PROXY, BeanIds.SPRING_SECURITY_FILTER_CHAIN);
}

拋開前面的程式碼不看,由最後一行可以看出這裡建立了springSecurityFilterChain例項,到這裡終於和前面的掛鉤起來了

3.2.2 createFilterChain分析

// 建立過濾器鏈的程式碼
private BeanReference createFilterChain(Element element, ParserContext pc) {
    // 判斷是否需要SpringSecurity攔截
    boolean secured = !OPT_SECURITY_NONE.equals(element.getAttribute(ATT_SECURED));
    if (!secured) {
        // 如果沒pattern並且配置request-matcher-ref為空 新增錯誤資訊
        if (!StringUtils.hasText(element.getAttribute(ATT_PATH_PATTERN)) && !StringUtils.hasText(ATT_REQUEST_MATCHER_REF)) {
            pc.getReaderContext().error("The '" + ATT_SECURED + "' attribute must be used in combination with" + " the '" + ATT_PATH_PATTERN + "' or '" + ATT_REQUEST_MATCHER_REF + "' attributes.", pc.extractSource(element));
        }
        for (int n = 0; n < element.getChildNodes().getLength(); n++) {
            if (element.getChildNodes().item(n) instanceof Element) {
                // 如果element有子節點並且instanceofElement 新增錯誤資訊
                pc.getReaderContext().error("If you are using <http> to define an unsecured pattern, " + "it cannot contain child elements.", pc.extractSource(element));
            }
        }

        return createSecurityFilterChainBean(element, pc, Collections.emptyList());
    }

    final BeanReference portMapper = createPortMapper(element, pc);
    final BeanReference portResolver = createPortResolver(portMapper, pc);

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

    boolean forceAutoConfig = isDefaultHttpConfig(element);
    // HttpConfigurationBuilder建立過濾器鏈,詳情移步3.2.2.1
    HttpConfigurationBuilder httpBldr = new HttpConfigurationBuilder(element, forceAutoConfig, pc, portMapper, portResolver, authenticationManager);
    // AuthenticationConfigBuilder建立過濾器鏈,詳情移步3.2.2.2
    AuthenticationConfigBuilder authBldr = new AuthenticationConfigBuilder(element, forceAutoConfig, pc, httpBldr.getSessionCreationPolicy(), httpBldr.getRequestCache(), authenticationManager, httpBldr.getSessionStrategy(), portMapper, portResolver, httpBldr.getCsrfLogoutHandler());

    httpBldr.setLogoutHandlers(authBldr.getLogoutHandlers());
    httpBldr.setEntryPoint(authBldr.getEntryPointBean());
    httpBldr.setAccessDeniedHandler(authBldr.getAccessDeniedHandlerBean());

    authenticationProviders.addAll(authBldr.getProviders());

    List<OrderDecorator> unorderedFilterChain = new ArrayList<OrderDecorator>();

    // 新增過濾器到unorderedFilterChain
    unorderedFilterChain.addAll(httpBldr.getFilters());
    unorderedFilterChain.addAll(authBldr.getFilters());
    unorderedFilterChain.addAll(buildCustomFilterList(element, pc));

    // 對過濾器鏈進行排序,詳細分析請移步3.2.2.3
    Collections.sort(unorderedFilterChain, new OrderComparator());
    // 對過濾器鏈正確性檢查
    checkFilterChainOrder(unorderedFilterChain, pc, pc.extractSource(element));

    // The list of filter beans
    List<BeanMetadataElement> filterChain = new ManagedList<BeanMetadataElement>();

    for (OrderDecorator od : unorderedFilterChain) {
        filterChain.add(od.bean);
    }
    // 建立過濾器鏈,詳情移步3.2.2.4
    return createSecurityFilterChainBean(element, pc, filterChain);
}

3.2.2.1 HttpConfigurationBuilder建立過濾器鏈分析

public HttpConfigurationBuilder(Element element, boolean addAllAuth, ParserContext pc, BeanReference portMapper, BeanReference portResolver, BeanReference authenticationManager) {
    this.httpElt = element;
    this.addAllAuth = addAllAuth;
    this.pc = pc;
    this.portMapper = portMapper;
    this.portResolver = portResolver;
    this.matcherType = MatcherType.fromElement(element);
    interceptUrls = DomUtils.getChildElementsByTagName(element, Elements.INTERCEPT_URL);

    for (Element urlElt : interceptUrls) {
        if (StringUtils.hasText(urlElt.getAttribute(ATT_FILTERS))) {
            pc.getReaderContext().error("The use of \"filters='none'\" is no longer supported. Please define a" + " separate <http> element for the pattern you want to exclude and use the attribute" + " \"security='none'\".", pc.extractSource(urlElt));
        }
    }

    String createSession = element.getAttribute(ATT_CREATE_SESSION);

    if (StringUtils.hasText(createSession)) {
        sessionPolicy = createPolicy(createSession);
    } else {
        sessionPolicy = SessionCreationPolicy.IF_REQUIRED;
    }

    createCsrfFilter();
    createSecurityContextPersistenceFilter();
    createSessionManagementFilters();
    createWebAsyncManagerFilter();
    createRequestCacheFilter();
    createServletApiFilter(authenticationManager);
    createJaasApiFilter();
    createChannelProcessingFilter();
    createFilterSecurityInterceptor(authenticationManager);
    createAddHeadersFilter();
}

3.2.2 AuthenticationConfigBuilder建立過濾器鏈分析

public AuthenticationConfigBuilder(Element element, boolean forceAutoConfig, ParserContext pc, SessionCreationPolicy sessionPolicy, BeanReference requestCache, BeanReference authenticationManager, BeanReference sessionStrategy, BeanReference portMapper, BeanReference portResolver, BeanMetadataElement csrfLogoutHandler) {
    this.httpElt = element;
    this.pc = pc;
    this.requestCache = requestCache;
    autoConfig = forceAutoConfig | "true".equals(element.getAttribute(ATT_AUTO_CONFIG));
    this.allowSessionCreation = sessionPolicy != SessionCreationPolicy.NEVER && sessionPolicy != SessionCreationPolicy.STATELESS;
    this.portMapper = portMapper;
    this.portResolver = portResolver;
    this.csrfLogoutHandler = csrfLogoutHandler;

    createAnonymousFilter();
    createRememberMeFilter(authenticationManager);
    createBasicFilter(authenticationManager);
    createFormLoginFilter(sessionStrategy, authenticationManager);
    createOpenIDLoginFilter(sessionStrategy, authenticationManager);
    createX509Filter(authenticationManager);
    createJeeFilter(authenticationManager);
    createLogoutFilter();
    createLoginPageFilterIfNeeded();
    createUserDetailsServiceFactory();
    createExceptionTranslationFilter();
}

這裡可以看到autoConfig屬性,如果該屬性為true,SpringSecurity就會自動配置好過濾器鏈。具體如何設定檢視AuthenticationConfigBuilder的create*Filter方法即可看到,具體Create分析下一篇再細說。

3.2.2.3 對過濾器鏈排序分析

排序主要是由Collections.sort(unorderedFilterChain, new OrderComparator());這行程式碼實現的

unorderedFilterChain是OrderDecorator的List集合,開啟OrderDecorator原始碼,檢視建構函式
public OrderDecorator(BeanMetadataElement bean, SecurityFilters filterOrder) {
    this.bean = bean;
    this.order = filterOrder.getOrder();
}
這裡我們發現它其實是根據SecurityFilters定義好的順序進行排序的

enum SecurityFilters {
    FIRST(Integer.MIN_VALUE), CHANNEL_FILTER, SECURITY_CONTEXT_FILTER, CONCURRENT_SESSION_FILTER,

    /** {@link WebAsyncManagerIntegrationFilter} */
    WEB_ASYNC_MANAGER_FILTER, HEADERS_FILTER, CSRF_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, JAAS_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;
    }
}

3.2.2.4 建立過濾器鏈分析

private BeanReference createSecurityFilterChainBean(Element element, ParserContext pc, List<?> filterChain) {
    BeanMetadataElement filterChainMatcher;

    String requestMatcherRef = element.getAttribute(ATT_REQUEST_MATCHER_REF);
    String filterChainPattern = element.getAttribute(ATT_PATH_PATTERN);

    if (StringUtils.hasText(requestMatcherRef)) {
        if (StringUtils.hasText(filterChainPattern)) {
            pc.getReaderContext().error("You can't define a pattern and a request-matcher-ref for the " + "same filter chain", pc.extractSource(element));
        }
        filterChainMatcher = new RuntimeBeanReference(requestMatcherRef);
    } else if (StringUtils.hasText(filterChainPattern)) {
        filterChainMatcher = MatcherType.fromElement(element).createMatcher(filterChainPattern, null);
    } else {
        filterChainMatcher = new RootBeanDefinition(AnyRequestMatcher.class);
    }

    BeanDefinitionBuilder filterChainBldr = BeanDefinitionBuilder.rootBeanDefinition(DefaultSecurityFilterChain.class);
    filterChainBldr.addConstructorArgValue(filterChainMatcher);
    filterChainBldr.addConstructorArgValue(filterChain);

    BeanDefinition filterChainBean = filterChainBldr.getBeanDefinition();

    String id = element.getAttribute("name");
    if (!StringUtils.hasText(id)) {
        id = element.getAttribute("id");
        if (!StringUtils.hasText(id)) {
            id = pc.getReaderContext().generateBeanName(filterChainBean);
        }
    }

    pc.registerBeanComponent(new BeanComponentDefinition(filterChainBean, id));

    return new RuntimeBeanReference(id);
}

4. 呼叫代理類的doFilter方法

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

通過以上分析,對FilterChainProxy如何產生的,以及Spring Security的標籤是如何解析有了大體的認識。具體標籤的解析,Filter鏈的執行,下次再更新……