@SuppressWarnings("unchecked")
    private Map<String, Key<? extends Filter>[]> setupFilterChainConfigs() {

        // loop through and build a map of Filter Key -> Map<Path, Config>
        Map<Key<? extends Filter>, Map<String, String>> filterToPathToConfig =
                new HashMap<Key<? extends Filter>, Map<String, String>>();

        // At the same time build a map to return with Path -> Key[]
        Map<String, Key<? extends Filter>[]> resultConfigMap = new LinkedHashMap<String, Key<? extends Filter>[]>();

        for (Map.Entry<String, FilterConfig<? extends Filter>[]> filterChain : filterChains.entrySet()) {

            String path = filterChain.getKey();

            // collect the keys used for this path
            List<Key<? extends Filter>> keysForPath = new ArrayList<Key<? extends Filter>>();

            List<FilterConfig<? extends Filter>> globalFilters = this.globalFilters();
            FilterConfig<? extends Filter>[] pathFilters = filterChain.getValue();

            // merge the global filters and the path specific filters
            List<FilterConfig<? extends Filter>> filterConfigs = new ArrayList<>(globalFilters.size() + pathFilters.length);
            filterConfigs.addAll(globalFilters);
            filterConfigs.addAll(Arrays.asList(pathFilters));

            for (FilterConfig<? extends Filter> filterConfig : filterConfigs) {

                Key<? extends Filter> key = filterConfig.getKey();
                String config = filterConfig.getConfigValue();

                // initialize key in filterToPathToConfig, if it doesn't exist
                if (filterToPathToConfig.get(key) == null) {
                    // Fix for SHIRO-621: REST filter bypassing matched path
                    filterToPathToConfig.put((key), new LinkedHashMap<String, String>());
                }
                // now set the value
                filterToPathToConfig.get(key).put(path, config);

                // Config error if someone configured a non PathMatchingFilter with a config value
                if (StringUtils.hasText(config)
                        && !PathMatchingFilter.class.isAssignableFrom(key.getTypeLiteral().getRawType())) {
                    throw new ConfigurationException(
                            "Config information requires a PathMatchingFilter - can't apply to "
                                    + key.getTypeLiteral().getRawType());
                }

                // store the key in keysForPath
                keysForPath.add(key);
            }

            // map the current path to all of its Keys
            resultConfigMap.put(path, keysForPath.toArray(new Key[keysForPath.size()]));
        }

        // now we find only the PathMatchingFilter and configure bindings
        // non PathMatchingFilter, can be loaded with the default provider via the class name
        for (Key<? extends Filter> key : filterToPathToConfig.keySet()) {
            if (PathMatchingFilter.class.isAssignableFrom(key.getTypeLiteral().getRawType())) {
                bindPathMatchingFilter(castToPathMatching(key), filterToPathToConfig.get(key));
            } else {
                bind(key);
            }
        }

        return resultConfigMap;
    }
We use a LinkedHashMap here to ensure that iterator order is the same as add order.  This is important, as the
FilterChainResolver uses iterator order when searching for a matching chain.
support/guice/src/main/java/org/apache/shiro/guice/web/ShiroWebModule.java