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.util.ClassUtils; 24 import org.apache.shiro.util.LifecycleUtils; 25 import org.apache.shiro.util.StringUtils; 26 import org.apache.shiro.util.UnknownClassException; 27 import org.slf4j.Logger; 28 import org.slf4j.LoggerFactory; 29 30 import javax.servlet.ServletContext; 31 32 /** 33 * An {@code EnvironmentLoader} is responsible for loading a web application's Shiro {@link WebEnvironment} 34 * (which includes the web app's {@link org.apache.shiro.web.mgt.WebSecurityManager WebSecurityManager}) into the 35 * {@code ServletContext} at application startup. 36 * <p/> 37 * In Shiro 1.1 and earlier, the Shiro ServletFilter was responsible for creating the {@code WebSecurityManager} and 38 * any additional objects (security filters, etc). However, any component not filtered by the Shiro Filter (such 39 * as other context listeners) was not able to easily acquire the these objects to perform security operations. 40 * <p/> 41 * Due to this, in Shiro 1.2 and later, this {@code EnvironmentLoader} (or more likely, the 42 * {@link EnvironmentLoaderListener} subclass) is the preferred mechanism to initialize 43 * a Shiro environment. The Shiro Filter, while still required for request filtering, will not perform this 44 * initialization at startup if the {@code EnvironmentLoader} (or listener) runs first. 45 * <h2>Usage</h2> 46 * This implementation will look for two servlet context {@code context-param}s in {@code web.xml}: 47 * {@code shiroEnvironmentClass} and {@code shiroConfigLocations} that customize how the {@code WebEnvironment} instance 48 * will be initialized. 49 * <h3>shiroEnvironmentClass</h3> 50 * The {@code shiroEnvironmentClass} {@code context-param}, if it exists, allows you to specify the 51 * fully-qualified implementation class name of the {@link WebEnvironment} to instantiate. For example: 52 * <pre> 53 * <context-param> 54 * <param-name>shiroEnvironmentClass</param-name> 55 * <param-value>com.foo.bar.shiro.MyWebEnvironment</param-value> 56 * </context-param> 57 * </pre> 58 * If not specified, the default value is the {@link IniWebEnvironment} class, which assumes Shiro's default 59 * <a href="http://shiro.apache.org/configuration.html">INI configuration format</a> 60 * <h3>shiroConfigLocations</h3> 61 * The {@code shiroConfigLocations} {@code context-param}, if it exists, allows you to specify the config location(s) 62 * (resource path(s)) that will be relayed to the instantiated {@link WebEnvironment}. For example: 63 * <pre> 64 * <context-param> 65 * <param-name>shiroConfigLocations</param-name> 66 * <param-value>/WEB-INF/someLocation/shiro.ini</param-value> 67 * </context-param> 68 * </pre> 69 * The {@code WebEnvironment} implementation must implement the {@link ResourceConfigurable} interface if it is to 70 * acquire the {@code shiroConfigLocations} value. 71 * <p/> 72 * If this {@code context-param} is not specified, the {@code WebEnvironment} instance determines default resource 73 * lookup behavior. For example, the {@link IniWebEnvironment} will check the following two locations for INI config 74 * by default (in order): 75 * <ol> 76 * <li>/WEB-INF/shiro.ini</li> 77 * <li>classpath:shiro.ini</li> 78 * </ol> 79 * <h2>Web Security Enforcement</h2> 80 * Using this loader will only initialize Shiro's environment in a web application - it will not filter web requests or 81 * perform web-specific security operations. To do this, you must ensure that you have also configured the 82 * {@link org.apache.shiro.web.servlet.ShiroFilter ShiroFilter} in {@code web.xml}. 83 * <p/> 84 * Finally, it should be noted that this implementation was based on ideas in Spring 3's 85 * {@code org.springframework.web.context.ContextLoader} implementation - no need to reinvent the wheel for this common 86 * behavior. 87 * 88 * @see EnvironmentLoaderListener 89 * @see org.apache.shiro.web.servlet.ShiroFilter ShiroFilter 90 * @since 1.2 91 */ 92 public class EnvironmentLoader { 93 94 /** 95 * Servlet Context config param for specifying the {@link WebEnvironment} implementation class to use: 96 * {@code shiroEnvironmentClass} 97 */ 98 public static final String ENVIRONMENT_CLASS_PARAM = "shiroEnvironmentClass"; 99 100 /** 101 * Servlet Context config param for the resource path to use for configuring the {@link WebEnvironment} instance: 102 * {@code shiroConfigLocations} 103 */ 104 public static final String CONFIG_LOCATIONS_PARAM = "shiroConfigLocations"; 105 106 public static final String ENVIRONMENT_ATTRIBUTE_KEY = EnvironmentLoader.class.getName() + ".ENVIRONMENT_ATTRIBUTE_KEY"; 107 108 private static final Logger log = LoggerFactory.getLogger(EnvironmentLoader.class); 109 110 /** 111 * Initializes Shiro's {@link WebEnvironment} instance for the specified {@code ServletContext} based on the 112 * {@link #CONFIG_LOCATIONS_PARAM} value. 113 * 114 * @param servletContext current servlet context 115 * @return the new Shiro {@code WebEnvironment} instance. 116 * @throws IllegalStateException if an existing WebEnvironment has already been initialized and associated with 117 * the specified {@code ServletContext}. 118 */ 119 public WebEnvironment initEnvironment(ServletContext servletContext) throws IllegalStateException { 120 121 if (servletContext.getAttribute(ENVIRONMENT_ATTRIBUTE_KEY) != null) { 122 String msg = "There is already a Shiro environment associated with the current ServletContext. " + 123 "Check if you have multiple EnvironmentLoader* definitions in your web.xml!"; 124 throw new IllegalStateException(msg); 125 } 126 127 servletContext.log("Initializing Shiro environment"); 128 log.info("Starting Shiro environment initialization."); 129 130 long startTime = System.currentTimeMillis(); 131 132 try { 133 134 WebEnvironment environment = createEnvironment(servletContext); 135 servletContext.setAttribute(ENVIRONMENT_ATTRIBUTE_KEY,environment); 136 137 log.debug("Published WebEnvironment as ServletContext attribute with name [{}]", 138 ENVIRONMENT_ATTRIBUTE_KEY); 139 140 if (log.isInfoEnabled()) { 141 long elapsed = System.currentTimeMillis() - startTime; 142 log.info("Shiro environment initialized in {} ms.", elapsed); 143 } 144 145 return environment; 146 } catch (RuntimeException ex) { 147 log.error("Shiro environment initialization failed", ex); 148 servletContext.setAttribute(ENVIRONMENT_ATTRIBUTE_KEY, ex); 149 throw ex; 150 } catch (Error err) { 151 log.error("Shiro environment initialization failed", err); 152 servletContext.setAttribute(ENVIRONMENT_ATTRIBUTE_KEY, err); 153 throw err; 154 } 155 } 156 157 /** 158 * Return the WebEnvironment implementation class to use, either the default 159 * {@link IniWebEnvironment} or a custom class if specified. 160 * 161 * @param servletContext current servlet context 162 * @return the WebEnvironment implementation class to use 163 * @see #ENVIRONMENT_CLASS_PARAM 164 * @see IniWebEnvironment 165 */ 166 protected Class<?> determineWebEnvironmentClass(ServletContext servletContext) { 167 String className = servletContext.getInitParameter(ENVIRONMENT_CLASS_PARAM); 168 if (className != null) { 169 try { 170 return ClassUtils.forName(className); 171 } catch (UnknownClassException ex) { 172 throw new ConfigurationException( 173 "Failed to load custom WebEnvironment class [" + className + "]", ex); 174 } 175 } else { 176 return IniWebEnvironment.class; 177 } 178 } 179 180 /** 181 * Instantiates a {@link WebEnvironment} based on the specified ServletContext. 182 * <p/> 183 * This implementation {@link #determineWebEnvironmentClass(javax.servlet.ServletContext) determines} a 184 * {@link WebEnvironment} implementation class to use. That class is instantiated, configured, and returned. 185 * <p/> 186 * This allows custom {@code WebEnvironment} implementations to be specified via a ServletContext init-param if 187 * desired. If not specified, the default {@link IniWebEnvironment} implementation will be used. 188 * 189 * @param sc current servlet context 190 * @return the constructed Shiro WebEnvironment instance 191 * @see MutableWebEnvironment 192 * @see ResourceConfigurable 193 */ 194 protected WebEnvironment createEnvironment(ServletContext sc) { 195 196 Class<?> clazz = determineWebEnvironmentClass(sc); 197 if (!MutableWebEnvironment.class.isAssignableFrom(clazz)) { 198 throw new ConfigurationException("Custom WebEnvironment class [" + clazz.getName() + 199 "] is not of required type [" + MutableWebEnvironment.class.getName() + "]"); 200 } 201 202 String configLocations = sc.getInitParameter(CONFIG_LOCATIONS_PARAM); 203 boolean configSpecified = StringUtils.hasText(configLocations); 204 205 if (configSpecified && !(ResourceConfigurable.class.isAssignableFrom(clazz))) { 206 String msg = "WebEnvironment class [" + clazz.getName() + "] does not implement the " + 207 ResourceConfigurable.class.getName() + "interface. This is required to accept any " + 208 "configured " + CONFIG_LOCATIONS_PARAM + "value(s)."; 209 throw new ConfigurationException(msg); 210 } 211 212 MutableWebEnvironment environment = (MutableWebEnvironment) ClassUtils.newInstance(clazz); 213 214 environment.setServletContext(sc); 215 216 if (configSpecified && (environment instanceof ResourceConfigurable)) { 217 ((ResourceConfigurable) environment).setConfigLocations(configLocations); 218 } 219 220 customizeEnvironment(environment); 221 222 LifecycleUtils.init(environment); 223 224 return environment; 225 } 226 227 /** 228 * Any additional customization of the Environment can be by overriding this method. For example setup shared 229 * resources, etc. By default this method does nothing. 230 * @param environment 231 */ 232 protected void customizeEnvironment(WebEnvironment environment) { 233 } 234 235 /** 236 * Destroys the {@link WebEnvironment} for the given servlet context. 237 * 238 * @param servletContext the ServletContext attributed to the WebSecurityManager 239 */ 240 public void destroyEnvironment(ServletContext servletContext) { 241 servletContext.log("Cleaning up Shiro Environment"); 242 try { 243 Object environment = servletContext.getAttribute(ENVIRONMENT_ATTRIBUTE_KEY); 244 if (environment instanceof WebEnvironment) { 245 finalizeEnvironment((WebEnvironment) environment); 246 } 247 LifecycleUtils.destroy(environment); 248 } finally { 249 servletContext.removeAttribute(ENVIRONMENT_ATTRIBUTE_KEY); 250 } 251 } 252 253 /** 254 * Any additional cleanup of the Environment can be done by overriding this method. For example clean up shared 255 * resources, etc. By default this method does nothing. 256 * @param environment 257 * @since 1.3 258 */ 259 protected void finalizeEnvironment(WebEnvironment environment) { 260 } 261 }