Apache Shiro on AppEngine

Apache Shiro

Apache Shiro is a powerful and easy-to-use Java security framework that performs authentication, authorization, cryptography, and session management.

  • Authentication: Support logins across one or more pluggable data sources (LDAP, JDBC, Kerberos, ActiveDirectory, etc).
  • Authorization: Perform access control based on roles or fine-grained permissions, also using pluggable data sources.

Apache Shiro Authentication on AppEngine

We use AppEngine’s User Service to authenticate users. To integrate Apache Shiro with AppEngine User Service, we implement our own AuthenticatingFilter to control the login process

/**
 * An {@link AuthenticatingFilter} which uses UserService
 * to control the authentication process
 */
public class GaeAuthenticatingFilter extends AuthenticatingFilter{
  public GaeAuthenticatingFilter() {
    setLoginUrl(null);
  }

  @Override
  protected AuthenticationToken createToken(
      ServletRequest request, ServletResponse response) {
    UserService userService = UserServiceFactory.getUserService();
    User user = userService.getCurrentUser();
    return createToken(user.getUserId(), null, request, response);
  }

  @Override
  protected boolean isRememberMe(ServletRequest request) {
    return true;
  }

  @Override
  protected boolean onAccessDenied(ServletRequest request,
                                   ServletResponse response) throws Exception{
    UserService userService = UserServiceFactory.getUserService();
    User user = userService.getCurrentUser();

    if (user == null){
      saveRequest(request);

      String requestURI = WebUtils.getRequestUri(WebUtils.toHttp(request));

      String loginUrl = this.getLoginUrl();
      if (loginUrl == null){
        loginUrl = userService.createLoginURL(requestURI);
        WebUtils.issueRedirect(request, response, loginUrl);
      }else{
        request.setAttribute("requestURI", requestURI);
        request.getRequestDispatcher(loginUrl).forward(request, response);
      }

      return false;
    }else{
      // Perform the internal login process
      boolean result = executeLogin(request, response);
      return result;
    }
  }
}

Apache Shiro Security Realms

Apache Shiro provides several standard Realms (JndiLdapRealm, JdbcRealm, etc) which translates this application-specific data into Shiro internal format.

AppEngine Authenticating Realm

For AppEngine our realm as below:

/**
 * A {@link Realm} which uses {@link UserService} to authenicate users
 */
public class GaeAuthenticatingRealm extends AuthorizingRealm{
  public GaeAuthenticatingRealm() {
  }

  @Override
  protected void onInit() {
    // We use UserService to authenticate users,
    // thus dont have any credentials
    setCredentialsMatcher(new AllowAllCredentialsMatcher());
  }

  protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) {
    UserService userService = UserServiceFactory.getUserService();
    User user = userService.getCurrentUser();
    if (user == null){
      throw new AccountException("Not authenticated.");
    }

    return new SimpleAuthenticationInfo(user, null, getName());
  }

  protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
    // We perform Authentication only, thus return null
    return null;
  }
}

Put it all in web.xml

<web-app>
  ...
  <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>
        [main]
        authc = com.gdevelop.security.GaeAuthenticatingFilter
        # Optional, used for OpenId provider selection form.
        # authc.loginUrl=/WEB-INF/openIdLogin.jsp

        authcRealm = com.gdevelop.security.GaeAuthenticatingRealm

        [urls]
        /account/* = authc
      </param-value>
    </init-param>
  </filter>

  ...
  <filter-mapping>
    <filter-name>ShiroFilter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>
</web-app>

Build-in Authorizing Realm

For demo purpose, we use the build-in IniRealm. Just configure it in web.xml file.

<web-app>
  ...
  <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>
        [main]
        authc = com.gdevelop.security.GaeAuthenticatingFilter
        # Optional, used for OpenId provider selection form.
        # authc.loginUrl=/WEB-INF/openIdLogin.jsp

        authcRealm = com.gdevelop.security.GaeAuthenticatingRealm

        [users]
        # format: username = password, role1, role2, ..., roleN
        test@example.com  = ,admin
        test1@example.com = ,president
        test2@example.com = ,darklord,schwartz
        test3@example.com = ,goodguy,schwartz

        [urls]
        /account/* = authc
      </param-value>
    </init-param>
  </filter>

  ...
  <filter-mapping>
    <filter-name>ShiroFilter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>
</web-app>

Custom Authorizing Realm

You can implement a custom Authorizing Realm. The JdbcRealm is a good starting point to do so. See also the DatastoreRealm of the http://code.google.com/p/shiro-gae project.

<web-app>
  ...
  <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>
        [main]
        authc = com.gdevelop.security.GaeAuthenticatingFilter
        # Optional, used for OpenId provider selection form.
        # authc.loginUrl=/WEB-INF/openIdLogin.jsp

        authcRealm = com.gdevelop.security.GaeAuthenticatingRealm

        authzRealm = <full-qualified class name>

        [urls]
        /account/* = authc
      </param-value>
    </init-param>
  </filter>

  ...
  <filter-mapping>
    <filter-name>ShiroFilter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>
</web-app>

Secure our application

Now we are ready to perform authorization. For example, in your servlet class, we can check user permission, role as below:

Subject currentUser = SecurityUtils.getSubject();
if (currentUser.hasRole("manager")){
  ...
}
if (currentUser.isPermitted("reports:generate")){
  ..
}

Resources

Sample code base on the Apache Shiro Quickstart

Google App Engine
Apache Shiro

This entry was posted in AppEngine and tagged , , , . Bookmark the permalink.

2 Responses to Apache Shiro on AppEngine

  1. Hi Trung,

    This is a great post and very useful for GAE users. Thanks for sharing!

    Cheers,

    Les

  2. Dominik says:

    Thank you for the post.

    I am quite a noob but as I understand it, this tutorial adds Shiro on top of the Users service. Does this mean that everyone still logs in with their google accounts?

    Actually I was looking for a tutorial that would show how to use Apache Shiro and GAE to build your own user db (in the datastore). With people having to register and then login. Can someone point me to a tutorial like that? I haven’t found anything, yet.

Leave a Reply

Your email address will not be published. Required fields are marked *

*


*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>