001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 */ 019package org.apache.shiro.guice.web; 020 021import java.util.*; 022 023import javax.servlet.Filter; 024import javax.servlet.ServletContext; 025 026import org.apache.shiro.config.ConfigurationException; 027import org.apache.shiro.env.Environment; 028import org.apache.shiro.guice.ShiroModule; 029import org.apache.shiro.mgt.SecurityManager; 030import org.apache.shiro.session.mgt.SessionManager; 031import org.apache.shiro.util.StringUtils; 032import org.apache.shiro.web.env.WebEnvironment; 033import org.apache.shiro.web.filter.PathMatchingFilter; 034import org.apache.shiro.web.filter.authc.AnonymousFilter; 035import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter; 036import org.apache.shiro.web.filter.authc.FormAuthenticationFilter; 037import org.apache.shiro.web.filter.authc.LogoutFilter; 038import org.apache.shiro.web.filter.authc.UserFilter; 039import org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter; 040import org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter; 041import org.apache.shiro.web.filter.authz.PortFilter; 042import org.apache.shiro.web.filter.authz.RolesAuthorizationFilter; 043import org.apache.shiro.web.filter.authz.SslFilter; 044import org.apache.shiro.web.filter.mgt.FilterChainResolver; 045import org.apache.shiro.web.filter.session.NoSessionCreationFilter; 046import org.apache.shiro.web.mgt.DefaultWebSecurityManager; 047import org.apache.shiro.web.mgt.WebSecurityManager; 048import org.apache.shiro.web.session.mgt.ServletContainerSessionManager; 049 050import com.google.inject.Binder; 051import com.google.inject.Key; 052import com.google.inject.TypeLiteral; 053import com.google.inject.binder.AnnotatedBindingBuilder; 054import com.google.inject.name.Names; 055import com.google.inject.servlet.ServletModule; 056 057/** 058 * Sets up Shiro lifecycles within Guice, enables the injecting of Shiro objects, and binds a default 059 * {@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 060 * using {@link #bindRealm() bindRealm}. 061 * <p/> 062 * Also provides for the configuring of filter chains and binds a {@link org.apache.shiro.web.filter.mgt.FilterChainResolver} with that information. 063 */ 064public abstract class ShiroWebModule extends ShiroModule { 065 @SuppressWarnings({"UnusedDeclaration"}) 066 public static final Key<AnonymousFilter> ANON = Key.get(AnonymousFilter.class); 067 @SuppressWarnings({"UnusedDeclaration"}) 068 public static final Key<FormAuthenticationFilter> AUTHC = Key.get(FormAuthenticationFilter.class); 069 @SuppressWarnings({"UnusedDeclaration"}) 070 public static final Key<BasicHttpAuthenticationFilter> AUTHC_BASIC = Key.get(BasicHttpAuthenticationFilter.class); 071 @SuppressWarnings({"UnusedDeclaration"}) 072 public static final Key<NoSessionCreationFilter> NO_SESSION_CREATION = Key.get(NoSessionCreationFilter.class); 073 @SuppressWarnings({"UnusedDeclaration"}) 074 public static final Key<LogoutFilter> LOGOUT = Key.get(LogoutFilter.class); 075 @SuppressWarnings({"UnusedDeclaration"}) 076 public static final Key<PermissionsAuthorizationFilter> PERMS = Key.get(PermissionsAuthorizationFilter.class); 077 @SuppressWarnings({"UnusedDeclaration"}) 078 public static final Key<PortFilter> PORT = Key.get(PortFilter.class); 079 @SuppressWarnings({"UnusedDeclaration"}) 080 public static final Key<HttpMethodPermissionFilter> REST = Key.get(HttpMethodPermissionFilter.class); 081 @SuppressWarnings({"UnusedDeclaration"}) 082 public static final Key<RolesAuthorizationFilter> ROLES = Key.get(RolesAuthorizationFilter.class); 083 @SuppressWarnings({"UnusedDeclaration"}) 084 public static final Key<SslFilter> SSL = Key.get(SslFilter.class); 085 @SuppressWarnings({"UnusedDeclaration"}) 086 public static final Key<UserFilter> USER = Key.get(UserFilter.class); 087 088 089 static final String NAME = "SHIRO"; 090 091 /** 092 * We use a LinkedHashMap here to ensure that iterator order is the same as add order. This is important, as the 093 * FilterChainResolver uses iterator order when searching for a matching chain. 094 */ 095 private final Map<String, FilterConfig<? extends Filter>[]> filterChains = new LinkedHashMap<String, FilterConfig<? extends Filter>[]>(); 096 private final ServletContext servletContext; 097 098 public ShiroWebModule(ServletContext servletContext) { 099 this.servletContext = servletContext; 100 } 101 102 public static void bindGuiceFilter(Binder binder) { 103 binder.install(guiceFilterModule()); 104 } 105 106 @SuppressWarnings({"UnusedDeclaration"}) 107 public static void bindGuiceFilter(final String pattern, Binder binder) { 108 binder.install(guiceFilterModule(pattern)); 109 } 110 111 public static ServletModule guiceFilterModule() { 112 return guiceFilterModule("/*"); 113 } 114 115 public static ServletModule guiceFilterModule(final String pattern) { 116 return new ServletModule() { 117 @Override 118 protected void configureServlets() { 119 filter(pattern).through(GuiceShiroFilter.class); 120 } 121 }; 122 } 123 124 @Override 125 protected final void configureShiro() { 126 bindBeanType(TypeLiteral.get(ServletContext.class), Key.get(ServletContext.class, Names.named(NAME))); 127 bind(Key.get(ServletContext.class, Names.named(NAME))).toInstance(this.servletContext); 128 bindWebSecurityManager(bind(WebSecurityManager.class)); 129 bindWebEnvironment(bind(WebEnvironment.class)); 130 bind(GuiceShiroFilter.class).asEagerSingleton(); 131 expose(GuiceShiroFilter.class); 132 133 this.configureShiroWeb(); 134 135 bind(FilterChainResolver.class).toProvider(new FilterChainResolverProvider(setupFilterChainConfigs())); 136 } 137 138 private Map<String, Key<? extends Filter>[]> setupFilterChainConfigs() { 139 140 // loop through and build a map of Filter Key -> Map<Path, Config> 141 Map<Key<? extends Filter>, Map<String, String>> filterToPathToConfig = new HashMap<Key<? extends Filter>, Map<String, String>>(); 142 143 // At the same time build a map to return with Path -> Key[] 144 Map<String, Key<? extends Filter>[]> resultConfigMap = new LinkedHashMap<String, Key<? extends Filter>[]>(); 145 146 for (Map.Entry<String, FilterConfig<? extends Filter>[]> filterChain : filterChains.entrySet()) { 147 148 String path = filterChain.getKey(); 149 150 // collect the keys used for this path 151 List<Key<? extends Filter>> keysForPath = new ArrayList<Key<? extends Filter>>(); 152 153 for (int i = 0; i < filterChain.getValue().length; i++) { 154 FilterConfig<? extends Filter> filterConfig = filterChain.getValue()[i]; 155 156 Key<? extends Filter> key = filterConfig.getKey(); 157 String config = filterConfig.getConfigValue(); 158 159 // initialize key in filterToPathToConfig, if it doesn't exist 160 if (filterToPathToConfig.get(key) == null) { 161 // Fix for SHIRO-621: REST filter bypassing matched path 162 filterToPathToConfig.put((key), new LinkedHashMap<String, String>()); 163 } 164 // now set the value 165 filterToPathToConfig.get(key).put(path, config); 166 167 // Config error if someone configured a non PathMatchingFilter with a config value 168 if (StringUtils.hasText(config) && !PathMatchingFilter.class.isAssignableFrom(key.getTypeLiteral().getRawType())) { 169 throw new ConfigurationException("Config information requires a PathMatchingFilter - can't apply to " + key.getTypeLiteral().getRawType()); 170 } 171 172 // store the key in keysForPath 173 keysForPath.add(key); 174 } 175 176 // map the current path to all of its Keys 177 resultConfigMap.put(path, keysForPath.toArray(new Key[keysForPath.size()])); 178 } 179 180 // now we find only the PathMatchingFilter and configure bindings 181 // non PathMatchingFilter, can be loaded with the default provider via the class name 182 for (Key<? extends Filter> key : filterToPathToConfig.keySet()) { 183 if (PathMatchingFilter.class.isAssignableFrom(key.getTypeLiteral().getRawType())) { 184 bindPathMatchingFilter(castToPathMatching(key), filterToPathToConfig.get(key)); 185 } 186 else { 187 bind(key); 188 } 189 } 190 191 return resultConfigMap; 192 } 193 194 195 private <T extends PathMatchingFilter> void bindPathMatchingFilter(Key<T> filterKey, Map<String, String> configs) { 196 bind(filterKey).toProvider(new PathMatchingFilterProvider<T>(filterKey, configs)).asEagerSingleton(); 197 } 198 199 @SuppressWarnings({"unchecked"}) 200 private Key<? extends PathMatchingFilter> castToPathMatching(Key<? extends Filter> key) { 201 return (Key<? extends PathMatchingFilter>) key; 202 } 203 204 protected abstract void configureShiroWeb(); 205 206 @SuppressWarnings({"unchecked"}) 207 @Override 208 protected final void bindSecurityManager(AnnotatedBindingBuilder<? super SecurityManager> bind) { 209 bind.to(WebSecurityManager.class); // SHIRO-435 210 } 211 212 /** 213 * Binds the security manager. Override this method in order to provide your own security manager binding. 214 * <p/> 215 * By default, a {@link org.apache.shiro.web.mgt.DefaultWebSecurityManager} is bound as an eager singleton. 216 * 217 * @param bind 218 */ 219 protected void bindWebSecurityManager(AnnotatedBindingBuilder<? super WebSecurityManager> bind) { 220 try { 221 bind.toConstructor(DefaultWebSecurityManager.class.getConstructor(Collection.class)).asEagerSingleton(); 222 } catch (NoSuchMethodException e) { 223 throw new ConfigurationException("This really shouldn't happen. Either something has changed in Shiro, or there's a bug in ShiroModule.", e); 224 } 225 } 226 227 /** 228 * Binds the session manager. Override this method in order to provide your own session manager binding. 229 * <p/> 230 * By default, a {@link org.apache.shiro.web.session.mgt.DefaultWebSessionManager} is bound as an eager singleton. 231 * 232 * @param bind 233 */ 234 @Override 235 protected void bindSessionManager(AnnotatedBindingBuilder<SessionManager> bind) { 236 bind.to(ServletContainerSessionManager.class).asEagerSingleton(); 237 } 238 239 @Override 240 protected final void bindEnvironment(AnnotatedBindingBuilder<Environment> bind) { 241 bind.to(WebEnvironment.class); // SHIRO-435 242 } 243 244 protected void bindWebEnvironment(AnnotatedBindingBuilder<? super WebEnvironment> bind) { 245 bind.to(WebGuiceEnvironment.class).asEagerSingleton(); 246 } 247 248 protected final void addFilterChain(String pattern, Key<? extends Filter> key) { 249 // check for legacy API 250 if (key instanceof FilterConfigKey) { 251 addLegacyFilterChain(pattern, (FilterConfigKey) key); 252 } 253 else { 254 addFilterChain(pattern, new FilterConfig<Filter>((Key<Filter>) key, "")); 255 } 256 } 257 258 /** 259 * Maps 'n' number of <code>filterConfig</code>s to a specific path pattern.<BR/> 260 * For example, a path of '/my_private_resource/**' to 'filterConfig(AUTHC)' would require 261 * any resource under the path '/my_private_resource' would be processed through the {@link FormAuthenticationFilter}. 262 * 263 * @param pattern URL patter to be mapped to a FilterConfig, e.g. '/my_private-path/**' 264 * @param filterConfigs FilterConfiguration representing the Filter and config to be used when processing resources on <code>pattern</code>. 265 * @since 1.4 266 */ 267 protected final void addFilterChain(String pattern, FilterConfig<? extends Filter>... filterConfigs) { 268 filterChains.put(pattern, filterConfigs); 269 } 270 271 /** 272 * Builds a FilterConfig from a Filer and configuration String 273 * @param baseKey The Key of the Filter class to be used. 274 * @param <T> A Servlet Filter class. 275 * @return A FilterConfig used to map a String path to this configuration. 276 * @since 1.4 277 */ 278 protected static <T extends Filter> FilterConfig<T> filterConfig(Key<T> baseKey, String configValue) { 279 return new FilterConfig<T>(baseKey, configValue); 280 } 281 282 /** 283 * Builds a FilterConfig from a Filer and configuration String 284 * @param baseKey The Key of the Filter class to be used. 285 * @param <T> A Servlet Filter class. 286 * @return A FilterConfig used to map a String path to this configuration. 287 * @since 1.4 288 */ 289 protected static <T extends Filter> FilterConfig<T> filterConfig(Key<T> baseKey) { 290 return filterConfig(baseKey, ""); 291 } 292 293 /** 294 * Builds a FilterConfig from a Filer and configuration String 295 * @param typeLiteral The TyleLiteral of the filter key to be used. 296 * @param configValue the configuration used. 297 * @param <T> A Servlet Filter class. 298 * @return A FilterConfig used to map a String path to this configuration. 299 * @since 1.4 300 */ 301 @SuppressWarnings({"UnusedDeclaration"}) 302 protected static <T extends Filter> FilterConfig<T> filterConfig(TypeLiteral<T> typeLiteral, String configValue) { 303 return filterConfig(Key.get(typeLiteral), configValue); 304 } 305 306 /** 307 * Builds a FilterConfig from a Filer and configuration String 308 * @param type The filter to be used. 309 * @param configValue the configuration used. 310 * @param <T> A Servlet Filter class. 311 * @return A FilterConfig used to map a String path to this configuration. 312 * @since 1.4 313 */ 314 @SuppressWarnings({"UnusedDeclaration"}) 315 protected static <T extends Filter> FilterConfig<T> filterConfig(Class<T> type, String configValue) { 316 return filterConfig(Key.get(type), configValue); 317 } 318 319 320 /** 321 * Filter configuration which pairs a Filter class with its configuration used on a path. 322 * @param <T> The Servlet Filter class. 323 * @since 1.4 324 */ 325 public static class FilterConfig<T extends Filter> { 326 private Key<T> key; 327 private String configValue; 328 329 private FilterConfig(Key<T> key, String configValue) { 330 super(); 331 this.key = key; 332 this.configValue = configValue; 333 } 334 335 public Key<T> getKey() { 336 return key; 337 } 338 339 public String getConfigValue() { 340 return configValue; 341 } 342 } 343 344 345 346 347 348 349 350 // legacy methods 351 352 353 static boolean isGuiceVersion3() { 354 try { 355 Class.forName("com.google.inject.multibindings.MapKey"); 356 return false; 357 } catch (ClassNotFoundException e) { 358 return true; 359 } 360 } 361 362 private void addLegacyFilterChain(String pattern, FilterConfigKey filterConfigKey) { 363 364 FilterConfig<Filter> filterConfig = new FilterConfig<Filter>(filterConfigKey.getKey(), filterConfigKey.getConfigValue()); 365 addFilterChain(pattern, filterConfig); 366 } 367 368 /** 369 * Adds a filter chain to the shiro configuration. 370 * <p/> 371 * NOTE: If the provided key is for a subclass of {@link org.apache.shiro.web.filter.PathMatchingFilter}, it will be registered with a proper 372 * provider. 373 * 374 * @param pattern 375 * @param keys 376 */ 377 @SuppressWarnings({"UnusedDeclaration"}) 378 @Deprecated 379 protected final void addFilterChain(String pattern, Key<? extends Filter>... keys) { 380 381 // We need to extract the keys and FilterConfigKey and convert to the new format. 382 383 FilterConfig[] filterConfigs = new FilterConfig[keys.length]; 384 for (int ii = 0; ii < keys.length; ii++) { 385 Key<? extends Filter> key = keys[ii]; 386 // If this is a path matching filter, we need to remember the config 387 if (key instanceof FilterConfigKey) { 388 // legacy config 389 FilterConfigKey legacyKey = (FilterConfigKey) key; 390 filterConfigs[ii] = new FilterConfig(legacyKey.getKey(), legacyKey.getConfigValue()); 391 } 392 else { 393 // Some other type of Filter key, no config 394 filterConfigs[ii] = new FilterConfig(key, ""); 395 } 396 } 397 398 filterChains.put(pattern, filterConfigs); 399 } 400 401 @Deprecated 402 protected static <T extends PathMatchingFilter> Key<T> config(Key<T> baseKey, String configValue) { 403 404 if( !isGuiceVersion3()) { 405 throw new ConfigurationException("Method ShiroWebModule.config(Key<? extends PathMatchingFilter>, String configValue), is not supported when using Guice 4+"); 406 } 407 408 return new FilterConfigKey<T>(baseKey, configValue); 409 } 410 411 @SuppressWarnings({"UnusedDeclaration"}) 412 @Deprecated 413 protected static <T extends PathMatchingFilter> Key<T> config(TypeLiteral<T> typeLiteral, String configValue) { 414 return config(Key.get(typeLiteral), configValue); 415 } 416 417 @SuppressWarnings({"UnusedDeclaration"}) 418 @Deprecated 419 protected static <T extends PathMatchingFilter> Key<T> config(Class<T> type, String configValue) { 420 return config(Key.get(type), configValue); 421 } 422 423 @Deprecated 424 private static class FilterConfigKey<T extends PathMatchingFilter> extends Key<T> { 425 private Key<T> key; 426 private String configValue; 427 428 private FilterConfigKey(Key<T> key, String configValue) { 429 super(); 430 this.key = key; 431 this.configValue = configValue; 432 } 433 434 public Key<T> getKey() { 435 return key; 436 } 437 438 public String getConfigValue() { 439 return configValue; 440 } 441 } 442 443}