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