| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| package org.apache.shiro.web.env; |
| |
| import org.apache.shiro.config.ConfigurationException; |
| import org.apache.shiro.config.ResourceConfigurable; |
| import org.apache.shiro.util.ClassUtils; |
| import org.apache.shiro.util.LifecycleUtils; |
| import org.apache.shiro.util.StringUtils; |
| import org.apache.shiro.util.UnknownClassException; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| import javax.servlet.ServletContext; |
| import java.util.ArrayList; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.ServiceLoader; |
| |
| |
| /** |
| * An {@code EnvironmentLoader} is responsible for loading a web application's Shiro {@link WebEnvironment} |
| * (which includes the web app's {@link org.apache.shiro.web.mgt.WebSecurityManager WebSecurityManager}) into the |
| * {@code ServletContext} at application startup. |
| * <p/> |
| * In Shiro 1.1 and earlier, the Shiro ServletFilter was responsible for creating the {@code WebSecurityManager} and |
| * any additional objects (security filters, etc). However, any component not filtered by the Shiro Filter (such |
| * as other context listeners) was not able to easily acquire the these objects to perform security operations. |
| * <p/> |
| * Due to this, in Shiro 1.2 and later, this {@code EnvironmentLoader} (or more likely, the |
| * {@link EnvironmentLoaderListener} subclass) is the preferred mechanism to initialize |
| * a Shiro environment. The Shiro Filter, while still required for request filtering, will not perform this |
| * initialization at startup if the {@code EnvironmentLoader} (or listener) runs first. |
| * <h2>Usage</h2> |
| * This implementation will look for two servlet context {@code context-param}s in {@code web.xml}: |
| * {@code shiroEnvironmentClass} and {@code shiroConfigLocations} that customize how the {@code WebEnvironment} instance |
| * will be initialized. |
| * <h3>shiroEnvironmentClass</h3> |
| * The {@code shiroEnvironmentClass} {@code context-param}, if it exists, allows you to specify the |
| * fully-qualified implementation class name of the {@link WebEnvironment} to instantiate. For example: |
| * <pre> |
| * <context-param> |
| * <param-name>shiroEnvironmentClass</param-name> |
| * <param-value>com.foo.bar.shiro.MyWebEnvironment</param-value> |
| * </context-param> |
| * </pre> |
| * If not specified, the default value is the {@link IniWebEnvironment} class, which assumes Shiro's default |
| * <a href="http://shiro.apache.org/configuration.html">INI configuration format</a> |
| * <h3>shiroConfigLocations</h3> |
| * The {@code shiroConfigLocations} {@code context-param}, if it exists, allows you to specify the config location(s) |
| * (resource path(s)) that will be relayed to the instantiated {@link WebEnvironment}. For example: |
| * <pre> |
| * <context-param> |
| * <param-name>shiroConfigLocations</param-name> |
| * <param-value>/WEB-INF/someLocation/shiro.ini</param-value> |
| * </context-param> |
| * </pre> |
| * The {@code WebEnvironment} implementation must implement the {@link ResourceConfigurable} interface if it is to |
| * acquire the {@code shiroConfigLocations} value. |
| * <p/> |
| * If this {@code context-param} is not specified, the {@code WebEnvironment} instance determines default resource |
| * lookup behavior. For example, the {@link IniWebEnvironment} will check the following two locations for INI config |
| * by default (in order): |
| * <ol> |
| * <li>/WEB-INF/shiro.ini</li> |
| * <li>classpath:shiro.ini</li> |
| * </ol> |
| * <h2>Web Security Enforcement</h2> |
| * Using this loader will only initialize Shiro's environment in a web application - it will not filter web requests or |
| * perform web-specific security operations. To do this, you must ensure that you have also configured the |
| * {@link org.apache.shiro.web.servlet.ShiroFilter ShiroFilter} in {@code web.xml}. |
| * <p/> |
| * Finally, it should be noted that this implementation was based on ideas in Spring 3's |
| * {@code org.springframework.web.context.ContextLoader} implementation - no need to reinvent the wheel for this common |
| * behavior. |
| * |
| * @see EnvironmentLoaderListener |
| * @see org.apache.shiro.web.servlet.ShiroFilter ShiroFilter |
| * @since 1.2 |
| */ |
| public class EnvironmentLoader { |
| |
| /** |
| * Servlet Context config param for specifying the {@link WebEnvironment} implementation class to use: |
| * {@code shiroEnvironmentClass} |
| */ |
| public static final String ENVIRONMENT_CLASS_PARAM = "shiroEnvironmentClass"; |
| |
| /** |
| * Servlet Context config param for the resource path to use for configuring the {@link WebEnvironment} instance: |
| * {@code shiroConfigLocations} |
| */ |
| public static final String CONFIG_LOCATIONS_PARAM = "shiroConfigLocations"; |
| |
| public static final String ENVIRONMENT_ATTRIBUTE_KEY = EnvironmentLoader.class.getName() + ".ENVIRONMENT_ATTRIBUTE_KEY"; |
| |
| private static final Logger log = LoggerFactory.getLogger(EnvironmentLoader.class); |
| |
| /** |
| * Initializes Shiro's {@link WebEnvironment} instance for the specified {@code ServletContext} based on the |
| * {@link #CONFIG_LOCATIONS_PARAM} value. |
| * |
| * @param servletContext current servlet context |
| * @return the new Shiro {@code WebEnvironment} instance. |
| * @throws IllegalStateException if an existing WebEnvironment has already been initialized and associated with |
| * the specified {@code ServletContext}. |
| */ |
| public WebEnvironment initEnvironment(ServletContext servletContext) throws IllegalStateException { |
| |
| if (servletContext.getAttribute(ENVIRONMENT_ATTRIBUTE_KEY) != null) { |
| String msg = "There is already a Shiro environment associated with the current ServletContext. " + |
| "Check if you have multiple EnvironmentLoader* definitions in your web.xml!"; |
| throw new IllegalStateException(msg); |
| } |
| |
| servletContext.log("Initializing Shiro environment"); |
| log.info("Starting Shiro environment initialization."); |
| |
| long startTime = System.currentTimeMillis(); |
| |
| try { |
| |
| WebEnvironment environment = createEnvironment(servletContext); |
| servletContext.setAttribute(ENVIRONMENT_ATTRIBUTE_KEY,environment); |
| |
| log.debug("Published WebEnvironment as ServletContext attribute with name [{}]", |
| ENVIRONMENT_ATTRIBUTE_KEY); |
| |
| if (log.isInfoEnabled()) { |
| long elapsed = System.currentTimeMillis() - startTime; |
| log.info("Shiro environment initialized in {} ms.", elapsed); |
| } |
| |
| return environment; |
| } catch (RuntimeException ex) { |
| log.error("Shiro environment initialization failed", ex); |
| servletContext.setAttribute(ENVIRONMENT_ATTRIBUTE_KEY, ex); |
| throw ex; |
| } catch (Error err) { |
| log.error("Shiro environment initialization failed", err); |
| servletContext.setAttribute(ENVIRONMENT_ATTRIBUTE_KEY, err); |
| throw err; |
| } |
| } |
| |
| /** |
| * Return the WebEnvironment implementation class to use, either the default |
| * {@link IniWebEnvironment} or a custom class if specified. |
| * |
| * @param servletContext current servlet context |
| * @return the WebEnvironment implementation class to use |
| * @see #ENVIRONMENT_CLASS_PARAM |
| * @see IniWebEnvironment |
| * @see #determineWebEnvironment(ServletContext) |
| * @see #getDefaultWebEnvironmentClass() |
| * @deprecated This method is not longer used by Shiro, and will be removed in future versions, |
| * use {@link #determineWebEnvironment(ServletContext)} or {@link #determineWebEnvironment(ServletContext)} |
| */ |
| @Deprecated |
| protected Class<?> determineWebEnvironmentClass(ServletContext servletContext) { |
| Class<? extends WebEnvironment> webEnvironmentClass = webEnvironmentClassFromServletContext(servletContext); |
| if( webEnvironmentClass != null) { |
| return webEnvironmentClass; |
| } else { |
| |
| return getDefaultWebEnvironmentClass(); |
| } |
| } |
| |
| private Class<? extends WebEnvironment> webEnvironmentClassFromServletContext(ServletContext servletContext) { |
| |
| Class<? extends WebEnvironment> webEnvironmentClass = null; |
| String className = servletContext.getInitParameter(ENVIRONMENT_CLASS_PARAM); |
| if (className != null) { |
| try { |
| webEnvironmentClass = ClassUtils.forName(className); |
| } catch (UnknownClassException ex) { |
| throw new ConfigurationException( |
| "Failed to load custom WebEnvironment class [" + className + "]", ex); |
| } |
| } |
| return webEnvironmentClass; |
| } |
| |
| private WebEnvironment webEnvironmentFromServiceLoader() { |
| |
| WebEnvironment webEnvironment = null; |
| // try to load WebEnvironment as a service |
| Iterator<WebEnvironment> iterator = doLoadWebEnvironmentsFromServiceLoader(); |
| |
| // Use the first one |
| if (iterator.hasNext()) { |
| webEnvironment = iterator.next(); |
| } |
| // if there are others, throw an error |
| if (iterator.hasNext()) { |
| List<String> allWebEnvironments = new ArrayList<String>(); |
| allWebEnvironments.add(webEnvironment.getClass().getName()); |
| while (iterator.hasNext()) { |
| allWebEnvironments.add(iterator.next().getClass().getName()); |
| } |
| throw new ConfigurationException("ServiceLoader for class [" + WebEnvironment.class + "] returned more then one " + |
| "result. ServiceLoader must return zero or exactly one result for this class. Select one using the " + |
| "servlet init parameter '"+ ENVIRONMENT_CLASS_PARAM +"'. Found: " + allWebEnvironments); |
| } |
| return webEnvironment; |
| } |
| |
| protected Iterator<WebEnvironment> doLoadWebEnvironmentsFromServiceLoader() { |
| ServiceLoader<WebEnvironment> serviceLoader = ServiceLoader.load(WebEnvironment.class); |
| |
| return serviceLoader.iterator(); |
| } |
| |
| /** |
| * Returns the default WebEnvironment class, which is unless overridden: {@link IniWebEnvironment}. |
| * @return the default WebEnvironment class. |
| */ |
| protected Class<? extends WebEnvironment> getDefaultWebEnvironmentClass() { |
| return IniWebEnvironment.class; |
| } |
| |
| /** |
| * Return the WebEnvironment implementation class to use, based on the order of: |
| * <ul> |
| * <li>A custom WebEnvironment class - specified in the {@code servletContext} {@link #ENVIRONMENT_ATTRIBUTE_KEY} property</li> |
| * <li>{@code ServiceLoader.load(WebEnvironment.class)} - (if more then one instance is found a {@link ConfigurationException} will be thrown</li> |
| * <li>A call to {@link #getDefaultWebEnvironmentClass()} (default: {@link IniWebEnvironment})</li> |
| * </ul> |
| * |
| * @param servletContext current servlet context |
| * @return the WebEnvironment implementation class to use |
| * @see #ENVIRONMENT_CLASS_PARAM |
| * @param servletContext the {@code servletContext} to query the {@code ENVIRONMENT_ATTRIBUTE_KEY} property from |
| * @return the {@code WebEnvironment} to be used |
| */ |
| protected WebEnvironment determineWebEnvironment(ServletContext servletContext) { |
| |
| Class<? extends WebEnvironment> webEnvironmentClass = webEnvironmentClassFromServletContext(servletContext); |
| WebEnvironment webEnvironment = null; |
| |
| // try service loader next |
| if (webEnvironmentClass == null) { |
| webEnvironment = webEnvironmentFromServiceLoader(); |
| } |
| |
| // if webEnvironment is not set, and ENVIRONMENT_CLASS_PARAM prop was not set, use the default |
| if (webEnvironmentClass == null && webEnvironment == null) { |
| webEnvironmentClass = getDefaultWebEnvironmentClass(); |
| } |
| |
| // at this point, we anything is set for the webEnvironmentClass, load it. |
| if (webEnvironmentClass != null) { |
| webEnvironment = (WebEnvironment) ClassUtils.newInstance(webEnvironmentClass); |
| } |
| |
| return webEnvironment; |
| } |
| |
| /** |
| * Instantiates a {@link WebEnvironment} based on the specified ServletContext. |
| * <p/> |
| * This implementation {@link #determineWebEnvironmentClass(javax.servlet.ServletContext) determines} a |
| * {@link WebEnvironment} implementation class to use. That class is instantiated, configured, and returned. |
| * <p/> |
| * This allows custom {@code WebEnvironment} implementations to be specified via a ServletContext init-param if |
| * desired. If not specified, the default {@link IniWebEnvironment} implementation will be used. |
| * |
| * @param sc current servlet context |
| * @return the constructed Shiro WebEnvironment instance |
| * @see MutableWebEnvironment |
| * @see ResourceConfigurable |
| */ |
| protected WebEnvironment createEnvironment(ServletContext sc) { |
| |
| WebEnvironment webEnvironment = determineWebEnvironment(sc); |
| if (!MutableWebEnvironment.class.isInstance(webEnvironment)) { |
| throw new ConfigurationException("Custom WebEnvironment class [" + webEnvironment.getClass().getName() + |
| "] is not of required type [" + MutableWebEnvironment.class.getName() + "]"); |
| } |
| |
| String configLocations = sc.getInitParameter(CONFIG_LOCATIONS_PARAM); |
| boolean configSpecified = StringUtils.hasText(configLocations); |
| |
| if (configSpecified && !(ResourceConfigurable.class.isInstance(webEnvironment))) { |
| String msg = "WebEnvironment class [" + webEnvironment.getClass().getName() + "] does not implement the " + |
| ResourceConfigurable.class.getName() + "interface. This is required to accept any " + |
| "configured " + CONFIG_LOCATIONS_PARAM + "value(s)."; |
| throw new ConfigurationException(msg); |
| } |
| |
| MutableWebEnvironment environment = (MutableWebEnvironment) webEnvironment; |
| |
| environment.setServletContext(sc); |
| |
| if (configSpecified && (environment instanceof ResourceConfigurable)) { |
| ((ResourceConfigurable) environment).setConfigLocations(configLocations); |
| } |
| |
| customizeEnvironment(environment); |
| |
| LifecycleUtils.init(environment); |
| |
| return environment; |
| } |
| |
| /** |
| * Any additional customization of the Environment can be by overriding this method. For example setup shared |
| * resources, etc. By default this method does nothing. |
| * @param environment |
| */ |
| protected void customizeEnvironment(WebEnvironment environment) { |
| } |
| |
| /** |
| * Destroys the {@link WebEnvironment} for the given servlet context. |
| * |
| * @param servletContext the ServletContext attributed to the WebSecurityManager |
| */ |
| public void destroyEnvironment(ServletContext servletContext) { |
| servletContext.log("Cleaning up Shiro Environment"); |
| try { |
| Object environment = servletContext.getAttribute(ENVIRONMENT_ATTRIBUTE_KEY); |
| if (environment instanceof WebEnvironment) { |
| finalizeEnvironment((WebEnvironment) environment); |
| } |
| LifecycleUtils.destroy(environment); |
| } finally { |
| servletContext.removeAttribute(ENVIRONMENT_ATTRIBUTE_KEY); |
| } |
| } |
| |
| /** |
| * Any additional cleanup of the Environment can be done by overriding this method. For example clean up shared |
| * resources, etc. By default this method does nothing. |
| * @param environment |
| * @since 1.3 |
| */ |
| protected void finalizeEnvironment(WebEnvironment environment) { |
| } |
| } |