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 com.google.inject.Binder; 022import com.google.inject.Key; 023import com.google.inject.TypeLiteral; 024import com.google.inject.binder.AnnotatedBindingBuilder; 025import com.google.inject.name.Names; 026import com.google.inject.servlet.ServletModule; 027import org.apache.shiro.config.ConfigurationException; 028import org.apache.shiro.env.Environment; 029import org.apache.shiro.guice.ShiroModule; 030import org.apache.shiro.lang.util.StringUtils; 031import org.apache.shiro.mgt.SecurityManager; 032import org.apache.shiro.session.mgt.SessionManager; 033import org.apache.shiro.web.env.WebEnvironment; 034import org.apache.shiro.web.filter.InvalidRequestFilter; 035import org.apache.shiro.web.filter.PathMatchingFilter; 036import org.apache.shiro.web.filter.authc.AnonymousFilter; 037import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter; 038import org.apache.shiro.web.filter.authc.BearerHttpAuthenticationFilter; 039import org.apache.shiro.web.filter.authc.FormAuthenticationFilter; 040import org.apache.shiro.web.filter.authc.LogoutFilter; 041import org.apache.shiro.web.filter.authc.UserFilter; 042import org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter; 043import org.apache.shiro.web.filter.authz.IpFilter; 044import org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter; 045import org.apache.shiro.web.filter.authz.PortFilter; 046import org.apache.shiro.web.filter.authz.RolesAuthorizationFilter; 047import org.apache.shiro.web.filter.authz.SslFilter; 048import org.apache.shiro.web.filter.mgt.FilterChainResolver; 049import org.apache.shiro.web.filter.session.NoSessionCreationFilter; 050import org.apache.shiro.web.mgt.DefaultWebSecurityManager; 051import org.apache.shiro.web.mgt.WebSecurityManager; 052import org.apache.shiro.web.session.mgt.ServletContainerSessionManager; 053 054import javax.servlet.Filter; 055import javax.servlet.ServletContext; 056import java.util.ArrayList; 057import java.util.Arrays; 058import java.util.Collection; 059import java.util.Collections; 060import java.util.HashMap; 061import java.util.LinkedHashMap; 062import java.util.List; 063import java.util.Map; 064 065@SuppressWarnings("checkstyle:JavadocVariable") 066/** 067 * Sets up Shiro lifecycles within Guice, enables the injecting of Shiro objects, and binds a default 068 * {@link org.apache.shiro.web.mgt.WebSecurityManager}, 069 * {@link org.apache.shiro.mgt.SecurityManager} and {@link org.apache.shiro.session.mgt.SessionManager}. 070 * At least one realm must be added by 071 * using {@link #bindRealm() bindRealm}. 072 * <p/> 073 * Also provides for the configuring of filter chains and binds a 074 * {@link org.apache.shiro.web.filter.mgt.FilterChainResolver} with that information. 075 */ 076public abstract class ShiroWebModule extends ShiroModule { 077 public static final Key<AnonymousFilter> ANON = Key.get(AnonymousFilter.class); 078 public static final Key<FormAuthenticationFilter> AUTHC = Key.get(FormAuthenticationFilter.class); 079 public static final Key<BasicHttpAuthenticationFilter> AUTHC_BASIC = Key.get(BasicHttpAuthenticationFilter.class); 080 public static final Key<BearerHttpAuthenticationFilter> AUTHC_BEARER = Key.get(BearerHttpAuthenticationFilter.class); 081 public static final Key<NoSessionCreationFilter> NO_SESSION_CREATION = Key.get(NoSessionCreationFilter.class); 082 public static final Key<LogoutFilter> LOGOUT = Key.get(LogoutFilter.class); 083 public static final Key<PermissionsAuthorizationFilter> PERMS = Key.get(PermissionsAuthorizationFilter.class); 084 public static final Key<PortFilter> PORT = Key.get(PortFilter.class); 085 public static final Key<HttpMethodPermissionFilter> REST = Key.get(HttpMethodPermissionFilter.class); 086 public static final Key<RolesAuthorizationFilter> ROLES = Key.get(RolesAuthorizationFilter.class); 087 public static final Key<SslFilter> SSL = Key.get(SslFilter.class); 088 public static final Key<IpFilter> IP = Key.get(IpFilter.class); 089 public static final Key<UserFilter> USER = Key.get(UserFilter.class); 090 public static final Key<InvalidRequestFilter> INVALID_REQUEST = Key.get(InvalidRequestFilter.class); 091 092 static final String NAME = "SHIRO"; 093 094 /** 095 * We use a LinkedHashMap here to ensure that iterator order is the same as add order. This is important, as the 096 * FilterChainResolver uses iterator order when searching for a matching chain. 097 */ 098 private final Map<String, FilterConfig<? extends Filter>[]> filterChains = 099 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}