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.web.env; 20 21 import org.apache.shiro.config.ConfigurationException; 22 import org.apache.shiro.config.ResourceConfigurable; 23 import org.apache.shiro.lang.util.ClassUtils; 24 import org.apache.shiro.lang.util.LifecycleUtils; 25 import org.apache.shiro.lang.util.StringUtils; 26 import org.apache.shiro.lang.util.UnknownClassException; 27 import org.slf4j.Logger; 28 import org.slf4j.LoggerFactory; 29 30 import javax.servlet.ServletContext; 31 import java.util.ArrayList; 32 import java.util.Iterator; 33 import java.util.List; 34 import java.util.ServiceLoader; 35 36 37 /** 38 * An {@code EnvironmentLoader} is responsible for loading a web application's Shiro {@link WebEnvironment} 39 * (which includes the web app's {@link org.apache.shiro.web.mgt.WebSecurityManager WebSecurityManager}) into the 40 * {@code ServletContext} at application startup. 41 * <p/> 42 * In Shiro 1.1 and earlier, the Shiro ServletFilter was responsible for creating the {@code WebSecurityManager} and 43 * any additional objects (security filters, etc.). However, any component not filtered by the Shiro Filter (such 44 * as other context listeners) was not able to easily acquire the these objects to perform security operations. 45 * <p/> 46 * Due to this, in Shiro 1.2 and later, this {@code EnvironmentLoader} (or more likely, the 47 * {@link EnvironmentLoaderListener} subclass) is the preferred mechanism to initialize 48 * a Shiro environment. The Shiro Filter, while still required for request filtering, will not perform this 49 * initialization at startup if the {@code EnvironmentLoader} (or listener) runs first. 50 * <h2>Usage</h2> 51 * This implementation will look for two servlet context {@code context-param}s in {@code web.xml}: 52 * {@code shiroEnvironmentClass} and {@code shiroConfigLocations} that customize how the {@code WebEnvironment} instance 53 * will be initialized. 54 * <h3>shiroEnvironmentClass</h3> 55 * The {@code shiroEnvironmentClass} {@code context-param}, if it exists, allows you to specify the 56 * fully-qualified implementation class name of the {@link WebEnvironment} to instantiate. For example: 57 * <pre> 58 * <context-param> 59 * <param-name>shiroEnvironmentClass</param-name> 60 * <param-value>com.foo.bar.shiro.MyWebEnvironment</param-value> 61 * </context-param> 62 * </pre> 63 * If not specified, the default value is the {@link IniWebEnvironment} class, which assumes Shiro's default 64 * <a href="http://shiro.apache.org/configuration.html">INI configuration format</a> 65 * <h3>shiroConfigLocations</h3> 66 * The {@code shiroConfigLocations} {@code context-param}, if it exists, allows you to specify the config location(s) 67 * (resource path(s)) that will be relayed to the instantiated {@link WebEnvironment}. For example: 68 * <pre> 69 * <context-param> 70 * <param-name>shiroConfigLocations</param-name> 71 * <param-value>/WEB-INF/someLocation/shiro.ini</param-value> 72 * </context-param> 73 * </pre> 74 * The {@code WebEnvironment} implementation must implement the {@link ResourceConfigurable} interface if it is to 75 * acquire the {@code shiroConfigLocations} value. 76 * <p/> 77 * If this {@code context-param} is not specified, the {@code WebEnvironment} instance determines default resource 78 * lookup behavior. For example, the {@link IniWebEnvironment} will check the following two locations for INI config 79 * by default (in order): 80 * <ol> 81 * <li>/WEB-INF/shiro.ini</li> 82 * <li>classpath:shiro.ini</li> 83 * </ol> 84 * <h2>Web Security Enforcement</h2> 85 * Using this loader will only initialize Shiro's environment in a web application - it will not filter web requests or 86 * perform web-specific security operations. To do this, you must ensure that you have also configured the 87 * {@link org.apache.shiro.web.servlet.ShiroFilter ShiroFilter} in {@code web.xml}. 88 * <p/> 89 * Finally, it should be noted that this implementation was based on ideas in Spring 3's 90 * {@code org.springframework.web.context.ContextLoader} implementation - no need to reinvent the wheel for this common 91 * behavior. 92 * 93 * @see EnvironmentLoaderListener 94 * @see org.apache.shiro.web.servlet.ShiroFilter ShiroFilter 95 * @since 1.2 96 */ 97 public class EnvironmentLoader { 98 99 /** 100 * Servlet Context config param for specifying the {@link WebEnvironment} implementation class to use: 101 * {@code shiroEnvironmentClass} 102 */ 103 public static final String ENVIRONMENT_CLASS_PARAM = "shiroEnvironmentClass"; 104 105 /** 106 * Servlet Context config param for the resource path to use for configuring the {@link WebEnvironment} instance: 107 * {@code shiroConfigLocations} 108 */ 109 public static final String CONFIG_LOCATIONS_PARAM = "shiroConfigLocations"; 110 111 /** 112 * environment attribute key. 113 */ 114 public static final String ENVIRONMENT_ATTRIBUTE_KEY = EnvironmentLoader.class.getName() + ".ENVIRONMENT_ATTRIBUTE_KEY"; 115 116 private static final Logger LOGGER = LoggerFactory.getLogger(EnvironmentLoader.class); 117 118 /** 119 * Initializes Shiro's {@link WebEnvironment} instance for the specified {@code ServletContext} based on the 120 * {@link #CONFIG_LOCATIONS_PARAM} value. 121 * 122 * @param servletContext current servlet context 123 * @return the new Shiro {@code WebEnvironment} instance. 124 * @throws IllegalStateException if an existing WebEnvironment has already been initialized and associated with 125 * the specified {@code ServletContext}. 126 */ 127 public WebEnvironment initEnvironment(ServletContext servletContext) throws IllegalStateException { 128 129 if (servletContext.getAttribute(ENVIRONMENT_ATTRIBUTE_KEY) != null) { 130 String msg = "There is already a Shiro environment associated with the current ServletContext. " 131 + "Check if you have multiple EnvironmentLoader* definitions in your web.xml!"; 132 throw new IllegalStateException(msg); 133 } 134 135 servletContext.log("Initializing Shiro environment"); 136 LOGGER.info("Starting Shiro environment initialization."); 137 138 long startTime = System.currentTimeMillis(); 139 140 try { 141 142 WebEnvironment environment = createEnvironment(servletContext); 143 servletContext.setAttribute(ENVIRONMENT_ATTRIBUTE_KEY, environment); 144 145 LOGGER.debug("Published WebEnvironment as ServletContext attribute with name [{}]", 146 ENVIRONMENT_ATTRIBUTE_KEY); 147 148 if (LOGGER.isInfoEnabled()) { 149 long elapsed = System.currentTimeMillis() - startTime; 150 LOGGER.info("Shiro environment initialized in {} ms.", elapsed); 151 } 152 153 return environment; 154 } catch (RuntimeException | Error ex) { 155 LOGGER.error("Shiro environment initialization failed", ex); 156 servletContext.setAttribute(ENVIRONMENT_ATTRIBUTE_KEY, ex); 157 throw ex; 158 } 159 } 160 161 /** 162 * Return the WebEnvironment implementation class to use, either the default 163 * {@link IniWebEnvironment} or a custom class if specified. 164 * 165 * @param servletContext current servlet context 166 * @return the WebEnvironment implementation class to use 167 * @see #ENVIRONMENT_CLASS_PARAM 168 * @see IniWebEnvironment 169 * @see #determineWebEnvironment(ServletContext) 170 * @see #getDefaultWebEnvironmentClass(ServletContext) 171 * @deprecated This method is not longer used by Shiro, and will be removed in future versions, 172 * use {@link #determineWebEnvironment(ServletContext)} or {@link #determineWebEnvironment(ServletContext)} 173 */ 174 @Deprecated 175 protected Class<?> determineWebEnvironmentClass(ServletContext servletContext) { 176 Class<? extends WebEnvironment> webEnvironmentClass = webEnvironmentClassFromServletContext(servletContext); 177 if (webEnvironmentClass != null) { 178 return webEnvironmentClass; 179 } else { 180 181 return getDefaultWebEnvironmentClass(servletContext); 182 } 183 } 184 185 private Class<? extends WebEnvironment> webEnvironmentClassFromServletContext(ServletContext servletContext) { 186 187 Class<? extends WebEnvironment> webEnvironmentClass = null; 188 String className = servletContext.getInitParameter(ENVIRONMENT_CLASS_PARAM); 189 if (className != null) { 190 try { 191 webEnvironmentClass = ClassUtils.forName(className); 192 } catch (UnknownClassException ex) { 193 throw new ConfigurationException( 194 "Failed to load custom WebEnvironment class [" + className + "]", ex); 195 } 196 } 197 return webEnvironmentClass; 198 } 199 200 private WebEnvironment webEnvironmentFromServiceLoader() { 201 202 WebEnvironment webEnvironment = null; 203 // try to load WebEnvironment as a service 204 Iterator<WebEnvironment> iterator = doLoadWebEnvironmentsFromServiceLoader(); 205 206 // Use the first one 207 if (iterator.hasNext()) { 208 webEnvironment = iterator.next(); 209 } 210 // if there are others, throw an error 211 if (iterator.hasNext()) { 212 List<String> allWebEnvironments = new ArrayList<String>(); 213 allWebEnvironments.add(webEnvironment.getClass().getName()); 214 while (iterator.hasNext()) { 215 allWebEnvironments.add(iterator.next().getClass().getName()); 216 } 217 throw new ConfigurationException("ServiceLoader for class [" + WebEnvironment.class + "] returned more then one " 218 + "result. ServiceLoader must return zero or exactly one result for this class. Select one using the " 219 + "servlet init parameter '" + ENVIRONMENT_CLASS_PARAM + "'. Found: " + allWebEnvironments); 220 } 221 return webEnvironment; 222 } 223 224 protected Iterator<WebEnvironment> doLoadWebEnvironmentsFromServiceLoader() { 225 ServiceLoader<WebEnvironment> serviceLoader = ServiceLoader.load(WebEnvironment.class); 226 227 return serviceLoader.iterator(); 228 } 229 230 /** 231 * Returns the default WebEnvironment class, which is unless overridden: {@link IniWebEnvironment}. 232 * 233 * @param ctx servlet context 234 * @return the default WebEnvironment class. 235 */ 236 protected Class<? extends WebEnvironment> getDefaultWebEnvironmentClass(ServletContext ctx) { 237 return IniWebEnvironment.class; 238 } 239 240 /** 241 * Return the WebEnvironment implementation class to use, based on the order of: 242 * <ul> 243 * <li>A custom WebEnvironment class 244 * - specified in the {@code servletContext} {@link #ENVIRONMENT_ATTRIBUTE_KEY} property</li> 245 * <li>{@code ServiceLoader.load(WebEnvironment.class)} - 246 * (if more then one instance is found a {@link ConfigurationException} will be thrown</li> 247 * <li>A call to {@link #getDefaultWebEnvironmentClass(ServletContext)} (default: {@link IniWebEnvironment})</li> 248 * </ul> 249 * 250 * @param servletContext current servlet context 251 * @param servletContext the {@code servletContext} to query the {@code ENVIRONMENT_ATTRIBUTE_KEY} property from 252 * @return the WebEnvironment implementation class to use 253 * @return the {@code WebEnvironment} to be used 254 * @see #ENVIRONMENT_CLASS_PARAM 255 */ 256 protected WebEnvironment determineWebEnvironment(ServletContext servletContext) { 257 258 Class<? extends WebEnvironment> webEnvironmentClass = webEnvironmentClassFromServletContext(servletContext); 259 WebEnvironment webEnvironment = null; 260 261 // try service loader next 262 if (webEnvironmentClass == null) { 263 webEnvironment = webEnvironmentFromServiceLoader(); 264 } 265 266 // if webEnvironment is not set, and ENVIRONMENT_CLASS_PARAM prop was not set, use the default 267 if (webEnvironmentClass == null && webEnvironment == null) { 268 webEnvironmentClass = getDefaultWebEnvironmentClass(servletContext); 269 } 270 271 // at this point, we anything is set for the webEnvironmentClass, load it. 272 if (webEnvironmentClass != null) { 273 webEnvironment = (WebEnvironment) ClassUtils.newInstance(webEnvironmentClass); 274 } 275 276 return webEnvironment; 277 } 278 279 /** 280 * Instantiates a {@link WebEnvironment} based on the specified ServletContext. 281 * <p/> 282 * This implementation {@link #determineWebEnvironmentClass(javax.servlet.ServletContext) determines} a 283 * {@link WebEnvironment} implementation class to use. That class is instantiated, configured, and returned. 284 * <p/> 285 * This allows custom {@code WebEnvironment} implementations to be specified via a ServletContext init-param if 286 * desired. If not specified, the default {@link IniWebEnvironment} implementation will be used. 287 * 288 * @param sc current servlet context 289 * @return the constructed Shiro WebEnvironment instance 290 * @see MutableWebEnvironment 291 * @see ResourceConfigurable 292 */ 293 protected WebEnvironment createEnvironment(ServletContext sc) { 294 295 WebEnvironment webEnvironment = determineWebEnvironment(sc); 296 if (!MutableWebEnvironment.class.isInstance(webEnvironment)) { 297 throw new ConfigurationException("Custom WebEnvironment class [" + webEnvironment.getClass().getName() 298 + "] is not of required type [" + MutableWebEnvironment.class.getName() + "]"); 299 } 300 301 String configLocations = sc.getInitParameter(CONFIG_LOCATIONS_PARAM); 302 boolean configSpecified = StringUtils.hasText(configLocations); 303 304 if (configSpecified && !(ResourceConfigurable.class.isInstance(webEnvironment))) { 305 String msg = "WebEnvironment class [" + webEnvironment.getClass().getName() + "] does not implement the " 306 + ResourceConfigurable.class.getName() + "interface. This is required to accept any " 307 + "configured " + CONFIG_LOCATIONS_PARAM + "value(s)."; 308 throw new ConfigurationException(msg); 309 } 310 311 MutableWebEnvironment environment = (MutableWebEnvironment) webEnvironment; 312 313 environment.setServletContext(sc); 314 315 if (configSpecified && (environment instanceof ResourceConfigurable)) { 316 ((ResourceConfigurable) environment).setConfigLocations(configLocations); 317 } 318 319 customizeEnvironment(environment); 320 321 LifecycleUtils.init(environment); 322 323 return environment; 324 } 325 326 /** 327 * Any additional customization of the Environment can be by overriding this method. For example setup shared 328 * resources, etc. By default this method does nothing. 329 * 330 * @param environment 331 */ 332 protected void customizeEnvironment(WebEnvironment environment) { 333 } 334 335 /** 336 * Destroys the {@link WebEnvironment} for the given servlet context. 337 * 338 * @param servletContext the ServletContext attributed to the WebSecurityManager 339 */ 340 public void destroyEnvironment(ServletContext servletContext) { 341 servletContext.log("Cleaning up Shiro Environment"); 342 try { 343 Object environment = servletContext.getAttribute(ENVIRONMENT_ATTRIBUTE_KEY); 344 if (environment instanceof WebEnvironment) { 345 finalizeEnvironment((WebEnvironment) environment); 346 } 347 LifecycleUtils.destroy(environment); 348 } finally { 349 servletContext.removeAttribute(ENVIRONMENT_ATTRIBUTE_KEY); 350 } 351 } 352 353 /** 354 * Any additional cleanup of the Environment can be done by overriding this method. For example clean up shared 355 * resources, etc. By default this method does nothing. 356 * 357 * @param environment 358 * @since 1.3 359 */ 360 protected void finalizeEnvironment(WebEnvironment environment) { 361 } 362 }