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