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             WebEnvironment environment = createEnvironment(servletContext);
134             servletContext.setAttribute(ENVIRONMENT_ATTRIBUTE_KEY, environment);
135 
136             log.debug("Published WebEnvironment as ServletContext attribute with name [{}]",
137                     ENVIRONMENT_ATTRIBUTE_KEY);
138 
139             if (log.isInfoEnabled()) {
140                 long elapsed = System.currentTimeMillis() - startTime;
141                 log.info("Shiro environment initialized in {} ms.", elapsed);
142             }
143 
144             return environment;
145         } catch (RuntimeException ex) {
146             log.error("Shiro environment initialization failed", ex);
147             servletContext.setAttribute(ENVIRONMENT_ATTRIBUTE_KEY, ex);
148             throw ex;
149         } catch (Error err) {
150             log.error("Shiro environment initialization failed", err);
151             servletContext.setAttribute(ENVIRONMENT_ATTRIBUTE_KEY, err);
152             throw err;
153         }
154     }
155 
156     /**
157      * Return the WebEnvironment implementation class to use, either the default
158      * {@link IniWebEnvironment} or a custom class if specified.
159      *
160      * @param servletContext current servlet context
161      * @return the WebEnvironment implementation class to use
162      * @see #ENVIRONMENT_CLASS_PARAM
163      * @see IniWebEnvironment
164      */
165     protected Class<?> determineWebEnvironmentClass(ServletContext servletContext) {
166         String className = servletContext.getInitParameter(ENVIRONMENT_CLASS_PARAM);
167         if (className != null) {
168             try {
169                 return ClassUtils.forName(className);
170             } catch (UnknownClassException ex) {
171                 throw new ConfigurationException(
172                         "Failed to load custom WebEnvironment class [" + className + "]", ex);
173             }
174         } else {
175             return IniWebEnvironment.class;
176         }
177     }
178 
179     /**
180      * Instantiates a {@link WebEnvironment} based on the specified ServletContext.
181      * <p/>
182      * This implementation {@link #determineWebEnvironmentClass(javax.servlet.ServletContext) determines} a
183      * {@link WebEnvironment} implementation class to use.  That class is instantiated, configured, and returned.
184      * <p/>
185      * This allows custom {@code WebEnvironment} implementations to be specified via a ServletContext init-param if
186      * desired.  If not specified, the default {@link IniWebEnvironment} implementation will be used.
187      *
188      * @param sc current servlet context
189      * @return the constructed Shiro WebEnvironment instance
190      * @see MutableWebEnvironment
191      * @see ResourceConfigurable
192      */
193     protected WebEnvironment createEnvironment(ServletContext sc) {
194 
195         Class<?> clazz = determineWebEnvironmentClass(sc);
196         if (!MutableWebEnvironment.class.isAssignableFrom(clazz)) {
197             throw new ConfigurationException("Custom WebEnvironment class [" + clazz.getName() +
198                     "] is not of required type [" + WebEnvironment.class.getName() + "]");
199         }
200 
201         String configLocations = sc.getInitParameter(CONFIG_LOCATIONS_PARAM);
202         boolean configSpecified = StringUtils.hasText(configLocations);
203 
204         if (configSpecified && !(ResourceConfigurable.class.isAssignableFrom(clazz))) {
205             String msg = "WebEnvironment class [" + clazz.getName() + "] does not implement the " +
206                     ResourceConfigurable.class.getName() + "interface.  This is required to accept any " +
207                     "configured " + CONFIG_LOCATIONS_PARAM + "value(s).";
208             throw new ConfigurationException(msg);
209         }
210 
211         MutableWebEnvironment environment = (MutableWebEnvironment) ClassUtils.newInstance(clazz);
212 
213         environment.setServletContext(sc);
214 
215         if (configSpecified && (environment instanceof ResourceConfigurable)) {
216             ((ResourceConfigurable) environment).setConfigLocations(configLocations);
217         }
218 
219         customizeEnvironment(environment);
220 
221         LifecycleUtils.init(environment);
222 
223         return environment;
224     }
225 
226     protected void customizeEnvironment(WebEnvironment environment) {
227     }
228 
229     /**
230      * Destroys the {@link WebEnvironment} for the given servlet context.
231      *
232      * @param servletContext the ServletContext attributed to the WebSecurityManager
233      */
234     public void destroyEnvironment(ServletContext servletContext) {
235         servletContext.log("Cleaning up Shiro Environment");
236         try {
237             Object environment = servletContext.getAttribute(ENVIRONMENT_ATTRIBUTE_KEY);
238             LifecycleUtils.destroy(environment);
239         } finally {
240             servletContext.removeAttribute(ENVIRONMENT_ATTRIBUTE_KEY);
241         }
242     }
243 }