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 com.google.inject.Binder;
22  import com.google.inject.Key;
23  import com.google.inject.TypeLiteral;
24  import com.google.inject.binder.AnnotatedBindingBuilder;
25  import com.google.inject.name.Names;
26  import com.google.inject.servlet.ServletModule;
27  import org.apache.shiro.config.ConfigurationException;
28  import org.apache.shiro.env.Environment;
29  import org.apache.shiro.guice.ShiroModule;
30  import org.apache.shiro.lang.util.StringUtils;
31  import org.apache.shiro.mgt.SecurityManager;
32  import org.apache.shiro.session.mgt.SessionManager;
33  import org.apache.shiro.web.env.WebEnvironment;
34  import org.apache.shiro.web.filter.InvalidRequestFilter;
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.BearerHttpAuthenticationFilter;
39  import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;
40  import org.apache.shiro.web.filter.authc.LogoutFilter;
41  import org.apache.shiro.web.filter.authc.UserFilter;
42  import org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter;
43  import org.apache.shiro.web.filter.authz.IpFilter;
44  import org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter;
45  import org.apache.shiro.web.filter.authz.PortFilter;
46  import org.apache.shiro.web.filter.authz.RolesAuthorizationFilter;
47  import org.apache.shiro.web.filter.authz.SslFilter;
48  import org.apache.shiro.web.filter.mgt.FilterChainResolver;
49  import org.apache.shiro.web.filter.session.NoSessionCreationFilter;
50  import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
51  import org.apache.shiro.web.mgt.WebSecurityManager;
52  import org.apache.shiro.web.session.mgt.ServletContainerSessionManager;
53  
54  import javax.servlet.Filter;
55  import javax.servlet.ServletContext;
56  import java.util.ArrayList;
57  import java.util.Arrays;
58  import java.util.Collection;
59  import java.util.Collections;
60  import java.util.HashMap;
61  import java.util.LinkedHashMap;
62  import java.util.List;
63  import java.util.Map;
64  
65  @SuppressWarnings("checkstyle:JavadocVariable")
66  /**
67   * Sets up Shiro lifecycles within Guice, enables the injecting of Shiro objects, and binds a default
68   * {@link org.apache.shiro.web.mgt.WebSecurityManager},
69   * {@link org.apache.shiro.mgt.SecurityManager} and {@link org.apache.shiro.session.mgt.SessionManager}.
70   * At least one realm must be added by
71   * using {@link #bindRealm() bindRealm}.
72   * <p/>
73   * Also provides for the configuring of filter chains and binds a
74   * {@link org.apache.shiro.web.filter.mgt.FilterChainResolver} with that information.
75   */
76  public abstract class ShiroWebModule extends ShiroModule {
77      public static final Key<AnonymousFilter> ANON = Key.get(AnonymousFilter.class);
78      public static final Key<FormAuthenticationFilter> AUTHC = Key.get(FormAuthenticationFilter.class);
79      public static final Key<BasicHttpAuthenticationFilter> AUTHC_BASIC = Key.get(BasicHttpAuthenticationFilter.class);
80      public static final Key<BearerHttpAuthenticationFilter> AUTHC_BEARER = Key.get(BearerHttpAuthenticationFilter.class);
81      public static final Key<NoSessionCreationFilter> NO_SESSION_CREATION = Key.get(NoSessionCreationFilter.class);
82      public static final Key<LogoutFilter> LOGOUT = Key.get(LogoutFilter.class);
83      public static final Key<PermissionsAuthorizationFilter> PERMS = Key.get(PermissionsAuthorizationFilter.class);
84      public static final Key<PortFilter> PORT = Key.get(PortFilter.class);
85      public static final Key<HttpMethodPermissionFilter> REST = Key.get(HttpMethodPermissionFilter.class);
86      public static final Key<RolesAuthorizationFilter> ROLES = Key.get(RolesAuthorizationFilter.class);
87      public static final Key<SslFilter> SSL = Key.get(SslFilter.class);
88      public static final Key<IpFilter> IP = Key.get(IpFilter.class);
89      public static final Key<UserFilter> USER = Key.get(UserFilter.class);
90      public static final Key<InvalidRequestFilter> INVALID_REQUEST = Key.get(InvalidRequestFilter.class);
91  
92      static final String NAME = "SHIRO";
93  
94      /**
95       * We use a LinkedHashMap here to ensure that iterator order is the same as add order.  This is important, as the
96       * FilterChainResolver uses iterator order when searching for a matching chain.
97       */
98      private final Map<String, FilterConfig<? extends Filter>[]> filterChains =
99              new LinkedHashMap<String, FilterConfig<? extends Filter>[]>();
100     private final ServletContext servletContext;
101 
102     public ShiroWebModule(ServletContext servletContext) {
103         this.servletContext = servletContext;
104     }
105 
106     public static void bindGuiceFilter(Binder binder) {
107         binder.install(guiceFilterModule());
108     }
109 
110     public static void bindGuiceFilter(final String pattern, Binder binder) {
111         binder.install(guiceFilterModule(pattern));
112     }
113 
114     public static ServletModule guiceFilterModule() {
115         return guiceFilterModule("/*");
116     }
117 
118     public static ServletModule guiceFilterModule(final String pattern) {
119         return new ServletModule() {
120             @Override
121             protected void configureServlets() {
122                 filter(pattern).through(GuiceShiroFilter.class);
123             }
124         };
125     }
126 
127     public List<FilterConfig<? extends Filter>> globalFilters() {
128         return Collections.singletonList(filterConfig(INVALID_REQUEST));
129     }
130 
131     @Override
132     @SuppressWarnings("unchecked")
133     protected final void configureShiro() {
134         bindBeanType(TypeLiteral.get(ServletContext.class), Key.get(ServletContext.class, Names.named(NAME)));
135         bind(Key.get(ServletContext.class, Names.named(NAME))).toInstance(this.servletContext);
136         bindWebSecurityManager(bind(WebSecurityManager.class));
137         bindWebEnvironment(bind(WebEnvironment.class));
138         bind(GuiceShiroFilter.class).asEagerSingleton();
139         expose(GuiceShiroFilter.class);
140 
141         this.configureShiroWeb();
142 
143         // add default matching route if not already set
144         if (!filterChains.containsKey("/**")) {
145             // no config, this will add only the global filters
146             this.addFilterChain("/**", new FilterConfig[0]);
147         }
148 
149         bind(FilterChainResolver.class).toProvider(new FilterChainResolverProvider(setupFilterChainConfigs()));
150     }
151 
152     @SuppressWarnings("unchecked")
153     private Map<String, Key<? extends Filter>[]> setupFilterChainConfigs() {
154 
155         // loop through and build a map of Filter Key -> Map<Path, Config>
156         Map<Key<? extends Filter>, Map<String, String>> filterToPathToConfig =
157                 new HashMap<Key<? extends Filter>, Map<String, String>>();
158 
159         // At the same time build a map to return with Path -> Key[]
160         Map<String, Key<? extends Filter>[]> resultConfigMap = new LinkedHashMap<String, Key<? extends Filter>[]>();
161 
162         for (Map.Entry<String, FilterConfig<? extends Filter>[]> filterChain : filterChains.entrySet()) {
163 
164             String path = filterChain.getKey();
165 
166             // collect the keys used for this path
167             List<Key<? extends Filter>> keysForPath = new ArrayList<Key<? extends Filter>>();
168 
169             List<FilterConfig<? extends Filter>> globalFilters = this.globalFilters();
170             FilterConfig<? extends Filter>[] pathFilters = filterChain.getValue();
171 
172             // merge the global filters and the path specific filters
173             List<FilterConfig<? extends Filter>> filterConfigs = new ArrayList<>(globalFilters.size() + pathFilters.length);
174             filterConfigs.addAll(globalFilters);
175             filterConfigs.addAll(Arrays.asList(pathFilters));
176 
177             for (FilterConfig<? extends Filter> filterConfig : filterConfigs) {
178 
179                 Key<? extends Filter> key = filterConfig.getKey();
180                 String config = filterConfig.getConfigValue();
181 
182                 // initialize key in filterToPathToConfig, if it doesn't exist
183                 if (filterToPathToConfig.get(key) == null) {
184                     // Fix for SHIRO-621: REST filter bypassing matched path
185                     filterToPathToConfig.put((key), new LinkedHashMap<String, String>());
186                 }
187                 // now set the value
188                 filterToPathToConfig.get(key).put(path, config);
189 
190                 // Config error if someone configured a non PathMatchingFilter with a config value
191                 if (StringUtils.hasText(config)
192                         && !PathMatchingFilter.class.isAssignableFrom(key.getTypeLiteral().getRawType())) {
193                     throw new ConfigurationException(
194                             "Config information requires a PathMatchingFilter - can't apply to "
195                                     + key.getTypeLiteral().getRawType());
196                 }
197 
198                 // store the key in keysForPath
199                 keysForPath.add(key);
200             }
201 
202             // map the current path to all of its Keys
203             resultConfigMap.put(path, keysForPath.toArray(new Key[keysForPath.size()]));
204         }
205 
206         // now we find only the PathMatchingFilter and configure bindings
207         // non PathMatchingFilter, can be loaded with the default provider via the class name
208         for (Key<? extends Filter> key : filterToPathToConfig.keySet()) {
209             if (PathMatchingFilter.class.isAssignableFrom(key.getTypeLiteral().getRawType())) {
210                 bindPathMatchingFilter(castToPathMatching(key), filterToPathToConfig.get(key));
211             } else {
212                 bind(key);
213             }
214         }
215 
216         return resultConfigMap;
217     }
218 
219 
220     private <T extends PathMatchingFilter> void bindPathMatchingFilter(Key<T> filterKey, Map<String, String> configs) {
221         bind(filterKey).toProvider(new PathMatchingFilterProvider<T>(filterKey, configs)).asEagerSingleton();
222     }
223 
224     @SuppressWarnings({"unchecked"})
225     private Key<? extends PathMatchingFilter> castToPathMatching(Key<? extends Filter> key) {
226         return (Key<? extends PathMatchingFilter>) key;
227     }
228 
229     protected abstract void configureShiroWeb();
230 
231     @SuppressWarnings({"unchecked"})
232     @Override
233     protected final void bindSecurityManager(AnnotatedBindingBuilder<? super SecurityManager> bind) {
234         // SHIRO-435
235         bind.to(WebSecurityManager.class);
236     }
237 
238     /**
239      * Binds the security manager.  Override this method in order to provide your own security manager binding.
240      * <p/>
241      * By default, a {@link org.apache.shiro.web.mgt.DefaultWebSecurityManager} is bound as an eager singleton.
242      *
243      * @param bind
244      */
245     protected void bindWebSecurityManager(AnnotatedBindingBuilder<? super WebSecurityManager> bind) {
246         try {
247             bind.toConstructor(DefaultWebSecurityManager.class.getConstructor(Collection.class)).asEagerSingleton();
248         } catch (NoSuchMethodException e) {
249             throw new ConfigurationException("This really shouldn't happen.  Either something has changed in Shiro, "
250                     + "or there's a bug in ShiroModule.", e);
251         }
252     }
253 
254     /**
255      * Binds the session manager.  Override this method in order to provide your own session manager binding.
256      * <p/>
257      * By default, a {@link org.apache.shiro.web.session.mgt.DefaultWebSessionManager} is bound as an eager singleton.
258      *
259      * @param bind
260      */
261     @Override
262     protected void bindSessionManager(AnnotatedBindingBuilder<SessionManager> bind) {
263         bind.to(ServletContainerSessionManager.class).asEagerSingleton();
264     }
265 
266     @Override
267     protected final void bindEnvironment(AnnotatedBindingBuilder<Environment> bind) {
268         // SHIRO-435
269         bind.to(WebEnvironment.class);
270     }
271 
272     protected void bindWebEnvironment(AnnotatedBindingBuilder<? super WebEnvironment> bind) {
273         bind.to(WebGuiceEnvironment.class).asEagerSingleton();
274     }
275 
276     @SuppressWarnings("unchecked")
277     protected final void addFilterChain(String pattern, Key<? extends Filter> key) {
278         // check for legacy API
279         if (key instanceof FilterConfigKey) {
280             addLegacyFilterChain(pattern, (FilterConfigKey) key);
281         } else {
282             addFilterChain(pattern, new FilterConfig<Filter>((Key<Filter>) key, ""));
283         }
284     }
285 
286     /**
287      * Maps 'n' number of <code>filterConfig</code>s to a specific path pattern.<BR/>
288      * For example, a path of '/my_private_resource/**' to 'filterConfig(AUTHC)' would require
289      * any resource under the path '/my_private_resource' would be processed through the {@link FormAuthenticationFilter}.
290      *
291      * @param pattern       URL patter to be mapped to a FilterConfig, e.g. '/my_private-path/**'
292      * @param filterConfigs FilterConfiguration representing the Filter
293      *                      and config to be used when processing resources on <code>pattern</code>.
294      * @since 1.4
295      */
296     @SafeVarargs
297     protected final void addFilterChain(String pattern, FilterConfig<? extends Filter>... filterConfigs) {
298         filterChains.put(pattern, filterConfigs);
299     }
300 
301     /**
302      * Builds a FilterConfig from a Filer and configuration String
303      *
304      * @param baseKey The Key of the Filter class to be used.
305      * @param <T>     A Servlet Filter class.
306      * @return A FilterConfig used to map a String path to this configuration.
307      * @since 1.4
308      */
309     protected static <T extends Filter> FilterConfig<T> filterConfig(Key<T> baseKey, String configValue) {
310         return new FilterConfig<T>(baseKey, configValue);
311     }
312 
313     /**
314      * Builds a FilterConfig from a Filer and configuration String
315      *
316      * @param baseKey The Key of the Filter class to be used.
317      * @param <T>     A Servlet Filter class.
318      * @return A FilterConfig used to map a String path to this configuration.
319      * @since 1.4
320      */
321     protected static <T extends Filter> FilterConfig<T> filterConfig(Key<T> baseKey) {
322         return filterConfig(baseKey, "");
323     }
324 
325     /**
326      * Builds a FilterConfig from a Filer and configuration String
327      *
328      * @param typeLiteral The TypeLiteral of the filter key to be used.
329      * @param configValue the configuration used.
330      * @param <T>         A Servlet Filter class.
331      * @return A FilterConfig used to map a String path to this configuration.
332      * @since 1.4
333      */
334     protected static <T extends Filter> FilterConfig<T> filterConfig(TypeLiteral<T> typeLiteral, String configValue) {
335         return filterConfig(Key.get(typeLiteral), configValue);
336     }
337 
338     /**
339      * Builds a FilterConfig from a Filer and configuration String
340      *
341      * @param type        The filter to be used.
342      * @param configValue the configuration used.
343      * @param <T>         A Servlet Filter class.
344      * @return A FilterConfig used to map a String path to this configuration.
345      * @since 1.4
346      */
347     protected static <T extends Filter> FilterConfig<T> filterConfig(Class<T> type, String configValue) {
348         return filterConfig(Key.get(type), configValue);
349     }
350 
351 
352     /**
353      * Filter configuration which pairs a Filter class with its configuration used on a path.
354      *
355      * @param <T> The Servlet Filter class.
356      * @since 1.4
357      */
358     public static final class FilterConfig<T extends Filter> {
359         private Key<T> key;
360         private String configValue;
361 
362         private FilterConfig(Key<T> key, String configValue) {
363             super();
364             this.key = key;
365             this.configValue = configValue;
366         }
367 
368         public Key<T> getKey() {
369             return key;
370         }
371 
372         public String getConfigValue() {
373             return configValue;
374         }
375     }
376 
377 
378     // legacy methods
379 
380 
381     static boolean isGuiceVersion3() {
382         try {
383             Class.forName("com.google.inject.multibindings.MapKey");
384             return false;
385         } catch (ClassNotFoundException e) {
386             return true;
387         }
388     }
389 
390     @SuppressWarnings("unchecked")
391     private void addLegacyFilterChain(String pattern, FilterConfigKey filterConfigKey) {
392         FilterConfig<Filter> filterConfig = new FilterConfig<>(filterConfigKey.getKey(), filterConfigKey.getConfigValue());
393         addFilterChain(pattern, filterConfig);
394     }
395 
396     /**
397      * Adds a filter chain to the shiro configuration.
398      * <p/>
399      * NOTE: If the provided key is for a subclass of {@link org.apache.shiro.web.filter.PathMatchingFilter},
400      * it will be registered with a proper provider.
401      *
402      * @param pattern
403      * @param keys
404      */
405     @Deprecated
406     @SuppressWarnings("unchecked")
407     protected final void addFilterChain(String pattern, Key<? extends Filter>... keys) {
408 
409         // We need to extract the keys and FilterConfigKey and convert to the new format.
410 
411         FilterConfig[] filterConfigs = new FilterConfig[keys.length];
412         for (int ii = 0; ii < keys.length; ii++) {
413             Key<? extends Filter> key = keys[ii];
414             // If this is a path matching filter, we need to remember the config
415             if (key instanceof FilterConfigKey) {
416                 // legacy config
417                 FilterConfigKey legacyKey = (FilterConfigKey) key;
418                 filterConfigs[ii] = new FilterConfig(legacyKey.getKey(), legacyKey.getConfigValue());
419             } else {
420                 // Some other type of Filter key, no config
421                 filterConfigs[ii] = new FilterConfig(key, "");
422             }
423         }
424 
425         filterChains.put(pattern, filterConfigs);
426     }
427 
428     @Deprecated
429     protected static <T extends PathMatchingFilter> Key<T> config(Key<T> baseKey, String configValue) {
430 
431         if (!isGuiceVersion3()) {
432             throw new ConfigurationException(
433                     "Method ShiroWebModule.config(Key<? extends PathMatchingFilter>,"
434                     + " String configValue), is not supported when using Guice 4+");
435         }
436 
437         return new FilterConfigKey<T>(baseKey, configValue);
438     }
439 
440     @Deprecated
441     protected static <T extends PathMatchingFilter> Key<T> config(TypeLiteral<T> typeLiteral, String configValue) {
442         return config(Key.get(typeLiteral), configValue);
443     }
444 
445     @Deprecated
446     protected static <T extends PathMatchingFilter> Key<T> config(Class<T> type, String configValue) {
447         return config(Key.get(type), configValue);
448     }
449 
450     @Deprecated
451     private static final class FilterConfigKey<T extends PathMatchingFilter> extends Key<T> {
452         private Key<T> key;
453         private String configValue;
454 
455         private FilterConfigKey(Key<T> key, String configValue) {
456             super();
457             this.key = key;
458             this.configValue = configValue;
459         }
460 
461         public Key<T> getKey() {
462             return key;
463         }
464 
465         public String getConfigValue() {
466             return configValue;
467         }
468     }
469 
470 }