Introducing Apache Shiro
Use Apache Shiro for user authentication for your web application
Apache Shiro is a framework that you can use for authentication and authorization. This article gives a few examples of how to use Shiro in a Java™ application and provides an overview of how to use it in a Grails web application. To get the most out of this article, you should be comfortable creating Java applications and have the following components installed:
- A Java 1.6 JDK
- Grails (to run the web application examples)
Authentication and authorization
When securing systems, two elements of security are important: authentication and authorization. Though the two terms mean different things, they are sometimes used interchangeably because of their respective roles in application security.
Authentication deals with verifying a user's identity. When you authenticate users, you confirm that they really are who they claim to be. In most applications, authentication is done through a combination of a user name and password. As long as users choose passwords that are sufficiently difficult for others to guess, the combination of a user name and password is usually enough to establish identity. However, other means of authentication, such as fingerprints, certificates, and generated keys, are also available.
Once the authentication process successfully establishes identity, authorization takes over to restrict or grant access. It is possible that through authentication, a user can log in to a system but, through authorization, not be allowed to do anything. It is also possible to have a certain level of authorization for users that have not been authenticated.
When planning the security model for your application, you must address both elements to make sure your system has a sufficient level of security. Authentication is a common problem across applications (especially when done with only a user name and password), so letting a framework handle the work is a good idea. Proper frameworks offer the advantage of being tested and maintained, letting you focus on your business problem instead of re-solving a problem whose solution has already been achieved.
Apache Shiro offers a usable security framework that a variety of clients can apply to their applications. The examples in this article introduce Shiro and focus on the basic task of authenticating a user.
Getting to know Shiro
Shiro is a framework implemented in the Java language that provides both authentication and authorization in an easy-to-use API. By using Shiro, you can provide security for your application without writing all of the code from the beginning.
Because Shiro offers authentication with so many different data sources, as well as Enterprise Session Management, it's ideal for implementing single sign-on (SSO)—a desirable feature in large enterprises where users routinely log in to and use many different systems in one day. These data sources include JDBC, LDAP, Kerberos, and Microsoft® Active Directory® Directory Services (AD DS).
Shiro's Session
object lets you use a user's session
without having an HttpSession
. By using a generic
Session
object, you can use the same code even if
that code is not running inside a web application. By avoiding the requirement of
application server or web application server session management, you can use Shiro even in command-line environments. In other words, the code that you
write using Shiro's API lets you build command-line applications that connect
to an LDAP server and is the same code inside a web application that accesses
the LDAP server.
Downloading and installing Shiro
Shiro comes in pre-built binary distributions. You can either download the Shiro JAR files or put entries into Apache Maven or Apache Ivy to have the files installed automatically. This example uses Ivy to download the Shiro JAR files and other required libraries using the simple scripts shown in Listing 1.
Listing 1. The Apache Ivy file and Apache Ant script
<?xml version="1.0" encoding="UTF-8"?> <ivy-module version="2.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://ant.apache.org/ivy/schemas/ivy.xsd"> <info organisation="com.nathanagood.examples" module="shirotest" /> <configurations> <conf name="dist" description="Dependency configuration for distribution." /> </configurations> <dependencies> <dependency org="commons-logging" name="commons-logging" rev="1.1.1" conf="dist->default" /> <dependency org="org.slf4j" name="slf4j-log4j12" rev="1.5.8" conf="dist->default" /> <dependency org="org.apache.shiro" name="shiro-core" rev="1.0.0-incubating" conf="dist->default" /> <dependency org="org.apache.shiro" name="shiro-web" rev="1.0.0-incubating" conf="dist->default" /> </dependencies> </ivy-module> <project name="shiroTestApp" default="usage" basedir="." xmlns:ivy="antlib:org.apache.ivy.ant"> <property name="project.lib" value="lib" /> <path id="ivy.task.path"> <fileset dir="${basedir}/ivy-lib"> <include name="**/*.jar" /> </fileset> </path> <target name="resolve"> <taskdef resource="org/apache/ivy/ant/antlib.xml" uri="antlib:org.apache.ivy.ant" classpathref="ivy.task.path" /> <ivy:resolve /> <ivy:retrieve pattern="${project.lib}/[conf]/[artifact].[ext]" sync="true" /> </target> <target name="usage"> <echo message="Use --projecthelp to learn more about this project" /> </target> </project>
See Related topics for more information about using Ivy. If you don't use Maven or Ivy, download the Shiro JAR files from the download site, which is also provided in Related topics.
Once you download the libraries, simply add them to your CLASSPATH.
Write the simple code shown in Listing 2, which obtains
a reference to the current user and reports that the user is not
authenticated. (You use the Subject
class to
represent the user.)
Listing 2. The ShiroTest Java class
package com.nathanagood.examples.shirotest; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.config.IniSecurityManagerFactory; import org.apache.shiro.subject.Subject; import org.apache.shiro.util.Factory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class ShiroTest { private static Logger logger = LoggerFactory.getLogger(ShiroTest.class); public static void main(String[] args) { // Using the IniSecurityManagerFactory, which will use the an INI file // as the security file. Factory<org.apache.shiro.mgt.SecurityManager> factory = new IniSecurityManagerFactory("auth.ini"); // Setting up the SecurityManager... org.apache.shiro.mgt.SecurityManager securityManager = factory.getInstance(); SecurityUtils.setSecurityManager(securityManager); Subject user = SecurityUtils.getSubject(); logger.info("User is authenticated: " + user.isAuthenticated()); } }
Once you add this code, create a file called auth.ini. For now, this file is blank; it exists only so that you can run the example here to check that the code is working correctly.
After you create the file, run your sample. You should see output that contains an INFO logging message, which reports that the user is not logged in.
The SecurityUtils
object is a singleton, which means
that different objects can use it to gain access to the current user. Once you
successfully set the SecurityManager
, you
can call SecurityUtils.getSubject()
in different
parts of your application to get the current user's information.
User tokens
In Shiro terms, a token is a key that you use to log in to a system. A basic
and common token is the UsernamePasswordToken
,
which lets you specify a name and password for a user.
The UsernamePasswordToken
class implements the
AuthenticationToken
interface, which provides a way
to get credentials and the user's principal (the account identity). The
UsernamePasswordToken
works for most applications,
and you can also extend the AuthenticationToken
interface
as needed to include your own method of getting credentials. For example, you can extend
the interface to provide the contents of a key
file that your application uses to authenticate users.
Simple authentication
So far, this simple example has covered starting the Shiro
SecurityManager
, getting the current user, and logging
that the user isn't authenticated yet. This example will use the
UsernamePasswordToken
, along with a user record stored
in an INI file, to show authentication through a user name and
password.
The example auth.ini file shown in Listing 3 now contains a record for the user; this record contains both the user name and password. You can define roles in this record as well provide authorization for your application.
Listing 3. The auth.ini file
[users] bjangles = dance
Now, create the UsernamePasswordToken
object introduced
in the previous section, as shown in Listing 4.
Listing 4. Using the UsernamePasswordToken class
// snipped... same as before. public class ShiroTest { private static Logger logger = LoggerFactory.getLogger(ShiroTest.class); public static void main(String[] args) { // Using the IniSecurityManagerFactory, which will use the an INI file // as the security file. Factory<org.apache.shiro.mgt.SecurityManager> factory = new IniSecurityManagerFactory("auth.ini"); // Setting up the SecurityManager... org.apache.shiro.mgt.SecurityManager securityManager = factory.getInstance(); SecurityUtils.setSecurityManager(securityManager); Subject user = SecurityUtils.getSubject(); logger.info("User is authenticated: " + user.isAuthenticated()); UsernamePasswordToken token = new UsernamePasswordToken("bjangles", "dance"); user.login(token); logger.info("User is authenticated: " + user.isAuthenticated()); } }
The UsernamePasswordToken
object is instantiated
with the user name and password combination. Then the token is passed into the
Subject
class' login()
method.
Run the example again. Notice that the logging message now reports that the user is authenticated.
To make sure that the code is working properly and that you aren't getting a false positive,
change either the password in the code or the INI file and run the example again.
The login()
method now throws an
IncorrectCredentialsException
. This exception will
be caught specifically in your production code so that your application can respond
graciously when a user supplies an incorrect password.
If the user is incorrect, the login()
method throws an
UnknownAccountException
. You might want to consider
handling this exception but opt not to give the user too much information. One common
practice is to deny the user any indication that the user name is valid but that the password is incorrect. If someone is attempting to gain
access by guessing, you don't want to give that person hints that the guessed user name
is correct.
Authentication with LDAP
LDAP is a protocol for querying a directory over TCP/IP. These directories can hold any amount of information about a user, including a user ID, contact information, and group membership, among other things. LDAP directories are useful for corporate address books and are widely used.
AD DS, which is a common directory used for user and group management, supports LDAP.
Shiro does not include a generic LDAP security realm, but it does include an
ActiveDirectoryRealm
object that lets you authenticate users against LDAP. This example uses the
ActiveDirectoryRealm
object configured in an INI file
to authenticate users. Though AD DS is not the same thing as LDAP, there is no
generic LDAP object that comes with the version of Shiro used in this article.
Getting an LDAP server to test this sample against can be considerably more work than writing and running the example itself. If you don't have access to an AD DS server, consider downloading and installing Apache Directory to provide a sample LDAP server implementation. Apache Directory is written in the Java language. Similarly, Apache Active Directory Studio is an Eclipse plug-in that lets you browse the LDAP data. It also comes with some sample data, which provides a quick way for you to write code against known values without wondering whether any problems you encounter are code or data problems.
Listing 5 shows the code used to authenticate a user stored in Apache Directory.
Listing 5. Authenticating against LDAP
// snipped... public class ShiroLDAPTest { private static Logger logger = LoggerFactory.getLogger(ShiroLDAPTest.class); /** * @param args */ public static void main(String[] args) { // Using the IniSecurityManagerFactory, which will use the an INI file // as the security file. Factory<org.apache.shiro.mgt.SecurityManager> factory = new IniSecurityManagerFactory("actived.ini"); // Setting up the SecurityManager... org.apache.shiro.mgt.SecurityManager securityManager = factory.getInstance(); SecurityUtils.setSecurityManager(securityManager); Subject user = SecurityUtils.getSubject(); logger.info("User is authenticated: " + user.isAuthenticated()); UsernamePasswordToken token = new UsernamePasswordToken( "cn=Cornelius Buckley,ou=people,o=sevenSeas", "argh"); user.login(token); logger.info("User is authenticated: " + user.isAuthenticated()); } }
Aside from the name of the INI file and the user name and password, the code is identical to that used to authenticate a user using the records in an INI file. This similarity results because you can configure Shiro using the INI file. The INI records used to set up Shiro for authentication against Apache Directory are shown in Listing 6.
Listing 6. The actived.ini file
[main] activeDirectoryRealm = org.apache.shiro.realm.activedirectory.ActiveDirectoryRealm activeDirectoryRealm.systemUsername = uid=admin,ou=system activeDirectoryRealm.systemPassword = secret activeDirectoryRealm.searchBase = o=sevenSeas,ou=people activeDirectoryRealm.url = ldap://localhost:10389
Note: I used Apache Directory Studio to change the user's password to a value that I could put into the test code to make sure it worked.
Running a Grails web application
You can apply Shiro to two basic techniques in a web application. First, you can simply use the API to incorporate a version of the code presented here in a base servlet. Second, you can use the HTTP filters that come with Shiro. This example demonstrates the second technique because using filters takes advantage of built-in web application server technology and pre-written code from the Shiro project.
This example shows how to use the filters in Grails. Grails is a project aimed at allowing you to write Groovy web applications as quickly as possible, using a convention-over-configuration approach. See Related topics for more information on Grails.
You would usually add the necessary filter entries to the web.xml file for Shiro's filters manually. However, Grails generates the web.xml file each time you start your application, so it isn't necessary to modify a web.xml file by hand.
Fortunately, Grails supports plug-ins that hook into the web.xml generation process and let you be involved in writing the entries in the web.xml file. Many plug-ins are already available for Grails, including one for Shiro. Try the Shiro Grails plug-in, which provides a few new scripts that you can run to create different realms and controllers.
Alternatively, you can write your own plug-in if you prefer to add the entries and do the configuration yourself. With Grails, writing a new plug-in is easy. To create a Grails plug-in that adds the necessary Shiro filter entries to your web.xml file, begin by using the following command:
> grails create-plugin ShiroWebXml
Once you create the plug-in project, edit the ShiroWebXmlPlugin.groovy file, and add the code shown in Listing 7.
Listing 7. The sample plug-in
class ShiroWebXmlPlugin { // snipped plugin details... def doWithWebDescriptor = { xml -> def filterElement = xml.'filter' def lastFilter = filterElement[filterElement.size() - 1] lastFilter + { 'filter' { 'filter-name'("ShiroFilter") 'filter-class'("org.apache.shiro.web.servlet.IniShiroFilter") 'init-param' { 'param-name'("config") 'param-value'("\n#config") } } } def filterMappingElement = xml.'filter-mapping' def lastFilterMappingElement = filterMappingElement[filterMappingElement.size() - 1] lastFilterMappingElement + { 'filter-mapping' { 'filter-name'("ShiroFilter") 'url-pattern'("/*") } } } }
This code runs when Grails executes the web.xml file.
Before starting the plug-in application to test it, copy the Shiro JAR files that you downloaded with Ivy into the lib folder of the plug-in. With the JAR files in place, test whether the plug-in works with the following command:
grails run-app
If the plug-in application starts successfully, you can package it for use in an example project. To package the plug-in, use the following command:
grails package-plugin
You can install the new ShiroWebXmlPlugin
with the following command:
cd myapp grails install-plugin /path/to/shiro-web-xml-1.0.zip
Troubleshooting
If you get an UnavailableSecurityManagerException
, then the SecurityManager
has not been properly
set up. Make sure that it is set on the SecurityUtils
object before calling the getSubject()
method.
Connecting to an LDAP server can be difficult. If you get a
javax.naming.CommunicationException
,
verify the host name and port of the LDAP server. Even if you don't use Apache
Directory, Apache Directory Studio (which you can install separately) can
help you troubleshoot connection problems and issues with names.
If you didn't use the Ant Ivy script to initialize your environment, you might get errors
about missing classes. The INI file example runs even without the Apache Commons
Logging library (commons-logging
), but running the LDAP
example results in an exception. Use Apace Commons BeanUtils
to parse the INI file and set values on objects.
Conclusion
Shiro is a framework in the Apache Incubator that lets you add authentication and authorization to your applications. It supports different authentication stores, such as LDAP, Kerberos, and AD DS. The combination of Shiro's minimal dependencies and its relative ease of configuration makes it a good option as a security framework in your application.
Downloadable resources
Related topics
- Learn more about Apache Ivy.
- Explore Apache Shiro further.
- Learn more about Apache Directory and Apache Directory Studio.
- Learn more about Grails.
- Try the Grails Shiro plug-in.
- Download pre-built Shiro binaries.
- Download IBM product evaluation versions or explore the online trials in the IBM SOA Sandbox and get your hands on application development tools and middleware products from DB2®, Lotus®, Rational®, Tivoli®, and WebSphere®.