View Javadoc
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   * &lt;context-param&gt;
54   *     &lt;param-name&gt;shiroEnvironmentClass&lt;/param-name&gt;
55   *     &lt;param-value&gt;com.foo.bar.shiro.MyWebEnvironment&lt;/param-value&gt;
56   * &lt;/context-param&gt;
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   * &lt;context-param&gt;
65   *     &lt;param-name&gt;shiroConfigLocations&lt;/param-name&gt;
66   *     &lt;param-value&gt;/WEB-INF/someLocation/shiro.ini&lt;/param-value&gt;
67   * &lt;/context-param&gt;
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 }