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 */
019 package org.apache.shiro.web.servlet;
020
021 import org.apache.shiro.config.ConfigurationException;
022 import org.apache.shiro.config.Ini;
023 import org.apache.shiro.config.IniFactorySupport;
024 import org.apache.shiro.io.ResourceUtils;
025 import org.apache.shiro.mgt.SecurityManager;
026 import org.apache.shiro.util.CollectionUtils;
027 import org.apache.shiro.util.StringUtils;
028 import org.apache.shiro.web.config.IniFilterChainResolverFactory;
029 import org.apache.shiro.web.config.WebIniSecurityManagerFactory;
030 import org.apache.shiro.web.filter.mgt.FilterChainResolver;
031 import org.apache.shiro.web.mgt.WebSecurityManager;
032 import org.apache.shiro.web.util.WebUtils;
033 import org.slf4j.Logger;
034 import org.slf4j.LoggerFactory;
035
036 import java.io.InputStream;
037 import java.util.Map;
038
039 /**
040 * <h1>Deprecated</h1>
041 * This filter has been deprecated as of Shiro 1.2 in favor of using the {@link ShiroFilter} in {@code web.xml} instead.
042 * See the {@link ShiroFilter} JavaDoc for usage.
043 * <p/>
044 * ======================
045 * <p/>
046 * Servlet Filter that configures and enables all Shiro functions within a web application by using the
047 * <a href="http://en.wikipedia.org/wiki/INI_file">INI</a> configuration format.
048 * <p/>
049 * The actual INI configuration contents are not covered here, but instead in Shiro's
050 * <a href="http://shiro.apache.org/configuration.html">Configuration Documentation</a> and additional web-specific
051 * <a href="http://shiro.apache.org/web.html">Web Documentation</a>.
052 * <h2>Usage</h2>
053 * <h3>Default</h3>
054 * By default, the simplest filter declaration expects a {@code shiro.ini} resource to be located at
055 * {@code /WEB-INF/shiro.ini}, or, if not there, falls back to checking the root of the classpath
056 * (i.e. {@code classpath:shiro.ini}):
057 * <pre>
058 * <filter>
059 * <filter-name>ShiroFilter</filter-name>
060 * <filter-class>org.apache.shiro.web.servlet.IniShiroFilter</filter-class>
061 * </filter>
062 * </pre>
063 * <h3>Custom Path</h3>
064 * If you want the INI configuration to be somewhere other than {@code /WEB-INF/shiro.ini} or
065 * {@code classpath:shiro.ini}, you may specify an alternate location via the {@code configPath init-param}:
066 * <pre>
067 * <filter>
068 * <filter-name>ShiroFilter</filter-name>
069 * <filter-class>org.apache.shiro.web.servlet.IniShiroFilter</filter-class>
070 * <init-param>
071 * <param-name>configPath</param-name>
072 * <param-value>/WEB-INF/someFile.ini</param-value>
073 * </init-param>
074 * </filter>
075 * </pre>
076 * Unqualified (schemeless or 'non-prefixed') paths are assumed to be {@code ServletContext} resource paths, resolvable
077 * via {@link javax.servlet.ServletContext#getResourceAsStream(String) ServletContext#getResourceAsStream}.
078 * <p/>
079 * Non-ServletContext resources may be loaded from qualified locations by specifying prefixes indicating the source,
080 * e.g. {@code file:}, {@code url:}, and {@code classpath:}. See the
081 * {@link ResourceUtils#getInputStreamForPath(String)} JavaDoc for more.
082 * <h3>Inline</h3>
083 * For relatively simple environments, you can embed the INI config directly inside the filter declaration with
084 * the {@code config init-param}:
085 * <pre>
086 * <filter>
087 * <filter-name>ShiroFilter</filter-name>
088 * <filter-class>org.apache.shiro.web.servlet.IniShiroFilter</filter-class>
089 * <init-param>
090 * <param-name>config</param-name>
091 * <param-value>
092 * #INI config goes here...
093 * </param-value>
094 * </init-param>
095 * </filter>
096 * </pre>
097 * Although this is typically not recommended because any Shiro configuration changes would contribute to version control
098 * 'noise' in the web.xml file.
099 * <p/>
100 * When creating the shiro.ini configuration itself, please see Shiro's
101 * <a href="http://shiro.apache.org/configuration.html">Configuration Documentation</a> and
102 * <a href="http://shiro.apache.org/web.html">Web Documentation</a>.
103 *
104 * @see <a href="http://shiro.apache.org/configuration.html">Apache Shiro INI Configuration</a>
105 * @see <a href="http://shiro.apache.org/web.html">Apache Shiro Web Documentation</a>
106 * @since 1.0
107 * @deprecated in 1.2 in favor of using the {@link ShiroFilter}
108 */
109 @Deprecated
110 public class IniShiroFilter extends AbstractShiroFilter {
111
112 public static final String CONFIG_INIT_PARAM_NAME = "config";
113 public static final String CONFIG_PATH_INIT_PARAM_NAME = "configPath";
114
115 public static final String DEFAULT_WEB_INI_RESOURCE_PATH = "/WEB-INF/shiro.ini";
116
117 private static final Logger log = LoggerFactory.getLogger(IniShiroFilter.class);
118
119 private String config;
120 private String configPath;
121
122 public IniShiroFilter() {
123 }
124
125 /**
126 * Returns the actual INI configuration text to use to build the {@link SecurityManager} and
127 * {@link FilterChainResolver} used by the web application or {@code null} if the
128 * {@link #getConfigPath() configPath} should be used to load a fallback INI source.
129 * <p/>
130 * This value is {@code null} by default, but it will be automatically set to the value of the
131 * '{@code config}' {@code init-param} if it exists in the {@code FilterConfig} provided by the servlet
132 * container at startup.
133 *
134 * @return the actual INI configuration text to use to build the {@link SecurityManager} and
135 * {@link FilterChainResolver} used by the web application or {@code null} if the
136 * {@link #getConfigPath() configPath} should be used to load a fallback INI source.
137 */
138 public String getConfig() {
139 return this.config;
140 }
141
142 /**
143 * Sets the actual INI configuration text to use to build the {@link SecurityManager} and
144 * {@link FilterChainResolver} used by the web application. If this value is {@code null}, the
145 * {@link #getConfigPath() configPath} will be checked to see if a .ini file should be loaded instead.
146 * <p/>
147 * This value is {@code null} by default, but it will be automatically set to the value of the
148 * '{@code config}' {@code init-param} if it exists in the {@code FilterConfig} provided by the servlet
149 * container at startup.
150 *
151 * @param config the actual INI configuration text to use to build the {@link SecurityManager} and
152 * {@link FilterChainResolver} used by the web application.
153 */
154 public void setConfig(String config) {
155 this.config = config;
156 }
157
158 /**
159 * Returns the config path to be used to load a .ini file for configuration if a configuration is
160 * not specified via the {@link #getConfig() config} attribute.
161 * <p/>
162 * This value is {@code null} by default, but it will be automatically set to the value of the
163 * '{@code configPath}' {@code init-param} if it exists in the {@code FilterConfig} provided by the servlet
164 * container at startup.
165 *
166 * @return the config path to be used to load a .ini file for configuration if a configuration is
167 * not specified via the {@link #getConfig() config} attribute.
168 */
169 public String getConfigPath() {
170 return configPath;
171 }
172
173 /**
174 * Sets the config path to be used to load a .ini file for configuration if a configuration is
175 * not specified via the {@link #getConfig() config} attribute.
176 * <p/>
177 * This value is {@code null} by default, but it will be automatically set to the value of the
178 * '{@code configPath}' {@code init-param} if it exists in the {@code FilterConfig} provided by the servlet
179 * container at startup.
180 *
181 * @param configPath the config path to be used to load a .ini file for configuration if a configuration is
182 * not specified via the {@link #getConfig() config} attribute.
183 */
184 public void setConfigPath(String configPath) {
185 this.configPath = StringUtils.clean(configPath);
186 }
187
188 public void init() throws Exception {
189 applyInitParams();
190 configure();
191 }
192
193 protected void applyInitParams() throws Exception {
194 String config = getInitParam(CONFIG_INIT_PARAM_NAME);
195 if (config != null) {
196 setConfig(config);
197 }
198 String configPath = getInitParam(CONFIG_PATH_INIT_PARAM_NAME);
199 if (configPath != null) {
200 setConfigPath(configPath);
201 }
202 }
203
204 protected void configure() throws Exception {
205 Ini ini = loadIniFromConfig();
206
207 if (CollectionUtils.isEmpty(ini)) {
208 log.info("Null or empty configuration specified via 'config' init-param. " +
209 "Checking path-based configuration.");
210 ini = loadIniFromPath();
211 }
212 //added for SHIRO-178:
213 if (CollectionUtils.isEmpty(ini)) {
214 log.info("Null or empty configuration specified via '" + CONFIG_INIT_PARAM_NAME + "' or '" +
215 CONFIG_PATH_INIT_PARAM_NAME + "' filter parameters. Trying the default " +
216 DEFAULT_WEB_INI_RESOURCE_PATH + " file.");
217 ini = getServletContextIniResource(DEFAULT_WEB_INI_RESOURCE_PATH);
218 }
219 //although the preferred default is /WEB-INF/shiro.ini per SHIRO-178, keep this
220 //for backwards compatibility:
221 if (CollectionUtils.isEmpty(ini)) {
222 log.info("Null or empty configuration specified via '" + CONFIG_INIT_PARAM_NAME + "' or '" +
223 CONFIG_PATH_INIT_PARAM_NAME + "' filter parameters. Trying the default " +
224 IniFactorySupport.DEFAULT_INI_RESOURCE_PATH + " file.");
225 ini = IniFactorySupport.loadDefaultClassPathIni();
226 }
227
228 Map<String, ?> objects = applySecurityManager(ini);
229 applyFilterChainResolver(ini, objects);
230 }
231
232 protected Ini loadIniFromConfig() {
233 Ini ini = null;
234 String config = getConfig();
235 if (config != null) {
236 ini = convertConfigToIni(config);
237 }
238 return ini;
239 }
240
241 protected Ini loadIniFromPath() {
242 Ini ini = null;
243 String path = getConfigPath();
244 if (path != null) {
245 ini = convertPathToIni(path);
246 }
247 return ini;
248 }
249
250 protected Map<String, ?> applySecurityManager(Ini ini) {
251 WebIniSecurityManagerFactory factory;
252 if (CollectionUtils.isEmpty(ini)) {
253 factory = new WebIniSecurityManagerFactory();
254 } else {
255 factory = new WebIniSecurityManagerFactory(ini);
256 }
257
258 // Create the security manager and check that it implements WebSecurityManager.
259 // Otherwise, it can't be used with the filter.
260 SecurityManager securityManager = factory.getInstance();
261 if (!(securityManager instanceof WebSecurityManager)) {
262 String msg = "The configured security manager is not an instance of WebSecurityManager, so " +
263 "it can not be used with the Shiro servlet filter.";
264 throw new ConfigurationException(msg);
265 }
266
267 setSecurityManager((WebSecurityManager) securityManager);
268
269 return factory.getBeans();
270 }
271
272 protected void applyFilterChainResolver(Ini ini, Map<String, ?> defaults) {
273 if (ini == null || ini.isEmpty()) {
274 //nothing to use to create the resolver, just return
275 //(the AbstractShiroFilter allows a null resolver, in which case the original FilterChain is
276 // always used).
277 return;
278 }
279
280 //only create a resolver if the 'filters' or 'urls' sections are defined:
281 Ini.Section urls = ini.getSection(IniFilterChainResolverFactory.URLS);
282 Ini.Section filters = ini.getSection(IniFilterChainResolverFactory.FILTERS);
283 if ((urls != null && !urls.isEmpty()) || (filters != null && !filters.isEmpty())) {
284 //either the urls section or the filters section was defined. Go ahead and create the resolver
285 //and set it:
286 IniFilterChainResolverFactory filterChainResolverFactory = new IniFilterChainResolverFactory(ini, defaults);
287 filterChainResolverFactory.setFilterConfig(getFilterConfig());
288 FilterChainResolver resolver = filterChainResolverFactory.getInstance();
289 setFilterChainResolver(resolver);
290 }
291 }
292
293 protected Ini convertConfigToIni(String config) {
294 Ini ini = new Ini();
295 ini.load(config);
296 return ini;
297 }
298
299 /**
300 * Returns the INI instance reflecting the specified servlet context resource path or {@code null} if no
301 * resource was found.
302 *
303 * @param servletContextPath the servlet context resource path of the INI file to load
304 * @return the INI instance reflecting the specified servlet context resource path or {@code null} if no
305 * resource was found.
306 * @since 1.2
307 */
308 protected Ini getServletContextIniResource(String servletContextPath) {
309 String path = WebUtils.normalize(servletContextPath);
310 if (getServletContext() != null) {
311 InputStream is = getServletContext().getResourceAsStream(path);
312 if (is != null) {
313 Ini ini = new Ini();
314 ini.load(is);
315 if (CollectionUtils.isEmpty(ini)) {
316 log.warn("ServletContext INI resource '" + servletContextPath + "' exists, but it did not contain " +
317 "any data.");
318 }
319 return ini;
320 }
321 }
322 return null;
323 }
324
325 /**
326 * Converts the specified file path to an {@link Ini} instance.
327 * <p/>
328 * If the path does not have a resource prefix as defined by {@link ResourceUtils#hasResourcePrefix(String)}, the
329 * path is expected to be resolvable by the {@code ServletContext} via
330 * {@link javax.servlet.ServletContext#getResourceAsStream(String)}.
331 *
332 * @param path the path of the INI resource to load into an INI instance.
333 * @return an INI instance populated based on the given INI resource path.
334 */
335 protected Ini convertPathToIni(String path) {
336
337 Ini ini = new Ini();
338
339 //SHIRO-178: Check for servlet context resource and not
340 //only resource paths:
341 if (!ResourceUtils.hasResourcePrefix(path)) {
342 ini = getServletContextIniResource(path);
343 if (ini == null) {
344 String msg = "There is no servlet context resource corresponding to configPath '" + path + "' If " +
345 "the resource is located elsewhere (not immediately resolveable in the servlet context), " +
346 "specify an appropriate classpath:, url:, or file: resource prefix accordingly.";
347 throw new ConfigurationException(msg);
348 }
349 } else {
350 //normal resource path - load as usual:
351 ini.loadFromPath(path);
352 }
353
354 return ini;
355 }
356 }