Dynamically Extending or Modifying the Acegi Security Chain in SBS 4.0 and later

Version 2

    Introduction

     

    This document presents a simple yet powerful approach to adding custom security filters deployed by a plugin. These filters allows a plugin to intercept requests and perform operations during the authentication phase.

     

    Problem Defintion

     

    The Acegi security filters are defined in spring-security.xml in a bean called "filterChainProxy".  This filter chain proxy is composed of a series of URL patterns and each URL pattern has an associated ordered list of filters. Often there is a need for plug-ins or other customizations to either modify the list of filters associated with a URL pattern or add a new URL pattern with it's own associated list. The Acegi API provides the ability to get access to the list of filters for each URL pattern and allows you to replace the entire list. This was the technique described in the aforementioned blog post. However it did not provide any ability to add new URL patterns. Furthermore, the coding required to do this was quite verbose and required you to copy the existing list of filters and re-write it back in order to preserve existing behavior.

     

    Example extract of the filter chain proxy bean:

     

     <bean id="filterChainProxy">
            <property name="filterInvocationDefinitionSource">
                <value>
                    ...     
                    /admin/**=httpSessionContextIntegrationFilter, sessionTrackingFilter, adminAuthenticationFilter, openfireAuthenticationFilter, adminExceptionTranslationFilter,jiveAuthenticationTranslationFilter
                    ...
                    /__services/**=httpSessionContextIntegrationFilter, basicAuthenticationFilter, wsExceptionTranslator, jiveAuthenticationTranslationFilter
                    ...               
                    /**=httpSessionContextIntegrationFilter, sessionTrackingFilter, formAuthenticationFilter, loginPopupFormAuthenticationFilter, rememberMeProcessingFilter, feedBasicAuthenticationFilter, exceptionTranslationFilter, jiveAuthenticationTranslationFilter,contextOptimizationFilter
                    ...
    
               </value>
            </property>
        </bean>
    

     

    We need a better way to add new filters into existing chain and also provide for new URL patterns.

     

    Solution

     

    Since each filter can also be treated as a filter chain proxy, we can add standardized filter chain proxies to the filter lists in the hard coded spring-security.xml. By defining these filter chain proxies as "org.springframework.security.util.FilterChainProxy", we are provided with a much more flexibile API where we can control both the list of filters and the actual URL mapping.

     

    Using the example extract above, the following standardized filter chain proxies have been defined and added to spring-security.xml in SBS 4.0:

     

        <bean id="filterChainProxy">
            <property name="filterInvocationDefinitionSource">
                <value>     
                    ...
                    /admin/**=pluginPreFilterChain, httpSessionContextIntegrationFilter, pluginPostSessionContextFilterChain, sessionTrackingFilter, adminAuthenticationFilter, openfireAuthenticationFilter, adminExceptionTranslationFilter,jiveAuthenticationTranslationFilter, pluginPostFilterChain
                    ...
                    /__services/**=pluginPreFilterChain, httpSessionContextIntegrationFilter, pluginPostSessionContextFilterChain, basicAuthenticationFilter, wsExceptionTranslator, jiveAuthenticationTranslationFilter, pluginPostFilterChain
                    ...
                    /**=pluginPreFilterChain, httpSessionContextIntegrationFilter, pluginPostSessionContextFilterChain, sessionTrackingFilter, formAuthenticationFilter, loginPopupFormAuthenticationFilter, rememberMeProcessingFilter, feedBasicAuthenticationFilter, exceptionTranslationFilter, jiveAuthenticationTranslationFilter,contextOptimizationFilter, pluginPostFilterChain
               </value>
            </property>
        </bean>
    
        <bean id="pluginPreFilterChain">
            <security:filter-chain-map path-type="ant">
                <security:filter-chain pattern="do-not-filter-stub" filters="none" />
            </security:filter-chain-map>
        </bean>
    
    
        <!-- Filter chain that is processed right after the httpSessionContextIntegrationFilter has been processed -->
        <bean id="pluginPostSessionContextFilterChain">
            <security:filter-chain-map path-type="ant">
                <security:filter-chain pattern="do-not-filter-stub" filters="none" />
            </security:filter-chain-map>
        </bean>
    
        <!-- Filter chain that is processed after all other filters have been processed -->
        <bean id="pluginPostFilterChain">
            <security:filter-chain-map path-type="ant">
                <security:filter-chain pattern="do-not-filter-stub" filters="none" />
            </security:filter-chain-map>
        </bean>
    
    

     

     

    Three standard filter chains have been provided; pluginPreFilterChain, pluginPostSessionContextFilterChain and pluginPostFilterChain. Of the three pluginPreFilterChain is the most powerful as it will be processed before the others, allowing you to potentially override subsequent processing. The other two have been provided for convenience as common customization require inserting processing in the middle of the chains.

     

    These filter chain proxies are defined as empty stubs in the hard coded spring file. Custom code can add new URL patterns and associate a list of filters.

     

    For example, consider the need to add our custom processing filter (smeAuthFilter) to our restful service which is accessed at the URL /__services/sme. In order to do this we need to add a sub filter chain to the existing pattern /__services/**. We will use the pluginPreFilterChain to ensure that we get priority processing. This can be achieved with the following code which should be called from your plugin init method:

     

        private void addWebserviceSecurityFilter() {
            JiveContext context = JiveApplication.getContext();
            FilterChainProxy chain = (FilterChainProxy) context.getSpringBean("pluginPostSessionContextFilterChain");
            Map filterChainMap = chain.getFilterChainMap();
            LinkedHashMap<String, List<Filter>> newMap = new LinkedHashMap<String, List<Filter>>();
            newMap.put("/__services/sme/**", getFilterList("smeWebServiceAuthFilter"));
            newMap.putAll(filterChainMap);
            chain.setFilterChainMap(newMap);
        }
    
        private List<Filter> getFilterList(String... names) {
            JiveContext context = JiveApplication.getContext();
            List<Filter> filters = Lists.newArrayList();
            for (String name : names) {
                filters.add((Filter) context.getSpringBean(name));
            }
            return filters;
        }
    

     

    This technique can be used to modify or extend any existing URL access pattern. For example, one could add a new URL pattern to block an existing specific web service.