Authenticating users is an important part of an application. Limiting the access to resources with authorization too. Spring Security is a reference in web environment. However, it is tied to the Spring technology and the size of the library --- more than 10 JAR of dependencies --- may restrain its use. Moreover, its lack of integration with Guice or the recurrent deployment of an App Engine application may exclude it. This is the opportunity to take a closer look at Apache Shiro.
- Introduction to HTTP Authentication
- Shiro servlet filter
- Secure a resource
- Test integration
- Realm and Matcher for the authentication
- A powerful permission model
- Authorize with annotations
- Shiro, a true challenger
Authentication is the process of identity verification. Identity is ordinarily represented by login:password
Authorization is the process of determining access rights to resources. Rights are ordinarily linked to the authenticated user's groups
Introduction to HTTP Authentication
JAAS --- Java Authentication and Authorization Service --- was one of the first framework to add security to Java. Its data model is now wide spread: a Subject --- the user --- is authenticated if its Principals --- its identity --- and its Credentials --- the proof of its identity --- match the authenticate referential; then it is added to several Roles which imply Permissions (operations are limited by permissions). HTTP Authentication was chosen for web environnements, its support by JAX-RS implementations and modern browsers (with an input window) made it also popular. JAX-RS uses JAAS (through a SecurityContext) but its stickiness to Java policies and its lack of modularity made security framework more popular. With time security measures can become obsolete, and facing the minor contributions to HTTP authentication, other protocols arose. OAuth, much used by popular websites, has libraries, Scribe and SocialAuth (Shiro's implementation is in progress). For simplicity sake, this article only deal with the two HTTP authentications and their integration to Shiro. Basic Authentication, the less secure --- it includes the password ---, follows this steps:- The server receives a request for a secured resource;
- The server sends a status code 401 and puts the header WWW-Authenticate: Basic;
- The client repeats its request with the header Authorization: Basic login:password encoding login:password in base64;
- The server decodes base64 and gives access to the resource or returns to step 2.
- The server receives a request for a secured resource;
- The server sends a status code 401 and puts the header WWW-Authenticate: Digest, the header realm with the application name, and date and hash the header nonce to limit the authentication offer in time;
- The client repeats its request with headers Authorization: Digest username="..", nonce="..", response="..";
- The server compares the computing md5(md5(login:realm:password):nonce:md5(httpMethod:uri)) to the header response and gives access to the resource or returns to step 2 (the password is not given by the client, the server gets it from its referential using the login).
Shiro servlet filter
Shiro --- formerly JSecurity --- is a robust framework offering authentication, authorization, cryptography and session management. It fits well in a web environnement with a servlet filter (another dependency is required). The following web.xml shows how to secure a resource: [xml] <?xml version="1.0" encoding="utf-8"?> <web-app> <filter> <filter-name>Shiro</filter-name> <filter-class> org.apache.shiro.web.servlet.IniShiroFilter </filter-class> </filter> <filter-mapping> <filter-name>Shiro</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <servlet> <servlet-name>Jersey</servlet-name> <servlet-class> com.sun.jersey.spi.container.servlet.ServletContainer </servlet-class> </servlet> <servlet-mapping> <servlet-name>Jersey</servlet-name> <url-pattern>/*</url-pattern> </servlet-mapping> </web-app> [/xml] The above configuration uses Jersey --- JAX-RS default implementation --- to declare resources and Shiro to intercept every calls. To indicate which calls are authorized/forbidden, a shiro.ini file needs to be created at the root of the classpath (this directory can be configure in the web.xml file, its content can also be placed in it; more on this). The main marker defines classes dedicated to the filtering logic. The urls marker defines addresses and filter with finer grain. [java] [main] authcBasicRealm = com.xebia.shiro.StaticRealm matcher = com.xebia.shiro.ReverseCredentialsMatcher authcBasicRealm.credentialsMatcher = $matcher [urls] /** = authcBasic [/java] The authcBasic filter is part of the default provided filters. It is configurable with the JavaBean style in the main marker (Shiro uses Apache BeanUtils for this --- primitives can be given directly, complex types have to be declared with $). Configured this way, it allows the authentication of every call to the server with the login:password found in the HTTP headers (in Basic). The resulting token is transmitted to a couple Realm/Matcher, the first one accesses the referential and gives the true password to the second who verifies if it matches the provided one. Classic Realms --- JDBC, LDAP, etc --- are provided. Matchers may be redefine when passwords are stored encrypted and need to be compared to the encryption of the provided one (further reading). When several Realms are used, the resolution logic can be specified within a strategy (all, at least one, etc).Secure a resource
Before speaking about security, it is necessary to create a resource and verify, with integration tests, that it is effectively secured. On every call to /safe, this resource checks if the user have a vip role and returns a resulting string. Because Shiro is configured with a servlet filter, the user does not reach the method if it has not correctly been authenticated. [java] import org.apache.shiro.SecurityUtils; @Path("/safe") public class SafeResource { @GET public Response get() { String state; if (SecurityUtils.getSubject().hasRole("vip")) { state = "authorized"; } else { state = "authenticated"; } return Response.ok(state).build(); } } [/java] The SafeResource class is filtered by Shiro. No authentication check is done here. The user is already authenticated when it reaches this resource method. Please note that unlike this example, the Shiro documentation highlights the importance of explicit roles, that means authorizing operations by permissions rather than by roles. That is, a role's rights are no longer directly attach to it, facilitating maintenance: when roles are added, modified or deleted, this can be done without modifying application's code; modifying the referential roles - permissions association is enough. Here is a pom with the dependencies needed for this article's example. Try to maintain them up to date. [xml] <dependencies> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> <version>1.1.0</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-web</artifactId> <version>1.1.0</version> </dependency> <dependency> <groupId>com.sun.jersey</groupId> <artifactId>jersey-server</artifactId> <version>1.6</version> </dependency> <dependency> <groupId>com.sun.jersey</groupId> <artifactId>jersey-client</artifactId> <version>1.6</version> </dependency> <dependency> <groupId>org.mortbay.jetty</groupId> <artifactId>jetty-embedded</artifactId> <version>6.1.26</version> </dependency> <dependency> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> <version>1.1.1</version> </dependency> <dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>r08</version> </dependency> </dependencies> [/xml]Test integration
Let us verify the filtering with unit tests using a Jetty-Embedded server. It is configured with the WEB-INF directory, which contains the web.xml declaring resources and Shiro filter. [java] public class SafeResourceTest { private static final String WEB_INF_DIRECTORY = "war"; Server server; @Before public void before() throws Exception { server = new Server(8080); server.addHandler(new WebAppContext(WEB_INF_DIRECTORY, "/")); server.start(); } @After public void after() throws Exception { server.stop(); } } [/java] Three integration tests can be done:- The first one verifies that, without any value in the HTTP header, a 401 error is returned;
- The second one verifies that, with a correct user, the authentication works;
- The last one verifies that, with the correct role, the authorization works.



