blob: 67fdd1473294711747ed4128fac7a244cfd753cc [file] [log] [blame]
/*
* 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 java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import javax.servlet.Filter;
import javax.servlet.ServletContext;
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.config.ConfigurationException;
import org.apache.shiro.env.Environment;
import org.apache.shiro.guice.ShiroModule;
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.AnonymousFilter;
import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter;
import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;
import org.apache.shiro.web.filter.authc.LogoutFilter;
import org.apache.shiro.web.filter.authc.UserFilter;
import org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter;
import org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter;
import org.apache.shiro.web.filter.authz.PortFilter;
import org.apache.shiro.web.filter.authz.RolesAuthorizationFilter;
import org.apache.shiro.web.filter.authz.SslFilter;
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;
/**
* 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.
*/
public abstract class ShiroWebModule 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 ShiroWebModule(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() {
Map<Key<? extends PathMatchingFilter>, Map<String, String>> configs = new HashMap<Key<? extends PathMatchingFilter>, Map<String, String>>();
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());
}
if (configs.get(castToPathMatching(key)) == null) configs.put(castToPathMatching(key), new HashMap<String, String>());
configs.get(castToPathMatching(key)).put(filterChain.getKey(), configKey.getConfigValue());
} else if (PathMatchingFilter.class.isAssignableFrom(key.getTypeLiteral().getRawType())) {
if (configs.get(castToPathMatching(key)) == null) configs.put(castToPathMatching(key), new HashMap<String, String>());
configs.get(castToPathMatching(key)).put(filterChain.getKey(), "");
}
}
}
for (Key<? extends PathMatchingFilter> filterKey : configs.keySet()) {
bindPathMatchingFilter(filterKey, configs.get(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) {
bindWebSecurityManager(bind);
}
/**
* 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) {
bindWebEnvironment(bind);
}
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;
}
}
}