2014-04-13

Configuring Apache Shiro (shiro-web) without shiro.ini

Intro

Apache Shiro is a Java security framework, for authentication, authorization, etc.
Tutorials online I found[1,2,3] are all built around /WEB-INF/shiro.ini. Since Servlet 3.0 you can live a life without web.xml completely, so I think it's also possible to get rid of shiro.ini.
Below it shows the (almost) minimal code needed to translate shiro.ini into Java code.

Environment

Eclipse WTP 4.4M6
Tomcat 8.0.5
Java 7

Steps

  1. Create a dynamic web project.
  2. Mavenize the project.
  3. Add Maven dependencies:
    Maven dependencyNote
    org.apache.shiro:shiro-web:jar:1.2.3
    org.apache.tomcat:tomcat-api:jar:8.0.5It's needed for current Luna version
  4. Create ShiroFilter.java:
    import javax.servlet.annotation.WebFilter;
    
    @WebFilter("/*")
    public class ShiroFilter extends org.apache.shiro.web.servlet.ShiroFilter {
    }
    
  5. Create ShiroListener.java:
    import javax.servlet.ServletContextEvent;
    import javax.servlet.ServletContextListener;
    import javax.servlet.annotation.WebListener;
    
    import org.apache.shiro.web.env.EnvironmentLoaderListener;
    
    @WebListener
    public class ShiroListener extends EnvironmentLoaderListener implements
        ServletContextListener {
    
      @Override
      public void contextInitialized(ServletContextEvent sce) {
        sce.getServletContext().setInitParameter(ENVIRONMENT_CLASS_PARAM,
            HelloWebEnvironment.class.getName());
        super.contextInitialized(sce);
      }
    
    }
    
  6. Create HelloWebEnvironment.java:
    import org.apache.shiro.web.env.DefaultWebEnvironment;
    
    public class HelloWebEnvironment extends DefaultWebEnvironment {
    
      public HelloWebEnvironment() {
        super();
        setFilterChainResolver(HelloHelper.getFilterChainResolver());
        setSecurityManager(HelloHelper.getSecurityManager());
      }
    
    }
    
  7. Create HelloHelper.java:
    import org.apache.shiro.mgt.SecurityManager;
    import org.apache.shiro.realm.text.IniRealm;
    import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;
    import org.apache.shiro.web.filter.authc.LogoutFilter;
    import org.apache.shiro.web.filter.mgt.DefaultFilterChainManager;
    import org.apache.shiro.web.filter.mgt.FilterChainManager;
    import org.apache.shiro.web.filter.mgt.FilterChainResolver;
    import org.apache.shiro.web.filter.mgt.PathMatchingFilterChainResolver;
    import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
    
    public class HelloHelper {
    
      private static SecurityManager securityManager = null;
      private static FilterChainResolver filterChainResolver = null;
    
      public static SecurityManager getSecurityManager() {
        if (securityManager == null) {
          SimpleAccountRealm realm = new SimpleAccountRealm();
          realm.addAccount("jack", "abca1234", "employee");
    
          securityManager = new DefaultWebSecurityManager(realm);
        }
        return securityManager;
      }
    
      public static FilterChainResolver getFilterChainResolver() {
        if (filterChainResolver == null) {
          FormAuthenticationFilter authc = new FormAuthenticationFilter();
          authc.setLoginUrl("/login.html");
          LogoutFilter logout = new LogoutFilter();
    
          FilterChainManager fcMan = new DefaultFilterChainManager();
          fcMan.addFilter("authc", authc);
          fcMan.addFilter("logout", logout);
          fcMan.createChain("/logout", "logout");
          fcMan.createChain("/**", "authc");
    
          PathMatchingFilterChainResolver resolver = new PathMatchingFilterChainResolver();
          resolver.setFilterChainManager(fcMan);
          filterChainResolver = resolver;
        }
        return filterChainResolver;
      }
    
    }
    
  8. Create login.html:
    <html>
    <body>
      <form method="post">
        <table>
          <tr>
            <td><input name="username" type="text" /></td>
          </tr>
          <tr>
            <td><input name="password" type="password" /></td>
          </tr>
          <tr>
            <td><input type="submit" value="Submit" /></td>
          </tr>
        </table>
      </form>
    </body>
    </html>
    

Key points

With the code above, you should be able to start authenticate with jack:abca1234. Some key points:
  • ShiroFilter inherits Shiro's own ShiroFilter, annotated essentially just to bring the Shiro machinery into the Servlet container.
  • ShiroListener is similar to ShiroFilter, inheriting EnvironmentLoaderListener, annotated, with some custom logic to plug HelloWebEnvironment in.
  • HelloWebEnvironment is a custom class to set up Shiro environment. This is the key part to get rid of shiro.ini, which is used by IniWebEnvironment.
  • HelloHelper has 2 functions setting up a FilterChainResolver and a SecurityManager, which are essential for Shiro's operation.
    • Realm configurations are associated with SecurityManager.
    • FilterChainResolver requires a configured FilterChainManager, which is in charge of mapping URLs to filters.
  • In login.html, the form element should have no action attribute.

2014-02-01

Setting up a Jersey app on Tomcat 7.0.50 with Eclipse 4.3 and Maven 3.0.4

It took me the past 2 days to learn what Maven is and how to use it in Eclipse; what Jersey is, and how convenient to use it to implement a JAX-RS app on Tomcat; and what's new in Servlet 3.0, and how to deploy an app without a web.xml.

Maven is the apt-get for jars. Descriptor-less deployment seems to make plug-n-play more easily. JAX-RS annotations make REST on Servlet platform simpler.

Alright, after several experiment failures, I figured out a way to get Jersey working on Tomcat with help from Maven and Eclipse.

So real stuff:
  1. Create a "Dynamic Web Project".
  2. Right click on the new project, "Configure" > "Convert to Maven project...".
  3. Add Jersey dependency to pom.xml: (this MUST be added with a "compile" scope, "provided" will fail to let Tomcat discover the REST app; for other containers/platforms, read this)
    groupId: org.glassfish.jersey.containers
    artifactId: jersey-container-servlet
    version: 2.5.1
  4. Save pom, Maven update.
  5. Create a class extends javax.ws.rs.core.Application, annotated with javax.ws.rs.ApplicationPath.
  6. Create a class, annotated with javax.ws.rs.Path. Individual methods of this class should be properly annotated with javax.ws.rs.GET etc. for handling requests.