Tuesday, October 18, 2011

Apache Shiro : Application Security Made Easy

Considering that JAVA is over 10+ years old, the number of choices for application developers that need to build authentication and authorization into their applications is shockingly low.

In JAVA & J2EE, the JAAS specification was an attempt to address security. While JAAS works for authentication, the authorization part is just too cumbersome to use. The EJB and Servlet specifications offer coarse grained authorization at a method and resource level. But these are too coarse to be of any use in real world applications. For Spring users, Spring Security is an alternative. But it is a little complicated to use, especially the authorization model. A majority of applications end up building their home grown solutions for authentication and authorization.

Apache Shiro
is a open source JAVA security framework that addresses this problem. It is an elegant framework that lets you add authentication, authorization and session management to your application with ease.

The highlights of Shiro are:

It is a pure java framework. It works with all kinds of JAVA applications: J2SE, J2EE, Web, standalone or distributed.

It can integrate easily with various repositories that may host user and permissions metadata such as RDBMs, LDAPs.

It has a simple and intuitive permissions model that can apply to wide variety of problem domains. It is a model that lets you focus on your problem domain without getting you bogged down in the framework.

It has built in support for session management.

It has built in support for caching metadata.

It integrates very easily with Spring. Same applies to any J2EE application server.

Most importantly, it is very easy to use. Most of the time, all you will need to do to integrate Shiro, will be to implement a REALM that ties Shiro to your User and Permissions metadata.

Shiro Concepts

The SecurityManager encapsulates the security configuration of an application that uses Shiro.

Subject is the runtimes view of a user that is using the system. When the subject is created, it is not authenticated. For authentication, the login method must be called, passing in the proper credentials.

Session represents the session associated with an authenticated Subject. The session has a session id. Applications can store arbitrary data in the session. The session is valid until the user logs out or the session times out.

A permission represents what actions a subject may perform on a resource in the application. Out of the box Shiro supports permissions represented by colon separated tokens. Each token has some logical meaning. For example, my application may define a permission as ResourceType:actions:ResourceInstance. More concretely File:read:contacts.doc represents a permission to read a file contacts.doc. The permission must be associated with a user, to grant that permission to the user.

A Role is a collection of permissions that might represent ability to perform some organizational function. Roles make the association between users and permissions more manageable.

A Realm abstracts your user, permission and role metadata for Shiro. You make this data available to Shiro by implementing a realm and plugging it into Shiro. Typical realms use either a relational database or LDAP to store user data.

Tutorial
Let us build a simple java application that does some authentication and authorization. For this tutorial you will need:
(1) Apache Shiro
(2) A java development environment. I use Eclipse. But you can use other IDEs or command line tools as well.
(3) You may download the source code for this example at simpleshiro.zip

Step 1: Create a Shiro.ini configuration file

We will use the default file base realm that comes with Shiro. This reads the user/permission metadata from the shiro.ini file. In a subsequent tutorial, I will show how to build a realm that gets data from a relational database.

In the Ini file, let us define some users and associate some roles to them.

# Simple shiro.ini file
[users]
# user admin with password 123456 and role Administrator
admin = 123456, Administrator
# user mike with password abcdef and role Reader
mike = abcdef, Reader
# user joe with password !23abC2 and role Writer
joe = !23abC2, Writer
# -----------------------------------------------------------------------------
# Roles with assigned permissions
[roles]
# A permission is modeled as Resourcetype:actions:resourceinstances
# Administrator has permission to do all actions on all resources
Administrator = *:*:*
# Reader has permission to read all files
Reader = File:read:*
# Writer role has permission to read and write all files
Writer = File:read,write:*

In the above shiro.ini we have defined 3 users and 3 roles. The permission is modeled
as colon separated tokens. Each token can have multiple comma separated parts. Each domain and part grants permission to some application specific domain.

Step 2: BootStrap shiro into you application
Factory factory = new IniSecurityManagerFactory("classpath:shiro.ini");
SecurityManager securityManager = factory.getInstance();
SecurityUtils.setSecurityManager(securityManager);
IniSecurityManagerFactory loads the configuration from shiro.ini and creates a singleton SecurityManager for the application. For simplicity, Our shiro.ini goes with the default SecurityManager configuration which uses a Text based realm and gets user,permission,role metadata from the shiro.ini file.

Step 3: Login
Subject usr = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken("mike", "abcdef");
try {
    usr.login(token);
} 
catch (AuthenticationException ae) {
    log.error(ae.toString()) ;
    return ;
}
log.info("User [" + usr.getPrincipal() + "] logged in successfully.");
SecurityUtils is a factory class for getting an existing subject or creating a new one. Credentials are passed in using an AuthenticationToken. In this case, we want to pass in a username and password and hence use the UsernamePasswordToken. Then we call the login method on the Subject passing in the authentication token.

Step 4: Check if the user has permission
if (usr.isPermitted("File:write:xyz.doc")) {
    log.info(usr.getPrincipal() + " has permission to write xyz.doc ");
} else {
    log.info(usr.getPrincipal() + " does not have permission to write xyz.doc ");
}
if (usr.isPermitted("File:read:xyz.doc")) {
    log.info(usr.getPrincipal() + " has permission to read xyz.doc ");
} else {
    log.info(usr.getPrincipal() + " does not have permission to read xyz.doc ");
}
Subject has a isPermitted method that takes a permission string as parameter and returns true/false.

Step 5: Logout
usr.logout() ;

The logout method logs the user out.
To get familiar with Shiro, try changing the UsernamePasswordToken and login as a different user. Check some other permissions. Modify the Shiro.ini file to create new users and roles with different permissions. Run the program a few times with different metadata and different input.

In a production environment, you will not want users and roles in an ini file. You want them in a secure repository like a relational database or LDAP. In the next part, I will show you how to build a Shiro Realm that can use user,role, permission metadata from a relational database.