You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@shiro.apache.org by lh...@apache.org on 2011/04/27 20:56:53 UTC

svn commit: r1097204 - in /shiro/trunk: samples/web/src/main/webapp/WEB-INF/ web/src/main/java/org/apache/shiro/web/servlet/ web/src/main/java/org/apache/shiro/web/util/ web/src/test/groovy/org/apache/shiro/web/servlet/

Author: lhazlewood
Date: Wed Apr 27 18:56:52 2011
New Revision: 1097204

URL: http://svn.apache.org/viewvc?rev=1097204&view=rev
Log:
SHIRO-178: implemented functionality w/ test cases.  /WEB-INF/shiro.ini is checked by default, but any servlet context resource path may be specified via the 'configPath' filter init-param.  Documentation on the website has been updated to reflect this.  Also updated sample 'web' application to use this new approach (using a /WEB-INF/shiro.ini file)

Added:
    shiro/trunk/samples/web/src/main/webapp/WEB-INF/shiro.ini
Modified:
    shiro/trunk/samples/web/src/main/webapp/WEB-INF/web.xml
    shiro/trunk/web/src/main/java/org/apache/shiro/web/servlet/IniShiroFilter.java
    shiro/trunk/web/src/main/java/org/apache/shiro/web/util/WebUtils.java
    shiro/trunk/web/src/test/groovy/org/apache/shiro/web/servlet/IniShiroFilterTest.groovy

Added: shiro/trunk/samples/web/src/main/webapp/WEB-INF/shiro.ini
URL: http://svn.apache.org/viewvc/shiro/trunk/samples/web/src/main/webapp/WEB-INF/shiro.ini?rev=1097204&view=auto
==============================================================================
--- shiro/trunk/samples/web/src/main/webapp/WEB-INF/shiro.ini (added)
+++ shiro/trunk/samples/web/src/main/webapp/WEB-INF/shiro.ini Wed Apr 27 18:56:52 2011
@@ -0,0 +1,29 @@
+# INI configuration is very powerful and flexible, while still remaining succinct.
+# Please http://shiro.apache.org/configuration.html and
+# http://shiro.apache.org/web.html for more.
+
+[main]
+shiro.loginUrl = /login.jsp
+
+[users]
+# format: username = password, role1, role2, ..., roleN
+root = secret,admin
+guest = guest,guest
+presidentskroob = 12345,president
+darkhelmet = ludicrousspeed,darklord,schwartz
+lonestarr = vespa,goodguy,schwartz
+
+[roles]
+# format: roleName = permission1, permission2, ..., permissionN
+admin = *
+schwartz = lightsaber:*
+goodguy = winnebago:drive:eagle5
+
+[urls]
+# The /login.jsp is not restricted to authenticated users (otherwise no one could log in!), but
+# the 'authc' filter must still be specified for it so it can process that url's
+# login submissions. It is 'smart' enough to allow those requests through as specified by the
+# shiro.loginUrl above.
+/login.jsp = authc
+/account/** = authc
+/remoting/** = authc, roles[b2bClient], perms["remote:invoke:lan,wan"]
\ No newline at end of file

Modified: shiro/trunk/samples/web/src/main/webapp/WEB-INF/web.xml
URL: http://svn.apache.org/viewvc/shiro/trunk/samples/web/src/main/webapp/WEB-INF/web.xml?rev=1097204&r1=1097203&r2=1097204&view=diff
==============================================================================
--- shiro/trunk/samples/web/src/main/webapp/WEB-INF/web.xml (original)
+++ shiro/trunk/samples/web/src/main/webapp/WEB-INF/web.xml Wed Apr 27 18:56:52 2011
@@ -25,48 +25,7 @@
     <filter>
         <filter-name>ShiroFilter</filter-name>
         <filter-class>org.apache.shiro.web.servlet.IniShiroFilter</filter-class>
-        <init-param>
-            <param-name>config</param-name>
-            <param-value>
-
-                # The IniShiroFilter configuration is very powerful and flexible, while still remaining succinct.
-                # Please read the org.apache.shiro.web.servlet.IniShiroFilter JavaDoc for information.
-
-                # Quick Tip: Instead of having this configuration here in web.xml, you can instead
-                # move all of this to a 'shiro.ini' file at the root of the classpath and remove
-                # the 'config' init-param. Or you can specify the 'configPath' init-param and specify the
-                # path to a resource at any location (url, file or classpath). This may be desired if the
-                # config gets long and you want to keep web.xml clean.
-
-                [users]
-                # format: username = password, role1, role2, ..., roleN
-                root = secret,admin
-                guest = guest,guest
-                presidentskroob = 12345,president
-                darkhelmet = ludicrousspeed,darklord,schwartz
-                lonestarr = vespa,goodguy,schwartz
-
-                [roles]
-                # format; roleName = permission1, permission2, ..., permissionN
-                admin = *
-                schwartz = lightsaber:*
-                goodguy = winnebago:drive:eagle5
-
-                [filters]
-                shiro.loginUrl = /login.jsp
-
-                [urls]
-                # The /login.jsp is not restricted to authenticated users (otherwise no one could log in!), but
-                # the 'authc' filter must still be specified for it so it can process that url's
-                # login submissions. It is 'smart' enough to allow those requests through as specified by the
-                # shiro.loginUrl above.
-                /login.jsp = authc
-
-                /account/** = authc
-                /remoting/** = authc, roles[b2bClient], perms["remote:invoke:lan,wan"]
-
-            </param-value>
-        </init-param>
+        <!-- no init-param means load config from /WEB-INF/shiro.ini or classpath:shiro.ini, whichever is found first -->
     </filter>
 
     <filter-mapping>

Modified: shiro/trunk/web/src/main/java/org/apache/shiro/web/servlet/IniShiroFilter.java
URL: http://svn.apache.org/viewvc/shiro/trunk/web/src/main/java/org/apache/shiro/web/servlet/IniShiroFilter.java?rev=1097204&r1=1097203&r2=1097204&view=diff
==============================================================================
--- shiro/trunk/web/src/main/java/org/apache/shiro/web/servlet/IniShiroFilter.java (original)
+++ shiro/trunk/web/src/main/java/org/apache/shiro/web/servlet/IniShiroFilter.java Wed Apr 27 18:56:52 2011
@@ -21,201 +21,91 @@ package org.apache.shiro.web.servlet;
 import org.apache.shiro.config.ConfigurationException;
 import org.apache.shiro.config.Ini;
 import org.apache.shiro.config.IniFactorySupport;
+import org.apache.shiro.io.ResourceUtils;
 import org.apache.shiro.mgt.SecurityManager;
 import org.apache.shiro.util.CollectionUtils;
+import org.apache.shiro.util.StringUtils;
 import org.apache.shiro.web.config.IniFilterChainResolverFactory;
 import org.apache.shiro.web.config.WebIniSecurityManagerFactory;
 import org.apache.shiro.web.filter.mgt.FilterChainResolver;
 import org.apache.shiro.web.mgt.WebSecurityManager;
+import org.apache.shiro.web.util.WebUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.io.InputStream;
 import java.util.Map;
 
 /**
  * Main Servlet Filter that configures and enables all Shiro functions within a web application by using the
  * <a href="http://en.wikipedia.org/wiki/INI_file">INI</a> configuration format.
  * <p/>
- * The following is a fully commented example that documents how to configure it:
- * <pre>&lt;filter&gt;
- * &lt;filter-name&gt;ShiroFilter&lt;/filter-name&gt;
- * &lt;filter-class&gt;org.apache.shiro.web.servlet.IniShiroFilter&lt;/filter-class&gt;
- * &lt;init-param&gt;&lt;param-name&gt;config&lt;/param-name&gt;&lt;param-value&gt;
- * #
- * #NOTE:  This config looks pretty long - but its not - its only a few lines of actual config.
- * #       Everything else is just heavily commented to explain things in-depth. Feel free to delete any
- * #       comments that you don't want to read from your own configuration ;)
- * #
- * # Any commented values below that _don't_ start with 'example.pkg' are Shiro's defaults.  If you want to change any
- * # values on those lines, you only need to uncomment the lines you want to change.
- * #
- * [main]
- * # The 'main' section defines Shiro-wide configuration.
- * #
- * # Each section's configuration is essentially an object graph definition in a .properties style (name/value pair)
- * # format.  The beans defined would be those that are used to construct the application's SecurityManager.  It is
- * # essentially 'poor man's' dependency injection via a .properties format.
- * #
- * # --- Defining Realms ---
- * #
- * # Any Realm defined here will automatically be injected into Shiro's default SecurityManager created at start up.
- * # For example:
- * #
- * # myRealm = example.pkg.security.MyRealm
- * #
- * # This would instantiate the example.pkg.security.MyRealm class with a default no-arg constructor and inject it into
- * # the SecurityManager.  More than one realm can be defined if needed.  You can create graphs and reference
- * # other beans ('$' bean reference notation) while defining Realms and other objects:
- * #
- * # <b>connectionFactory</b> = example.pkg.ConnectionFactory
- * # connectionFactory.driverClassName = a.jdbc.Driver
- * # connectionFactory.username = aUsername
- * # connectionFactory.password = aPassword
- * # connectionFactory.minConnections = 3
- * # connectionFactory.maxConnections = 10
- * # ... etc...
- * #
- * # myJdbcRealm = example.pkg.jdbc.MyJdbcRealm
- * # myJdbcRealm.connectionFactory = <b>$connectionFactory</b>
- * # ... etc ...
- * #
- * # --- Realm Factories ---
- * #
- * # If the INI style isn't robust enough for your needs, you also have the option of implementing the
- * # {@link org.apache.shiro.realm.RealmFactory org.apache.shiro.realm.RealmFactory} interface with more complex construction
- * # logic.  Then you can declare the implementation here instead.  The realms it returns will be injected in to the
- * # SecurityManager just as the individual Realms are.  For example:
- * #
- * # aRealmFactory = example.pkg.ClassThatImplementsRealmFactory
- * #
- * # --- SessionManager properties ---
- * #
- * # Except for Realms and RealmFactories, all other objects should be defined and set on the SecurityManager directly.
- * # The default 'securityManager' bean is an instance of {@link org.apache.shiro.web.mgt.DefaultWebSecurityManager}, so you
- * # can set any of its corresponding properties as necessary:
- * #
- * # someObject = some.fully.qualified.ClassName
- * # someObject.propertyN = foo
- * # ...
- * # securityManager.someObject = $someObject
- * #
- * # For example, if you wanted to change Shiro's default session mechanism, you can change the 'sessionMode' property.
- * # By default, Shiro's Session infrastructure in a web environment will use the
- * # Servlet container's HttpSession.  However, if you need to share session state across client types
- * # (e.g. Web MVC plus Java Web Start or Flash), or are doing distributed/shared Sessions for
- * # Single Sign On, HttpSessions aren't good enough.  You'll need to use Shiro's more powerful
- * # (and client-agnostic) session management.  You can enable this by uncommenting the following line
- * # and changing 'http' to 'native'
- * #
- * #securityManager.{@link org.apache.shiro.web.mgt.DefaultWebSecurityManager#setSessionMode(String) sessionMode} = http
- * #
- * [filters]
- * # This section defines the 'pool' of all Filters available to the url path definitions in the [urls] section below.
- * #
- * # The following commented values are already provided by Shiro by default and are immediately usable
- * # in the [urls] definitions below.  If you like, you may override any values by uncommenting only the lines
- * # you need to change.
- * #
- * # Each Filter is configured based on its functionality and/or protocol.  You should read each
- * # Filter's JavaDoc to fully understand what each does and how it works as well as how it would
- * # affect the user experience.
- * #
- * # Form-based Authentication filter:
- * #<a name="authc"></a>authc = {@link org.apache.shiro.web.filter.authc.FormAuthenticationFilter}
- * #authc.{@link org.apache.shiro.web.filter.authc.FormAuthenticationFilter#setLoginUrl(String) loginUrl} = /login.jsp
- * #authc.{@link org.apache.shiro.web.filter.authc.FormAuthenticationFilter#setUsernameParam(String) usernameParam} = username
- * #authc.{@link org.apache.shiro.web.filter.authc.FormAuthenticationFilter#setPasswordParam(String) passwordParam} = password
- * #authc.{@link org.apache.shiro.web.filter.authc.FormAuthenticationFilter#setRememberMeParam(String) rememberMeParam} = rememberMe
- * #authc.{@link org.apache.shiro.web.filter.authc.FormAuthenticationFilter#setSuccessUrl(String) successUrl}  = /login.jsp
- * #authc.{@link org.apache.shiro.web.filter.authc.FormAuthenticationFilter#setFailureKeyAttribute(String) failureKeyAttribute} = {@link org.apache.shiro.web.filter.authc.FormAuthenticationFilter#DEFAULT_ERROR_KEY_ATTRIBUTE_NAME}
- * #
- * # Http BASIC Authentication filter:
- * #<a name="authcBasic"></a>authcBasic = {@link org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter}
- * #authcBasic.{@link org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter#setApplicationName(String) applicationName} = application
- * #
- * # Roles filter: requires the requesting user to have one or more roles for the request to continue.
- * # If they do not have the specified roles, they are redirected to the specified URL.
- * #<a name="roles"></a>roles = {@link org.apache.shiro.web.filter.authz.RolesAuthorizationFilter}
- * #roles.{@link org.apache.shiro.web.filter.authz.RolesAuthorizationFilter#setUnauthorizedUrl(String) unauthorizedUrl} =
- * # (note the above url is null by default, which will cause an HTTP 403 (Access Denied) response instead
- * # of redirecting to a page.  If you want to show a 'nice page' instead, you should specify that url.
- * #
- * # Permissions filter: requires the requesting user to have one or more permissions for the request to
- * # continue, and if they do not, redirects them to the specified URL.
- * #<a name="perms"></a>perms = {@link org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter}
- * #perms.{@link org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter#setUnauthorizedUrl(String) unauthorizedUrl} =
- * # (note the above url is null by default, which will cause an HTTP 403 (Access Denied) response instead
- * # of redirecting to a page.  If you want to show a 'nice page' instead, you should specify that url.  Many
- * # applications like to use the same url specified in roles.unauthorizedUrl above.
- * #
- * #
- * # Define your own filters here as you would any other object as described in the '[main]' section above (properties,
- * # $references, etc).  To properly handle url path matching (see the [urls] section below), your
- * # filter should extend the {@link org.apache.shiro.web.filter.PathMatchingFilter PathMatchingFilter} abstract class.
- * #
- * [urls]
- * # This section defines url path mappings.  Each mapping entry must be on a single line and conform to the
- * # following representation:
- * #
- * # ant_path_expression = path_specific_filter_chain_definition
- * #
- * # For any request that matches a specified path, the corresponding value defines a comma-delimited chain of
- * # filters to execute for that request.
- * #
- * # This is incredibly powerful in that you can define arbitrary filter chains for any given request pattern
- * # to greatly customize the security experience.
- * #
- * # The path_specific_filter_chain_definition must match the following format:
- * #
- * # filter1[optional_config1], filter2[optional_config2], ..., filterN[optional_configN]
- * #
- * # where 'filterN' is the name of an filter defined above in the [filters] section and
- * # '[optional_configN]' is an optional bracketed string that has meaning for that particular filter for
- * # _that particular path_.  If the filter does not need specific config for that url path, you may
- * # discard the brackets so filterN[] just becomes filterN.
- * #
- * # And because filter tokens define chains, order matters!  Define the tokens for each path pattern
- * # in the order you want them to filter (comma-delimited).
- * #
- * # Finally, each filter is free to handle the response however it wants if its necessary
- * # conditions are not met (redirect, HTTP error code, direct rendering, etc).  Otherwise, it is expected to allow
- * # the request to continue through the chain on to the final destination view.
- * #
- * # Examples:
- * #
- * # To illustrate chain configuration, look at the /account/** mapping below.  This says
- * # &quot;apply the above 'authcBasic' filter to any request matching the '/account/**' pattern&quot;.  Since the
- * # 'authcBasic' filter does not need any path-specific config, it doesn't have any config brackets [].
- * #
- * # The /remoting/** definition on the other hand uses the 'roles' and 'perms' filters which do use
- * # bracket notation.  That definition says:
- * #
- * # &quot;To access /remoting/** urls, ensure that the user is first authenticated ('authcBasic'), then ensure that user
- * # has the 'b2bClient' role, and then finally ensure that they have the 'remote:invoke:lan,wan' permission.&quot;
- * #
- * # (Note that because elements within brackets [ ] are comma-delimited themselves, we needed to quote any config
- * # value which may require a comma. If we didn't do that, the permission filter below would interpret
- * # the text between the brackets as two permissions: 'remote:invoke:lan' and 'wan' instead of the
- * # single desired 'remote:invoke:lan,wan' token.  So, you can use quotes wherever you need to escape internal
- * # commas.)
- * #
- * /account/** = <a href="#authcBasic">authcBasic</a>
- * /remoting/** = <a href="#authcBasic">authcBasic</a>, <a href="#roles">roles</a>[b2bClient], <a href="#perms">perms</a>["remote:invoke:lan,wan"]
- * &lt;/param-value&gt;&lt;/init-param&gt;
+ * The actual INI configuration contents are not covered here, but instead in Shiro's
+ * <a href="http://shiro.apache.org/configuration.html">Configuration Documentation</a> and additional web-specific
+ * <a href="http://shiro.apache.org/web.html">Web Documentation</a>.
+ * <h2>Usage</h2>
+ * <h3>Default</h3>
+ * By default, the simplest filter declaration expects a {@code shiro.ini} resource to be located at
+ * {@code /WEB-INF/shiro.ini}, or, if not there, falls back to checking the root of the classpath
+ * (i.e. {@code classpath:shiro.ini}):
+ * <pre>
+ * &lt;filter&gt;
+ *     &lt;filter-name&gt;ShiroFilter&lt;/filter-name&gt;
+ *     &lt;filter-class&gt;org.apache.shiro.web.servlet.IniShiroFilter&lt;/filter-class&gt;
+ * &lt;/filter&gt;
+ * </pre>
+ * <h3>Custom Path</h3>
+ * If you want the INI configuration to be somewhere other than {@code /WEB-INF/shiro.ini} or
+ * {@code classpath:shiro.ini}, you may specify an alternate location via the {@code configPath init-param}:
+ * <pre>
+ * &lt;filter&gt;
+ *     &lt;filter-name&gt;ShiroFilter&lt;/filter-name&gt;
+ *     &lt;filter-class&gt;org.apache.shiro.web.servlet.IniShiroFilter&lt;/filter-class&gt;
+ *     &lt;init-param&gt;
+ *         &lt;param-name&gt;configPath&lt;/param-name&gt;
+ *         &lt;param-value&gt;/WEB-INF/someFile.ini&lt;/param-value&gt;
+ *     &lt;/init-param&gt;
  * &lt;/filter&gt;
+ * </pre>
+ * Unqualified (schemeless or 'non-prefixed') paths are assumed to be {@code ServletContext} resource paths, resolvable
+ * via {@link javax.servlet.ServletContext#getResourceAsStream(String) ServletContext#getResourceAsStream}.
  * <p/>
- * &lt;filter-mapping&gt;
+ * Non-ServletContext resources may be loaded from qualified locations by specifying prefixes indicating the source,
+ * e.g. {@code file:}, {@code url:}, and {@code classpath:}.  See the
+ * {@link ResourceUtils#getInputStreamForPath(String)} JavaDoc for more.
+ * <h3>Inline</h3>
+ * For relatively simple environments, you can embed the INI config directly inside the filter declaration with
+ * the {@code config init-param}:
+ * <pre>
+ * &lt;filter&gt;
  *     &lt;filter-name&gt;ShiroFilter&lt;/filter-name&gt;
- *     &lt;url-pattern&gt;/*&lt;/url-pattern&gt;
- * &lt;/filter-mapping&gt;</pre>
+ *     &lt;filter-class&gt;org.apache.shiro.web.servlet.IniShiroFilter&lt;/filter-class&gt;
+ *     &lt;init-param&gt;
+ *         &lt;param-name&gt;config&lt;/param-name&gt;
+ *         &lt;param-value&gt;
+ *             #INI config goes here...
+ *      &lt;/param-value&gt;
+ *     &lt;/init-param&gt;
+ * &lt;/filter&gt;
+ * </pre>
+ * Although this is typically not recommended because any Shiro configuration changes would contribute to version control
+ * 'noise' in the web.xml file.
+ * <p/>
+ * When creating the shiro.ini configuration itself, please see Shiro's
+ * <a href="http://shiro.apache.org/configuration.html">Configuration Documentation</a> and
+ * <a href="http://shiro.apache.org/web.html">Web Documentation</a>.
  *
  * @since 1.0
+ * @see <a href="http://shiro.apache.org/configuration.html">Apache Shiro INI Configuration</a>
+ * @see <a href="http://shiro.apache.org/web.html">Apache Shiro Web Documentation</a>
  */
 public class IniShiroFilter extends AbstractShiroFilter {
 
     public static final String CONFIG_INIT_PARAM_NAME = "config";
     public static final String CONFIG_PATH_INIT_PARAM_NAME = "configPath";
 
+    public static final String DEFAULT_WEB_INI_RESOURCE_PATH = "/WEB-INF/shiro.ini";
+
     private static final Logger log = LoggerFactory.getLogger(IniShiroFilter.class);
 
     private String config;
@@ -284,7 +174,11 @@ public class IniShiroFilter extends Abst
      *                   not specified via the {@link #getConfig() config} attribute.
      */
     public void setConfigPath(String configPath) {
-        this.configPath = configPath;
+        String path = StringUtils.clean(configPath);
+        if (path != null) {
+            path = WebUtils.normalize(path);
+        }
+        this.configPath = path;
     }
 
     public void init() throws Exception {
@@ -311,6 +205,15 @@ public class IniShiroFilter extends Abst
                     "Checking path-based configuration.");
             ini = loadIniFromPath();
         }
+        //added for SHIRO-178:
+        if (CollectionUtils.isEmpty(ini)) {
+            log.info("Null or empty configuration specified via '" + CONFIG_INIT_PARAM_NAME + "' or '" +
+                    CONFIG_PATH_INIT_PARAM_NAME + "' filter parameters.  Trying the default " +
+                    DEFAULT_WEB_INI_RESOURCE_PATH + " file.");
+            ini = getServletContextIniResource(DEFAULT_WEB_INI_RESOURCE_PATH);
+        }
+        //although the preferred default is /WEB-INF/shiro.ini per SHIRO-178, keep this
+        //for backwards compatibility:
         if (CollectionUtils.isEmpty(ini)) {
             log.info("Null or empty configuration specified via '" + CONFIG_INIT_PARAM_NAME + "' or '" +
                     CONFIG_PATH_INIT_PARAM_NAME + "' filter parameters.  Trying the default " +
@@ -389,9 +292,61 @@ public class IniShiroFilter extends Abst
         return ini;
     }
 
+    /**
+     * Returns the INI instance reflecting the specified servlet context resource path or {@code null} if no
+     * resource was found.
+     *
+     * @param servletContextPath the servlet context resource path of the INI file to load
+     * @return the INI instance reflecting the specified servlet context resource path or {@code null} if no
+     *         resource was found.
+     * @since 1.2
+     */
+    protected Ini getServletContextIniResource(String servletContextPath) {
+        String path = WebUtils.normalize(servletContextPath);
+        if (getServletContext() != null) {
+            InputStream is = getServletContext().getResourceAsStream(path);
+            if (is != null) {
+                Ini ini = new Ini();
+                ini.load(is);
+                if (CollectionUtils.isEmpty(ini)) {
+                    log.warn("ServletContext INI resource '" + servletContextPath + "' exists, but it did not contain " +
+                            "any data.");
+                }
+                return ini;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Converts the specified file path to an {@link Ini} instance.
+     * <p/>
+     * If the path does not have a resource prefix as defined by {@link ResourceUtils#hasResourcePrefix(String)}, the
+     * path is expected to be resolvable by the {@code ServletContext} via
+     * {@link javax.servlet.ServletContext#getResourceAsStream(String)}.
+     *
+     * @param path the path of the INI resource to load into an INI instance.
+     * @return an INI instance populated based on the given INI resource path.
+     */
     protected Ini convertPathToIni(String path) {
+
         Ini ini = new Ini();
-        ini.loadFromPath(path);
+
+        //SHIRO-178: Check for servlet context resource and not
+        //only resource paths:
+        if (!ResourceUtils.hasResourcePrefix(path)) {
+            ini = getServletContextIniResource(path);
+            if (ini == null) {
+                String msg = "There is no servlet context resource corresponding to configPath '" + path + "'  If " +
+                        "the resource is located elsewhere (not immediately resolveable in the servlet context), " +
+                        "specify an appropriate classpath:, url:, or file: resource prefix accordingly.";
+                throw new ConfigurationException(msg);
+            }
+        } else {
+            //normal resource path - load as usual:
+            ini.loadFromPath(path);
+        }
+
         return ini;
     }
 }

Modified: shiro/trunk/web/src/main/java/org/apache/shiro/web/util/WebUtils.java
URL: http://svn.apache.org/viewvc/shiro/trunk/web/src/main/java/org/apache/shiro/web/util/WebUtils.java?rev=1097204&r1=1097203&r2=1097204&view=diff
==============================================================================
--- shiro/trunk/web/src/main/java/org/apache/shiro/web/util/WebUtils.java (original)
+++ shiro/trunk/web/src/main/java/org/apache/shiro/web/util/WebUtils.java Wed Apr 27 18:56:52 2011
@@ -145,9 +145,9 @@ public class WebUtils {
      * Tomcat trunk, r939305
      *
      * @param path Relative path to be normalized
-     * 
+     * @return normalized path
      */
-    private static String normalize(String path) {
+    public static String normalize(String path) {
         return normalize(path, true);
     }
 
@@ -161,6 +161,7 @@ public class WebUtils {
      *
      * @param path Relative path to be normalized
      * @param replaceBackSlash Should '\\' be replaced with '/'
+     * @return normalized path
      */
     private static String normalize(String path, boolean replaceBackSlash) {
 

Modified: shiro/trunk/web/src/test/groovy/org/apache/shiro/web/servlet/IniShiroFilterTest.groovy
URL: http://svn.apache.org/viewvc/shiro/trunk/web/src/test/groovy/org/apache/shiro/web/servlet/IniShiroFilterTest.groovy?rev=1097204&r1=1097203&r2=1097204&view=diff
==============================================================================
--- shiro/trunk/web/src/test/groovy/org/apache/shiro/web/servlet/IniShiroFilterTest.groovy (original)
+++ shiro/trunk/web/src/test/groovy/org/apache/shiro/web/servlet/IniShiroFilterTest.groovy Wed Apr 27 18:56:52 2011
@@ -2,6 +2,7 @@ package org.apache.shiro.web.servlet
 
 import javax.servlet.FilterConfig
 import javax.servlet.ServletContext
+import org.apache.shiro.io.ResourceUtils
 import static org.easymock.EasyMock.*
 
 /**
@@ -9,60 +10,64 @@ import static org.easymock.EasyMock.*
  */
 class IniShiroFilterTest extends GroovyTestCase {
 
-    IniShiroFilter filter;
-    FilterConfig mockFilterConfig;
-    ServletContext mockServletContext;
-
-    protected void setUp(String config) {
-        mockFilterConfig = createMock(FilterConfig.class);
-        mockServletContext = createMock(ServletContext.class);
-
-        expect(mockFilterConfig.getServletContext()).andReturn(mockServletContext).anyTimes();
-        expect(mockFilterConfig.getInitParameter(eq(AbstractShiroFilter.STATIC_INIT_PARAM_NAME))).andReturn null
-        expect(mockFilterConfig.getInitParameter(eq(IniShiroFilter.CONFIG_INIT_PARAM_NAME))).andReturn(config).once();
-        expect(mockFilterConfig.getInitParameter(eq(IniShiroFilter.CONFIG_PATH_INIT_PARAM_NAME))).andReturn(null).once();
-    }
+    void testDefaultWebInfConfig() {
+        def filterConfig = createStrictMock(FilterConfig)
+        def servletContext = createStrictMock(ServletContext)
+        def inputStream = ResourceUtils.getResourceAsStream("classpath:IniShiroFilterTest.ini")
 
-    protected void setUpWithPathConfig(String path) {
-        mockFilterConfig = createMock(FilterConfig.class);
-        mockServletContext = createMock(ServletContext.class);
-
-        expect(mockFilterConfig.getServletContext()).andReturn(mockServletContext).anyTimes();
-        expect(mockFilterConfig.getInitParameter(AbstractShiroFilter.STATIC_INIT_PARAM_NAME)).andReturn null
-        expect(mockFilterConfig.getInitParameter(IniShiroFilter.CONFIG_INIT_PARAM_NAME)).andReturn(null).anyTimes();
-        expect(mockFilterConfig.getInitParameter(IniShiroFilter.CONFIG_PATH_INIT_PARAM_NAME)).andReturn(path).anyTimes();
-    }
+        expect(filterConfig.getServletContext()).andReturn servletContext
+        expect(filterConfig.getInitParameter(eq(AbstractShiroFilter.STATIC_INIT_PARAM_NAME))).andReturn null
+        expect(filterConfig.getInitParameter(IniShiroFilter.CONFIG_INIT_PARAM_NAME)).andReturn null
+        expect(filterConfig.getInitParameter(IniShiroFilter.CONFIG_PATH_INIT_PARAM_NAME)).andReturn null
+        expect(servletContext.getResourceAsStream(IniShiroFilter.DEFAULT_WEB_INI_RESOURCE_PATH)).andReturn inputStream
+
+        replay filterConfig, servletContext
 
-    public void tearDown() throws Exception {
-        reset mockServletContext, mockFilterConfig
+        IniShiroFilter filter = new IniShiroFilter()
+        filter.init(filterConfig)
+
+        verify filterConfig, servletContext
     }
 
-    protected void replayAndVerify() throws Exception {
-        replay mockServletContext, mockFilterConfig
+    void testDefaultClasspathConfig() {
 
-        this.filter = new IniShiroFilter();
-        this.filter.init(mockFilterConfig);
+        def filterConfig = createStrictMock(FilterConfig)
+        def servletContext = createStrictMock(ServletContext)
 
-        verify mockFilterConfig, mockServletContext
-    }
+        expect(filterConfig.getServletContext()).andReturn servletContext
+        expect(filterConfig.getInitParameter(eq(AbstractShiroFilter.STATIC_INIT_PARAM_NAME))).andReturn null
+        expect(filterConfig.getInitParameter(IniShiroFilter.CONFIG_INIT_PARAM_NAME)).andReturn null
+        expect(filterConfig.getInitParameter(IniShiroFilter.CONFIG_PATH_INIT_PARAM_NAME)).andReturn null
+        expect(servletContext.getResourceAsStream(IniShiroFilter.DEFAULT_WEB_INI_RESOURCE_PATH)).andReturn null
+
+        replay filterConfig, servletContext
 
+        IniShiroFilter filter = new IniShiroFilter()
+        filter.init(filterConfig)
 
-    void testDefaultConfig() {
-        setUp(null);
-        replayAndVerify();
+        verify filterConfig, servletContext
     }
 
+
     void testSimpleConfig() {
-        setUp("""
+        def config = """
         [filters]
         authc.successUrl = /index.jsp
-        """);
-        replayAndVerify();
-    }
+        """
+        def filterConfig = createMock(FilterConfig)
+        def servletContext = createMock(ServletContext)
+
+        expect(filterConfig.getServletContext()).andReturn(servletContext).anyTimes()
+        expect(filterConfig.getInitParameter(eq(AbstractShiroFilter.STATIC_INIT_PARAM_NAME))).andReturn null
+        expect(filterConfig.getInitParameter(eq(IniShiroFilter.CONFIG_INIT_PARAM_NAME))).andReturn config
+        expect(filterConfig.getInitParameter(eq(IniShiroFilter.CONFIG_PATH_INIT_PARAM_NAME))).andReturn null
+
+        replay filterConfig, servletContext
+
+        IniShiroFilter filter = new IniShiroFilter()
+        filter.init(filterConfig)
 
-    void testSimplePathConfig() {
-        setUpWithPathConfig("classpath:IniShiroFilterTest.ini");
-        replayAndVerify();
+        verify filterConfig, servletContext
     }
 
 }