View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.shiro.guice.web;
20  
21  import java.util.Collection;
22  import java.util.HashMap;
23  import java.util.LinkedHashMap;
24  import java.util.Map;
25  
26  import javax.servlet.Filter;
27  import javax.servlet.ServletContext;
28  
29  import org.apache.shiro.config.ConfigurationException;
30  import org.apache.shiro.env.Environment;
31  import org.apache.shiro.guice.ShiroModule;
32  import org.apache.shiro.mgt.SecurityManager;
33  import org.apache.shiro.session.mgt.SessionManager;
34  import org.apache.shiro.web.env.WebEnvironment;
35  import org.apache.shiro.web.filter.PathMatchingFilter;
36  import org.apache.shiro.web.filter.authc.AnonymousFilter;
37  import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter;
38  import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;
39  import org.apache.shiro.web.filter.authc.LogoutFilter;
40  import org.apache.shiro.web.filter.authc.UserFilter;
41  import org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter;
42  import org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter;
43  import org.apache.shiro.web.filter.authz.PortFilter;
44  import org.apache.shiro.web.filter.authz.RolesAuthorizationFilter;
45  import org.apache.shiro.web.filter.authz.SslFilter;
46  import org.apache.shiro.web.filter.mgt.FilterChainResolver;
47  import org.apache.shiro.web.filter.session.NoSessionCreationFilter;
48  import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
49  import org.apache.shiro.web.mgt.WebSecurityManager;
50  import org.apache.shiro.web.session.mgt.ServletContainerSessionManager;
51  
52  import com.google.inject.Binder;
53  import com.google.inject.Key;
54  import com.google.inject.TypeLiteral;
55  import com.google.inject.binder.AnnotatedBindingBuilder;
56  import com.google.inject.name.Names;
57  import com.google.inject.servlet.ServletModule;
58  
59  /**
60   * Sets up Shiro lifecycles within Guice, enables the injecting of Shiro objects, and binds a default
61   * {@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
62   * using {@link #bindRealm() bindRealm}.
63   * <p/>
64   * Also provides for the configuring of filter chains and binds a {@link org.apache.shiro.web.filter.mgt.FilterChainResolver} with that information.
65   */
66  public abstract class ShiroWebModule extends ShiroModule {
67      @SuppressWarnings({"UnusedDeclaration"})
68      public static final Key<AnonymousFilter> ANON = Key.get(AnonymousFilter.class);
69      @SuppressWarnings({"UnusedDeclaration"})
70      public static final Key<FormAuthenticationFilter> AUTHC = Key.get(FormAuthenticationFilter.class);
71      @SuppressWarnings({"UnusedDeclaration"})
72      public static final Key<BasicHttpAuthenticationFilter> AUTHC_BASIC = Key.get(BasicHttpAuthenticationFilter.class);
73      @SuppressWarnings({"UnusedDeclaration"})
74      public static final Key<NoSessionCreationFilter> NO_SESSION_CREATION = Key.get(NoSessionCreationFilter.class);
75      @SuppressWarnings({"UnusedDeclaration"})
76      public static final Key<LogoutFilter> LOGOUT = Key.get(LogoutFilter.class);
77      @SuppressWarnings({"UnusedDeclaration"})
78      public static final Key<PermissionsAuthorizationFilter> PERMS = Key.get(PermissionsAuthorizationFilter.class);
79      @SuppressWarnings({"UnusedDeclaration"})
80      public static final Key<PortFilter> PORT = Key.get(PortFilter.class);
81      @SuppressWarnings({"UnusedDeclaration"})
82      public static final Key<HttpMethodPermissionFilter> REST = Key.get(HttpMethodPermissionFilter.class);
83      @SuppressWarnings({"UnusedDeclaration"})
84      public static final Key<RolesAuthorizationFilter> ROLES = Key.get(RolesAuthorizationFilter.class);
85      @SuppressWarnings({"UnusedDeclaration"})
86      public static final Key<SslFilter> SSL = Key.get(SslFilter.class);
87      @SuppressWarnings({"UnusedDeclaration"})
88      public static final Key<UserFilter> USER = Key.get(UserFilter.class);
89  
90  
91      static final String NAME = "SHIRO";
92  
93      /**
94       * We use a LinkedHashMap here to ensure that iterator order is the same as add order.  This is important, as the
95       * FilterChainResolver uses iterator order when searching for a matching chain.
96       */
97      private final Map<String, Key<? extends Filter>[]> filterChains = new LinkedHashMap<String, Key<? extends Filter>[]>();
98      private final ServletContext servletContext;
99  
100     public ShiroWebModule(ServletContext servletContext) {
101         this.servletContext = servletContext;
102     }
103 
104     public static void bindGuiceFilter(Binder binder) {
105         binder.install(guiceFilterModule());
106     }
107 
108     @SuppressWarnings({"UnusedDeclaration"})
109     public static void bindGuiceFilter(final String pattern, Binder binder) {
110         binder.install(guiceFilterModule(pattern));
111     }
112 
113     public static ServletModule guiceFilterModule() {
114         return guiceFilterModule("/*");
115     }
116 
117     public static ServletModule guiceFilterModule(final String pattern) {
118         return new ServletModule() {
119             @Override
120             protected void configureServlets() {
121                 filter(pattern).through(GuiceShiroFilter.class);
122             }
123         };
124     }
125 
126     @Override
127     protected final void configureShiro() {
128         bindBeanType(TypeLiteral.get(ServletContext.class), Key.get(ServletContext.class, Names.named(NAME)));
129         bind(Key.get(ServletContext.class, Names.named(NAME))).toInstance(this.servletContext);
130         bindWebSecurityManager(bind(WebSecurityManager.class));
131         bindWebEnvironment(bind(WebEnvironment.class));
132         bind(GuiceShiroFilter.class).asEagerSingleton();
133         expose(GuiceShiroFilter.class);
134 
135         this.configureShiroWeb();
136 
137         setupFilterChainConfigs();
138 
139         bind(FilterChainResolver.class).toProvider(new FilterChainResolverProvider(filterChains));
140     }
141 
142     private void setupFilterChainConfigs() {
143         Map<Key<? extends PathMatchingFilter>, Map<String, String>> configs = new HashMap<Key<? extends PathMatchingFilter>, Map<String, String>>();
144 
145         for (Map.Entry<String, Key<? extends Filter>[]> filterChain : filterChains.entrySet()) {
146             for (int i = 0; i < filterChain.getValue().length; i++) {
147                 Key<? extends Filter> key = filterChain.getValue()[i];
148                 if (key instanceof FilterConfigKey) {
149                     FilterConfigKey<? extends PathMatchingFilter> configKey = (FilterConfigKey<? extends PathMatchingFilter>) key;
150                     key = configKey.getKey();
151                     filterChain.getValue()[i] = key;
152                     if (!PathMatchingFilter.class.isAssignableFrom(key.getTypeLiteral().getRawType())) {
153                         throw new ConfigurationException("Config information requires a PathMatchingFilter - can't apply to " + key.getTypeLiteral().getRawType());
154                     }
155                     if (configs.get(castToPathMatching(key)) == null) configs.put(castToPathMatching(key), new HashMap<String, String>());
156                     configs.get(castToPathMatching(key)).put(filterChain.getKey(), configKey.getConfigValue());
157                 } else if (PathMatchingFilter.class.isAssignableFrom(key.getTypeLiteral().getRawType())) {
158 	                  if (configs.get(castToPathMatching(key)) == null) configs.put(castToPathMatching(key), new HashMap<String, String>());
159                     configs.get(castToPathMatching(key)).put(filterChain.getKey(), "");
160                 }
161             }
162         }
163         for (Key<? extends PathMatchingFilter> filterKey : configs.keySet()) {
164             bindPathMatchingFilter(filterKey, configs.get(filterKey));
165         }
166     }
167 
168     private <T extends PathMatchingFilter> void bindPathMatchingFilter(Key<T> filterKey, Map<String, String> configs) {
169         bind(filterKey).toProvider(new PathMatchingFilterProvider<T>(filterKey, configs)).asEagerSingleton();
170     }
171 
172     @SuppressWarnings({"unchecked"})
173     private Key<? extends PathMatchingFilter> castToPathMatching(Key<? extends Filter> key) {
174         return (Key<? extends PathMatchingFilter>) key;
175     }
176 
177     protected abstract void configureShiroWeb();
178 
179     @SuppressWarnings({"unchecked"})
180     @Override
181     protected final void bindSecurityManager(AnnotatedBindingBuilder<? super SecurityManager> bind) {
182         bind.to(WebSecurityManager.class); // SHIRO-435
183     }
184 
185     /**
186      * Binds the security manager.  Override this method in order to provide your own security manager binding.
187      * <p/>
188      * By default, a {@link org.apache.shiro.web.mgt.DefaultWebSecurityManager} is bound as an eager singleton.
189      *
190      * @param bind
191      */
192     protected void bindWebSecurityManager(AnnotatedBindingBuilder<? super WebSecurityManager> bind) {
193         try {
194             bind.toConstructor(DefaultWebSecurityManager.class.getConstructor(Collection.class)).asEagerSingleton();
195         } catch (NoSuchMethodException e) {
196             throw new ConfigurationException("This really shouldn't happen.  Either something has changed in Shiro, or there's a bug in ShiroModule.", e);
197         }
198     }
199 
200     /**
201      * Binds the session manager.  Override this method in order to provide your own session manager binding.
202      * <p/>
203      * By default, a {@link org.apache.shiro.web.session.mgt.DefaultWebSessionManager} is bound as an eager singleton.
204      *
205      * @param bind
206      */
207     @Override
208     protected void bindSessionManager(AnnotatedBindingBuilder<SessionManager> bind) {
209         bind.to(ServletContainerSessionManager.class).asEagerSingleton();
210     }
211 
212     @Override
213     protected final void bindEnvironment(AnnotatedBindingBuilder<Environment> bind) {
214         bind.to(WebEnvironment.class); // SHIRO-435
215     }
216 
217     protected void bindWebEnvironment(AnnotatedBindingBuilder<? super WebEnvironment> bind) {
218         bind.to(WebGuiceEnvironment.class).asEagerSingleton();
219     }
220 
221     /**
222      * Adds a filter chain to the shiro configuration.
223      * <p/>
224      * NOTE: If the provided key is for a subclass of {@link org.apache.shiro.web.filter.PathMatchingFilter}, it will be registered with a proper
225      * provider.
226      *
227      * @param pattern
228      * @param keys
229      */
230     @SuppressWarnings({"UnusedDeclaration"})
231     protected final void addFilterChain(String pattern, Key<? extends Filter>... keys) {
232         filterChains.put(pattern, keys);
233     }
234 
235     protected static <T extends PathMatchingFilter> Key<T> config(Key<T> baseKey, String configValue) {
236         return new FilterConfigKey<T>(baseKey, configValue);
237     }
238 
239     @SuppressWarnings({"UnusedDeclaration"})
240     protected static <T extends PathMatchingFilter> Key<T> config(TypeLiteral<T> typeLiteral, String configValue) {
241         return config(Key.get(typeLiteral), configValue);
242     }
243 
244     @SuppressWarnings({"UnusedDeclaration"})
245     protected static <T extends PathMatchingFilter> Key<T> config(Class<T> type, String configValue) {
246         return config(Key.get(type), configValue);
247     }
248 
249     private static class FilterConfigKey<T extends PathMatchingFilter> extends Key<T> {
250         private Key<T> key;
251         private String configValue;
252 
253         private FilterConfigKey(Key<T> key, String configValue) {
254             super();
255             this.key = key;
256             this.configValue = configValue;
257         }
258 
259         public Key<T> getKey() {
260             return key;
261         }
262 
263         public String getConfigValue() {
264             return configValue;
265         }
266     }
267 }