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.web.env; 020 021import org.apache.shiro.config.ConfigurationException; 022import org.apache.shiro.config.ResourceConfigurable; 023import org.apache.shiro.util.ClassUtils; 024import org.apache.shiro.util.LifecycleUtils; 025import org.apache.shiro.util.StringUtils; 026import org.apache.shiro.util.UnknownClassException; 027import org.slf4j.Logger; 028import org.slf4j.LoggerFactory; 029 030import javax.servlet.ServletContext; 031import java.util.ArrayList; 032import java.util.Iterator; 033import java.util.List; 034import java.util.ServiceLoader; 035 036 037/** 038 * An {@code EnvironmentLoader} is responsible for loading a web application's Shiro {@link WebEnvironment} 039 * (which includes the web app's {@link org.apache.shiro.web.mgt.WebSecurityManager WebSecurityManager}) into the 040 * {@code ServletContext} at application startup. 041 * <p/> 042 * In Shiro 1.1 and earlier, the Shiro ServletFilter was responsible for creating the {@code WebSecurityManager} and 043 * any additional objects (security filters, etc). However, any component not filtered by the Shiro Filter (such 044 * as other context listeners) was not able to easily acquire the these objects to perform security operations. 045 * <p/> 046 * Due to this, in Shiro 1.2 and later, this {@code EnvironmentLoader} (or more likely, the 047 * {@link EnvironmentLoaderListener} subclass) is the preferred mechanism to initialize 048 * a Shiro environment. The Shiro Filter, while still required for request filtering, will not perform this 049 * initialization at startup if the {@code EnvironmentLoader} (or listener) runs first. 050 * <h2>Usage</h2> 051 * This implementation will look for two servlet context {@code context-param}s in {@code web.xml}: 052 * {@code shiroEnvironmentClass} and {@code shiroConfigLocations} that customize how the {@code WebEnvironment} instance 053 * will be initialized. 054 * <h3>shiroEnvironmentClass</h3> 055 * The {@code shiroEnvironmentClass} {@code context-param}, if it exists, allows you to specify the 056 * fully-qualified implementation class name of the {@link WebEnvironment} to instantiate. For example: 057 * <pre> 058 * <context-param> 059 * <param-name>shiroEnvironmentClass</param-name> 060 * <param-value>com.foo.bar.shiro.MyWebEnvironment</param-value> 061 * </context-param> 062 * </pre> 063 * If not specified, the default value is the {@link IniWebEnvironment} class, which assumes Shiro's default 064 * <a href="http://shiro.apache.org/configuration.html">INI configuration format</a> 065 * <h3>shiroConfigLocations</h3> 066 * The {@code shiroConfigLocations} {@code context-param}, if it exists, allows you to specify the config location(s) 067 * (resource path(s)) that will be relayed to the instantiated {@link WebEnvironment}. For example: 068 * <pre> 069 * <context-param> 070 * <param-name>shiroConfigLocations</param-name> 071 * <param-value>/WEB-INF/someLocation/shiro.ini</param-value> 072 * </context-param> 073 * </pre> 074 * The {@code WebEnvironment} implementation must implement the {@link ResourceConfigurable} interface if it is to 075 * acquire the {@code shiroConfigLocations} value. 076 * <p/> 077 * If this {@code context-param} is not specified, the {@code WebEnvironment} instance determines default resource 078 * lookup behavior. For example, the {@link IniWebEnvironment} will check the following two locations for INI config 079 * by default (in order): 080 * <ol> 081 * <li>/WEB-INF/shiro.ini</li> 082 * <li>classpath:shiro.ini</li> 083 * </ol> 084 * <h2>Web Security Enforcement</h2> 085 * Using this loader will only initialize Shiro's environment in a web application - it will not filter web requests or 086 * perform web-specific security operations. To do this, you must ensure that you have also configured the 087 * {@link org.apache.shiro.web.servlet.ShiroFilter ShiroFilter} in {@code web.xml}. 088 * <p/> 089 * Finally, it should be noted that this implementation was based on ideas in Spring 3's 090 * {@code org.springframework.web.context.ContextLoader} implementation - no need to reinvent the wheel for this common 091 * behavior. 092 * 093 * @see EnvironmentLoaderListener 094 * @see org.apache.shiro.web.servlet.ShiroFilter ShiroFilter 095 * @since 1.2 096 */ 097public class EnvironmentLoader { 098 099 /** 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 public static final String ENVIRONMENT_ATTRIBUTE_KEY = EnvironmentLoader.class.getName() + ".ENVIRONMENT_ATTRIBUTE_KEY"; 112 113 private static final Logger log = LoggerFactory.getLogger(EnvironmentLoader.class); 114 115 /** 116 * Initializes Shiro's {@link WebEnvironment} instance for the specified {@code ServletContext} based on the 117 * {@link #CONFIG_LOCATIONS_PARAM} value. 118 * 119 * @param servletContext current servlet context 120 * @return the new Shiro {@code WebEnvironment} instance. 121 * @throws IllegalStateException if an existing WebEnvironment has already been initialized and associated with 122 * the specified {@code ServletContext}. 123 */ 124 public WebEnvironment initEnvironment(ServletContext servletContext) throws IllegalStateException { 125 126 if (servletContext.getAttribute(ENVIRONMENT_ATTRIBUTE_KEY) != null) { 127 String msg = "There is already a Shiro environment associated with the current ServletContext. " + 128 "Check if you have multiple EnvironmentLoader* definitions in your web.xml!"; 129 throw new IllegalStateException(msg); 130 } 131 132 servletContext.log("Initializing Shiro environment"); 133 log.info("Starting Shiro environment initialization."); 134 135 long startTime = System.currentTimeMillis(); 136 137 try { 138 139 WebEnvironment environment = createEnvironment(servletContext); 140 servletContext.setAttribute(ENVIRONMENT_ATTRIBUTE_KEY,environment); 141 142 log.debug("Published WebEnvironment as ServletContext attribute with name [{}]", 143 ENVIRONMENT_ATTRIBUTE_KEY); 144 145 if (log.isInfoEnabled()) { 146 long elapsed = System.currentTimeMillis() - startTime; 147 log.info("Shiro environment initialized in {} ms.", elapsed); 148 } 149 150 return environment; 151 } catch (RuntimeException ex) { 152 log.error("Shiro environment initialization failed", ex); 153 servletContext.setAttribute(ENVIRONMENT_ATTRIBUTE_KEY, ex); 154 throw ex; 155 } catch (Error err) { 156 log.error("Shiro environment initialization failed", err); 157 servletContext.setAttribute(ENVIRONMENT_ATTRIBUTE_KEY, err); 158 throw err; 159 } 160 } 161 162 /** 163 * Return the WebEnvironment implementation class to use, either the default 164 * {@link IniWebEnvironment} or a custom class if specified. 165 * 166 * @param servletContext current servlet context 167 * @return the WebEnvironment implementation class to use 168 * @see #ENVIRONMENT_CLASS_PARAM 169 * @see IniWebEnvironment 170 * @see #determineWebEnvironment(ServletContext) 171 * @see #getDefaultWebEnvironmentClass() 172 * @deprecated This method is not longer used by Shiro, and will be removed in future versions, 173 * use {@link #determineWebEnvironment(ServletContext)} or {@link #determineWebEnvironment(ServletContext)} 174 */ 175 @Deprecated 176 protected Class<?> determineWebEnvironmentClass(ServletContext servletContext) { 177 Class<? extends WebEnvironment> webEnvironmentClass = webEnvironmentClassFromServletContext(servletContext); 178 if( webEnvironmentClass != null) { 179 return webEnvironmentClass; 180 } else { 181 182 return getDefaultWebEnvironmentClass(); 183 } 184 } 185 186 private Class<? extends WebEnvironment> webEnvironmentClassFromServletContext(ServletContext servletContext) { 187 188 Class<? extends WebEnvironment> webEnvironmentClass = null; 189 String className = servletContext.getInitParameter(ENVIRONMENT_CLASS_PARAM); 190 if (className != null) { 191 try { 192 webEnvironmentClass = ClassUtils.forName(className); 193 } catch (UnknownClassException ex) { 194 throw new ConfigurationException( 195 "Failed to load custom WebEnvironment class [" + className + "]", ex); 196 } 197 } 198 return webEnvironmentClass; 199 } 200 201 private WebEnvironment webEnvironmentFromServiceLoader() { 202 203 WebEnvironment webEnvironment = null; 204 // try to load WebEnvironment as a service 205 ServiceLoader<WebEnvironment> serviceLoader = ServiceLoader.load(WebEnvironment.class); 206 Iterator<WebEnvironment> iterator = serviceLoader.iterator(); 207 208 // Use the first one 209 if (iterator.hasNext()) { 210 webEnvironment = iterator.next(); 211 } 212 // if there are others, throw an error 213 if (iterator.hasNext()) { 214 List<String> allWebEnvironments = new ArrayList<String>(); 215 allWebEnvironments.add(webEnvironment.getClass().getName()); 216 while (iterator.hasNext()) { 217 allWebEnvironments.add(iterator.next().getClass().getName()); 218 } 219 throw new ConfigurationException("ServiceLoader for class [" + WebEnvironment.class + "] returned more then one " + 220 "result. ServiceLoader must return zero or exactly one result for this class. Select one using the " + 221 "servlet init parameter '"+ ENVIRONMENT_CLASS_PARAM +"'. Found: " + allWebEnvironments); 222 } 223 return webEnvironment; 224 } 225 226 /** 227 * Returns the default WebEnvironment class, which is unless overridden: {@link IniWebEnvironment}. 228 * @return the default WebEnvironment class. 229 */ 230 protected Class<? extends WebEnvironment> getDefaultWebEnvironmentClass() { 231 return IniWebEnvironment.class; 232 } 233 234 /** 235 * Return the WebEnvironment implementation class to use, based on the order of: 236 * <ul> 237 * <li>A custom WebEnvironment class - specified in the {@code servletContext} {@link #ENVIRONMENT_ATTRIBUTE_KEY} property</li> 238 * <li>{@code ServiceLoader.load(WebEnvironment.class)} - (if more then one instance is found a {@link ConfigurationException} will be thrown</li> 239 * <li>A call to {@link #getDefaultWebEnvironmentClass()} (default: {@link IniWebEnvironment})</li> 240 * </ul> 241 * 242 * @param servletContext current servlet context 243 * @return the WebEnvironment implementation class to use 244 * @see #ENVIRONMENT_CLASS_PARAM 245 * @param servletContext the {@code servletContext} to query the {@code ENVIRONMENT_ATTRIBUTE_KEY} property from 246 * @return the {@code WebEnvironment} to be used 247 */ 248 protected WebEnvironment determineWebEnvironment(ServletContext servletContext) { 249 250 Class<? extends WebEnvironment> webEnvironmentClass = webEnvironmentClassFromServletContext(servletContext); 251 WebEnvironment webEnvironment = null; 252 253 // try service loader next 254 if (webEnvironmentClass == null) { 255 webEnvironment = webEnvironmentFromServiceLoader(); 256 } 257 258 // if webEnvironment is not set, and ENVIRONMENT_CLASS_PARAM prop was not set, use the default 259 if (webEnvironmentClass == null && webEnvironment == null) { 260 webEnvironmentClass = getDefaultWebEnvironmentClass(); 261 } 262 263 // at this point, we anything is set for the webEnvironmentClass, load it. 264 if (webEnvironmentClass != null) { 265 webEnvironment = (WebEnvironment) ClassUtils.newInstance(webEnvironmentClass); 266 } 267 268 return webEnvironment; 269 } 270 271 /** 272 * Instantiates a {@link WebEnvironment} based on the specified ServletContext. 273 * <p/> 274 * This implementation {@link #determineWebEnvironmentClass(javax.servlet.ServletContext) determines} a 275 * {@link WebEnvironment} implementation class to use. That class is instantiated, configured, and returned. 276 * <p/> 277 * This allows custom {@code WebEnvironment} implementations to be specified via a ServletContext init-param if 278 * desired. If not specified, the default {@link IniWebEnvironment} implementation will be used. 279 * 280 * @param sc current servlet context 281 * @return the constructed Shiro WebEnvironment instance 282 * @see MutableWebEnvironment 283 * @see ResourceConfigurable 284 */ 285 protected WebEnvironment createEnvironment(ServletContext sc) { 286 287 WebEnvironment webEnvironment = determineWebEnvironment(sc); 288 if (!MutableWebEnvironment.class.isInstance(webEnvironment)) { 289 throw new ConfigurationException("Custom WebEnvironment class [" + webEnvironment.getClass().getName() + 290 "] is not of required type [" + MutableWebEnvironment.class.getName() + "]"); 291 } 292 293 String configLocations = sc.getInitParameter(CONFIG_LOCATIONS_PARAM); 294 boolean configSpecified = StringUtils.hasText(configLocations); 295 296 if (configSpecified && !(ResourceConfigurable.class.isInstance(webEnvironment))) { 297 String msg = "WebEnvironment class [" + webEnvironment.getClass().getName() + "] does not implement the " + 298 ResourceConfigurable.class.getName() + "interface. This is required to accept any " + 299 "configured " + CONFIG_LOCATIONS_PARAM + "value(s)."; 300 throw new ConfigurationException(msg); 301 } 302 303 MutableWebEnvironment environment = (MutableWebEnvironment) webEnvironment; 304 305 environment.setServletContext(sc); 306 307 if (configSpecified && (environment instanceof ResourceConfigurable)) { 308 ((ResourceConfigurable) environment).setConfigLocations(configLocations); 309 } 310 311 customizeEnvironment(environment); 312 313 LifecycleUtils.init(environment); 314 315 return environment; 316 } 317 318 /** 319 * Any additional customization of the Environment can be by overriding this method. For example setup shared 320 * resources, etc. By default this method does nothing. 321 * @param environment 322 */ 323 protected void customizeEnvironment(WebEnvironment environment) { 324 } 325 326 /** 327 * Destroys the {@link WebEnvironment} for the given servlet context. 328 * 329 * @param servletContext the ServletContext attributed to the WebSecurityManager 330 */ 331 public void destroyEnvironment(ServletContext servletContext) { 332 servletContext.log("Cleaning up Shiro Environment"); 333 try { 334 Object environment = servletContext.getAttribute(ENVIRONMENT_ATTRIBUTE_KEY); 335 if (environment instanceof WebEnvironment) { 336 finalizeEnvironment((WebEnvironment) environment); 337 } 338 LifecycleUtils.destroy(environment); 339 } finally { 340 servletContext.removeAttribute(ENVIRONMENT_ATTRIBUTE_KEY); 341 } 342 } 343 344 /** 345 * Any additional cleanup of the Environment can be done by overriding this method. For example clean up shared 346 * resources, etc. By default this method does nothing. 347 * @param environment 348 * @since 1.3 349 */ 350 protected void finalizeEnvironment(WebEnvironment environment) { 351 } 352}