Skip to content
Advertisement

Spring Security – How to mix xml and Java SecurityFilterChain declaration configurations?

Working on an application that was using XML Schema-based Spring configuration only, I’m slowly introducing Java based configuration.

Unfortunately I’m not able to use Spring Security html xml element and Java based SecurityFilterChain configuration at the same time.

When I inspect the filterChains list in the FilterChainProxy, it only contains the filter chains from the xml configuration. Despite the Java method creating the SecurityFilterChain bean being called, this filter chain is not added to the FilterChainProxy.

If I remove the html elements from the xml configuration, the SecurityFilterChain from the Java configuration is added to the FilterChainProxy and called as expected.

Spring versions I’m using:

  • Spring Framework: 5.3.16
  • Spring Security: 5.6.2

Here is the xml configuration and Java configuration I’m trying to use simultaneously:

<bean id="mySecurityConfig" class="com.xxx.SecurityConfiguration"/>

<http pattern="/style/**" security="none"/>
    package com.xxx;
    
    [...]
    
    @EnableWebSecurity
    @Configuration
    public class SecurityConfiguration {
      
        @Bean
        public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
            http.antMatcher("/app/**")
                .authorizeHttpRequests(authorize -> authorize
                      .antMatchers("/app/**").hasAuthority("ROLE_USER")
                      .anyRequest().denyAll()
                    )
                .addFilterBefore(anotherFilter(), Saml2WebSsoAuthenticationFilter.class);
            return http.build();
        }
    
       [...]
    }

web.xml

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

<filter-mapping>
    <filter-name>springSecurityFilterChain</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

Did I make a mistake, is there a way to achieve what I want, or is it simply not possible ?

Advertisement

Answer

Edit: @rwinch solution posted here

You can combine the XML and Java Configuration and exposes it as a different bean name using something like this:

    @Configuration
    public class AggregateSpringSecurityConfiguration {
        public static final String AGGREGATE_SPRING_SECURITY_FILTER_CHAIN_ID = "aggregateSpringSecurityFilterChain";
    
        /**
         * Provide a new FilterChainProxy that contains both XML and Java Configuration
         * @param webSecurityConfiguration
         * @return
         * @throws Exception
         */
        @Bean(AGGREGATE_SPRING_SECURITY_FILTER_CHAIN_ID)
        Filter aggregateSpringSecurityFilterChain(WebSecurityConfiguration webSecurityConfiguration) throws Exception {
            FilterChainProxy javaConfigFcp = (FilterChainProxy) webSecurityConfiguration.springSecurityFilterChain();
            return new FilterChainProxy(javaConfigFcp.getFilterChains());
        }
    
    }

Then ensure your XML configuration picks it up:

    <bean id="aggregateConfiguration" class="testapp.AggregateSpringSecurityConfiguration" />
    Finally, you need to update your web.xml to use the bean named aggregateSpringSecurityFilterChain instead of springSecurityFilterChain.
    
        <filter>
            <!-- matches AggregateSpringSecurityConfiguration.AGGREGATE_SPRING_SECURITY_FILTER_CHAIN_ID -->
            <filter-name>aggregateSpringSecurityFilterChain</filter-name>
            <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
        </filter>
    
        <filter-mapping>
            <filter-name>aggregateSpringSecurityFilterChain</filter-name>
            <url-pattern>/*</url-pattern>
        </filter-mapping>

Initial workaround

Until better solution, it is possible to use the workaround below (if really needed). It adds the missing SecurityFilterChain to the FilterChainProxy once Spring Application context is initialized. I implemented it against Spring Security 5.6.2. Since it uses reflection, it might break after any upgrade of Spring Security version.

    /**
     * This class is a workaround to allow declaring SecurityFilterChain using xml
     * configuration and Java configuration. It listens to the Spring Application
     * context events ContextRefreshedEvent and add SecurityFilterChain beans,
     * declared in the Java configuration, to the FilterChainProxy.
     */
    @Configuration
    public static class MissingSecurityFilterChainsInitializer {
        
        private FilterChainProxy filterChainProxy;
    
        private List<SecurityFilterChain> filterChains;
    
        /**
         * @param filterChainProxy
         * @param myFilter
         */
        public MissingSecurityFilterChainsInitializer(FilterChainProxy filterChainProxy,
                List<SecurityFilterChain> myFilterChains) {
            super();
            this.filterChainProxy = filterChainProxy;
            this.filterChains = myFilterChains;
        }
    
        /**
         * Method listening to the Spring Application context initialization, detects
         * missing SecurityFilterChain from the FilterChainProxy and add the missing
         * chains to the FilterChainProxy.
         * 
         * @param ctxRefreshed
         */
        @EventListener
        public void handleContextRefreshEvent(ContextRefreshedEvent ctxRefreshed) {
            List<SecurityFilterChain> missingChains = filterChains.stream()
                    .filter(chain -> !filterChainProxy.getFilterChains().contains(chain)).collect(Collectors.toList());
    
            if (missingChains.isEmpty()) {
                return;
            }
    
            try {
                Field filterChainsField = filterChainProxy.getClass().getDeclaredField("filterChains");
                boolean accessibleStatus = filterChainsField.isAccessible();
                try {
                    filterChainsField.setAccessible(true);
                    List<SecurityFilterChain> chains = (List<SecurityFilterChain>) filterChainsField
                            .get(filterChainProxy);
                    if (chains == null) {
                        throw new IllegalStateException(
                                "Unable to add the missing security filter chains to the existing filter chains list of the FilterChainProxy. The list from the FilterChainProxy is null");
                    }
                    missingChains.forEach(chain -> chains.add(chain));
                } finally {
                    filterChainsField.setAccessible(accessibleStatus);
                }
            } catch (NoSuchFieldException | IllegalArgumentException | IllegalAccessException e) {
                throw new IllegalStateException(
                        "Unable to add the missing security filter chains to the existing filter chains list of the FilterChainProxy: "
                                + e.getMessage(),
                        e);
    
            }
        }
    }
User contributions licensed under: CC BY-SA
1 People found this is helpful
Advertisement