diff --git a/profiles/killbill/src/main/java/org/apache/shiro/guice/web/ShiroWebModuleWith435.java b/profiles/killbill/src/main/java/org/apache/shiro/guice/web/ShiroWebModuleWith435.java
new file mode 100644
index 0000000..f877740
--- /dev/null
+++ b/profiles/killbill/src/main/java/org/apache/shiro/guice/web/ShiroWebModuleWith435.java
@@ -0,0 +1,257 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.guice.web;
+
+import com.google.common.collect.HashBasedTable;
+import com.google.common.collect.Table;
+import com.google.inject.Binder;
+import com.google.inject.Key;
+import com.google.inject.TypeLiteral;
+import com.google.inject.binder.AnnotatedBindingBuilder;
+import com.google.inject.name.Names;
+import com.google.inject.servlet.ServletModule;
+import org.apache.shiro.guice.ShiroModule;
+import org.apache.shiro.config.ConfigurationException;
+import org.apache.shiro.env.Environment;
+import org.apache.shiro.mgt.SecurityManager;
+import org.apache.shiro.session.mgt.SessionManager;
+import org.apache.shiro.web.env.WebEnvironment;
+import org.apache.shiro.web.filter.PathMatchingFilter;
+import org.apache.shiro.web.filter.authc.*;
+import org.apache.shiro.web.filter.authz.*;
+import org.apache.shiro.web.filter.mgt.FilterChainResolver;
+import org.apache.shiro.web.filter.session.NoSessionCreationFilter;
+import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
+import org.apache.shiro.web.mgt.WebSecurityManager;
+import org.apache.shiro.web.session.mgt.ServletContainerSessionManager;
+
+import javax.servlet.Filter;
+import javax.servlet.ServletContext;
+import java.util.Collection;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * Sets up Shiro lifecycles within Guice, enables the injecting of Shiro objects, and binds a default
+ * {@link org.apache.shiro.web.mgt.WebSecurityManager}, {@link org.apache.shiro.mgt.SecurityManager} and {@link org.apache.shiro.session.mgt.SessionManager}. At least one realm must be added by
+ * using {@link #bindRealm() bindRealm}.
+ * <p/>
+ * Also provides for the configuring of filter chains and binds a {@link org.apache.shiro.web.filter.mgt.FilterChainResolver} with that information.
+ * @see <a href="https://issues.apache.org/jira/browse/SHIRO-435">https://issues.apache.org/jira/browse/SHIRO-435</a>
+ */
+public abstract class ShiroWebModuleWith435 extends ShiroModule {
+ @SuppressWarnings({"UnusedDeclaration"})
+ public static final Key<AnonymousFilter> ANON = Key.get(AnonymousFilter.class);
+ @SuppressWarnings({"UnusedDeclaration"})
+ public static final Key<FormAuthenticationFilter> AUTHC = Key.get(FormAuthenticationFilter.class);
+ @SuppressWarnings({"UnusedDeclaration"})
+ public static final Key<BasicHttpAuthenticationFilter> AUTHC_BASIC = Key.get(BasicHttpAuthenticationFilter.class);
+ @SuppressWarnings({"UnusedDeclaration"})
+ public static final Key<NoSessionCreationFilter> NO_SESSION_CREATION = Key.get(NoSessionCreationFilter.class);
+ @SuppressWarnings({"UnusedDeclaration"})
+ public static final Key<LogoutFilter> LOGOUT = Key.get(LogoutFilter.class);
+ @SuppressWarnings({"UnusedDeclaration"})
+ public static final Key<PermissionsAuthorizationFilter> PERMS = Key.get(PermissionsAuthorizationFilter.class);
+ @SuppressWarnings({"UnusedDeclaration"})
+ public static final Key<PortFilter> PORT = Key.get(PortFilter.class);
+ @SuppressWarnings({"UnusedDeclaration"})
+ public static final Key<HttpMethodPermissionFilter> REST = Key.get(HttpMethodPermissionFilter.class);
+ @SuppressWarnings({"UnusedDeclaration"})
+ public static final Key<RolesAuthorizationFilter> ROLES = Key.get(RolesAuthorizationFilter.class);
+ @SuppressWarnings({"UnusedDeclaration"})
+ public static final Key<SslFilter> SSL = Key.get(SslFilter.class);
+ @SuppressWarnings({"UnusedDeclaration"})
+ public static final Key<UserFilter> USER = Key.get(UserFilter.class);
+
+
+ static final String NAME = "SHIRO";
+
+ /**
+ * 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.
+ */
+ private final Map<String, Key<? extends Filter>[]> filterChains = new LinkedHashMap<String, Key<? extends Filter>[]>();
+ private final ServletContext servletContext;
+
+ public ShiroWebModuleWith435(ServletContext servletContext) {
+ this.servletContext = servletContext;
+ }
+
+ public static void bindGuiceFilter(Binder binder) {
+ binder.install(guiceFilterModule());
+ }
+
+ @SuppressWarnings({"UnusedDeclaration"})
+ public static void bindGuiceFilter(final String pattern, Binder binder) {
+ binder.install(guiceFilterModule(pattern));
+ }
+
+ public static ServletModule guiceFilterModule() {
+ return guiceFilterModule("/*");
+ }
+
+ public static ServletModule guiceFilterModule(final String pattern) {
+ return new ServletModule() {
+ @Override
+ protected void configureServlets() {
+ filter(pattern).through(GuiceShiroFilter.class);
+ }
+ };
+ }
+
+ @Override
+ protected final void configureShiro() {
+ bindBeanType(TypeLiteral.get(ServletContext.class), Key.get(ServletContext.class, Names.named(NAME)));
+ bind(Key.get(ServletContext.class, Names.named(NAME))).toInstance(this.servletContext);
+ bindWebSecurityManager(bind(WebSecurityManager.class));
+ bindWebEnvironment(bind(WebEnvironment.class));
+ bind(GuiceShiroFilter.class).asEagerSingleton();
+ expose(GuiceShiroFilter.class);
+
+ this.configureShiroWeb();
+
+ setupFilterChainConfigs();
+
+ bind(FilterChainResolver.class).toProvider(new FilterChainResolverProvider(filterChains));
+ }
+
+ private void setupFilterChainConfigs() {
+ Table<Key<? extends PathMatchingFilter>, String, String> configs = HashBasedTable.create();
+
+ for (Map.Entry<String, Key<? extends Filter>[]> filterChain : filterChains.entrySet()) {
+ for (int i = 0; i < filterChain.getValue().length; i++) {
+ Key<? extends Filter> key = filterChain.getValue()[i];
+ if (key instanceof FilterConfigKey) {
+ FilterConfigKey<? extends PathMatchingFilter> configKey = (FilterConfigKey<? extends PathMatchingFilter>) key;
+ key = configKey.getKey();
+ filterChain.getValue()[i] = key;
+ if (!PathMatchingFilter.class.isAssignableFrom(key.getTypeLiteral().getRawType())) {
+ throw new ConfigurationException("Config information requires a PathMatchingFilter - can't apply to " + key.getTypeLiteral().getRawType());
+ }
+ configs.put(castToPathMatching(key), filterChain.getKey(), configKey.getConfigValue());
+ } else if (PathMatchingFilter.class.isAssignableFrom(key.getTypeLiteral().getRawType())) {
+ configs.put(castToPathMatching(key), filterChain.getKey(), "");
+ }
+ }
+ }
+ for (Key<? extends PathMatchingFilter> filterKey : configs.rowKeySet()) {
+ bindPathMatchingFilter(filterKey, configs.row(filterKey));
+ }
+ }
+
+ private <T extends PathMatchingFilter> void bindPathMatchingFilter(Key<T> filterKey, Map<String, String> configs) {
+ bind(filterKey).toProvider(new PathMatchingFilterProvider<T>(filterKey, configs)).asEagerSingleton();
+ }
+
+ @SuppressWarnings({"unchecked"})
+ private Key<? extends PathMatchingFilter> castToPathMatching(Key<? extends Filter> key) {
+ return (Key<? extends PathMatchingFilter>) key;
+ }
+
+ protected abstract void configureShiroWeb();
+
+ @SuppressWarnings({"unchecked"})
+ @Override
+ protected final void bindSecurityManager(AnnotatedBindingBuilder<? super SecurityManager> bind) {
+ bind.to(WebSecurityManager.class); // SHIRO-435
+ }
+
+ /**
+ * Binds the security manager. Override this method in order to provide your own security manager binding.
+ * <p/>
+ * By default, a {@link org.apache.shiro.web.mgt.DefaultWebSecurityManager} is bound as an eager singleton.
+ *
+ * @param bind
+ */
+ protected void bindWebSecurityManager(AnnotatedBindingBuilder<? super WebSecurityManager> bind) {
+ try {
+ bind.toConstructor(DefaultWebSecurityManager.class.getConstructor(Collection.class)).asEagerSingleton();
+ } catch (NoSuchMethodException e) {
+ throw new ConfigurationException("This really shouldn't happen. Either something has changed in Shiro, or there's a bug in ShiroModule.", e);
+ }
+ }
+
+ /**
+ * Binds the session manager. Override this method in order to provide your own session manager binding.
+ * <p/>
+ * By default, a {@link org.apache.shiro.web.session.mgt.DefaultWebSessionManager} is bound as an eager singleton.
+ *
+ * @param bind
+ */
+ @Override
+ protected void bindSessionManager(AnnotatedBindingBuilder<SessionManager> bind) {
+ bind.to(ServletContainerSessionManager.class).asEagerSingleton();
+ }
+
+ @Override
+ protected final void bindEnvironment(AnnotatedBindingBuilder<Environment> bind) {
+ bind.to(WebEnvironment.class); // SHIRO-435
+ }
+
+ protected void bindWebEnvironment(AnnotatedBindingBuilder<? super WebEnvironment> bind) {
+ bind.to(WebGuiceEnvironment.class).asEagerSingleton();
+ }
+
+ /**
+ * Adds a filter chain to the shiro configuration.
+ * <p/>
+ * NOTE: If the provided key is for a subclass of {@link org.apache.shiro.web.filter.PathMatchingFilter}, it will be registered with a proper
+ * provider.
+ *
+ * @param pattern
+ * @param keys
+ */
+ @SuppressWarnings({"UnusedDeclaration"})
+ protected final void addFilterChain(String pattern, Key<? extends Filter>... keys) {
+ filterChains.put(pattern, keys);
+ }
+
+ protected static <T extends PathMatchingFilter> Key<T> config(Key<T> baseKey, String configValue) {
+ return new FilterConfigKey<T>(baseKey, configValue);
+ }
+
+ @SuppressWarnings({"UnusedDeclaration"})
+ protected static <T extends PathMatchingFilter> Key<T> config(TypeLiteral<T> typeLiteral, String configValue) {
+ return config(Key.get(typeLiteral), configValue);
+ }
+
+ @SuppressWarnings({"UnusedDeclaration"})
+ protected static <T extends PathMatchingFilter> Key<T> config(Class<T> type, String configValue) {
+ return config(Key.get(type), configValue);
+ }
+
+ private static class FilterConfigKey<T extends PathMatchingFilter> extends Key<T> {
+ private Key<T> key;
+ private String configValue;
+
+ private FilterConfigKey(Key<T> key, String configValue) {
+ super();
+ this.key = key;
+ this.configValue = configValue;
+ }
+
+ public Key<T> getKey() {
+ return key;
+ }
+
+ public String getConfigValue() {
+ return configValue;
+ }
+ }
+}
diff --git a/profiles/killbill/src/main/java/org/killbill/billing/server/modules/KillBillShiroWebModule.java b/profiles/killbill/src/main/java/org/killbill/billing/server/modules/KillBillShiroWebModule.java
index 0617b12..96cd7ef 100644
--- a/profiles/killbill/src/main/java/org/killbill/billing/server/modules/KillBillShiroWebModule.java
+++ b/profiles/killbill/src/main/java/org/killbill/billing/server/modules/KillBillShiroWebModule.java
@@ -24,7 +24,7 @@ import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import org.apache.shiro.cache.CacheManager;
-import org.apache.shiro.guice.web.ShiroWebModule;
+import org.apache.shiro.guice.web.ShiroWebModuleWith435;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
@@ -46,7 +46,7 @@ import com.google.inject.binder.AnnotatedBindingBuilder;
// For Kill Bill server only.
// See org.killbill.billing.util.glue.KillBillShiroModule for Kill Bill library.
-public class KillBillShiroWebModule extends ShiroWebModule {
+public class KillBillShiroWebModule extends ShiroWebModuleWith435 {
private final ConfigSource configSource;