HTTP Authentication and Security with Apache Shiro

18 Apr, 2011
Xebia Background Header Wave
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.
 Read this article in french

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:
  1. The server receives a request for a secured resource;
  2. The server sends a status code 401 and puts the header WWW-Authenticate: Basic;
  3. The client repeats its request with the header Authorization: Basic login:password encoding login:password in base64;
  4. The server decodes base64 and gives access to the resource or returns to step 2.
Digest Authentication, more secured --- it uses an md5 encoding to prove its knowledge of the password, without giving it ---, is done this way:
  1. The server receives a request for a secured resource;
  2. 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;
  3. The client repeats its request with headers Authorization: Digest username="..", nonce="..", response="..";
  4. 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).
The computing of the digest have been improved by the RFC 2617, but both are valid.

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></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:
  1. The first one verifies that, without any value in the HTTP header, a 401 error is returned;
  2. The second one verifies that, with a correct user, the authentication works;
  3. The last one verifies that, with the correct role, the authorization works.
[java] import static com.sun.jersey.api.client.ClientResponse.Status.OK; import static com.sun.jersey.api.client.ClientResponse.Status.UNAUTHORIZED; public class SafeResourceTest { [...] @Test public void authentication_should_failed_without_credential() { assertEquals(UNAUTHORIZED.getStatusCode(), resource().getStatus()); } @Test public void authentication_should_succeed_with_credential() { ClientResponse response = resource("pierre", "neerg"); assertEquals(OK.getStatusCode(), response.getStatus()); assertEquals("authenticated", response.getEntity(String.class)); } @Test public void authozisation_should_succeed_with_role() { ClientResponse response = resource("paul", "eulb"); assertEquals(OK.getStatusCode(), response.getStatus()); assertEquals("authorized", response.getEntity(String.class)); } private static final String RESOURCE_URL = ""; private ClientResponse resource() { return Client.create().resource(RESOURCE_URL).get(ClientResponse.class); } private ClientResponse resource(String user, String pass) { WebResource resource = Client.create().resource(RESOURCE_URL); resource.addFilter(new HTTPBasicAuthFilter(user, pass)); return resource.get(ClientResponse.class); } } [/java]

Realm and Matcher for the authentication

Now that tests are created, it's time to focus on the authentication and authorization logic. For the purpose of this article, our Realm, StaticRealm, uses static datas defined in a static class as following: [java] import; import org.apache.shiro.crypto.hash.Sha256Hash; public class Safe { static Map<String, String> passwords = new HashMap<String, String>(); static HashMultimap<String, String> roles = HashMultimap.create(); static{ passwords.put("pierre", encrypt("green")); passwords.put("paul", encrypt("blue")); roles.put("paul", "vip"); } private String encrypt(String password) { return new Sha256Hash(password).toString(); } public static String getPassword(String username) { return passwords.get(username); } public static Set<String> getRoles(String username) { return roles.get(username); } } [/java] For the record, a couple Realm/Matcher is in charge of the authentication (see the configuration file above); the first one accesses the referential and gives the true password to the second who verifies if it matches the provided. When authentication fails, an AuthentificationException is thrown. [java] public class StaticRealm extends AuthorizingRealm { @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { UsernamePasswordToken upToken = (UsernamePasswordToken) token; String username = upToken.getUsername(); checkNotNull(username, "Null usernames are not allowed by this realm."); String password = Safe.getPassword(username); checkNotNull(password, "No account found for user [" + username + "]"); return new SimpleAuthenticationInfo(username, password.toCharArray(), getName()); } private void checkNotNull(Object reference, String message) { if (reference == null) { throw new AuthenticationException(message); } } } [/java] Using the class Safe as a referential, this Realm retrieves the user whose login is given as a parameter and gives its password to the Matcher (this separation of concerns in Shiro facilitates modularity). This Matcher checks if the given password is the reverse of the real one: [java] public class ReverseCredentialsMatcher extends SimpleCredentialsMatcher { @Override public boolean doCredentialsMatch(AuthenticationToken tok, AuthenticationInfo info) { String tokenCredentials = charArrayToString(tok.getCredentials()); String reverseToken = StringUtils.reverse(tokenCredentials); String encryptedToken = new Sha256Hash(reverseToken).toString(); String accountCredentials = charArrayToString(info.getCredentials()); return accountCredentials.equals(encryptedToken); } private String charArrayToString(Object credentials) { return new String((char[]) credentials); } } [/java] That is it. Integration tests are green. In simple cases, as ours, it is better to use (automatically) IniRealm by adding users and roles markers in the configuration file. [java] [users] # user = password, roles... pierre = ba4788b226aa8dc2e6dc74248bb9f618cfa8c959e0c26c147be48f6839a0b088 paul = 16477688c0e00699c6cfa4497a3612d7e83c532062b64b250fed8908128ed548, vip [roles] # role = permissions... [urls] /index.html = anon /profit = authcBasic, roles[admin] /stats = authcBasic, perms["stats:read"] /** = authcBasic [/java] Here, the urls marker can also be finely configured. The welcome page is accessible to everyone (anon for anonyme) and resources profit and stats are limited by roles or permissions. Please note that the declaration order is important. The last resource, **, takes only effect on resources that are not declared previously.

A powerful permission model

As it has already been said, the documentation recommends explicit roles, that means authorizing operations by permissions rather than by roles. Shiro permission model is its best asset: the permissions are described with a simple string organized like domain:action:instance (for example, printer:state:lp7200) which can be configured with wildcards (allowing rights to a set of objects/actions can be done with printer:* or *:state). This model is discussed further on the related documentation page. In our case, it is possible to use resources as a first authorization level and HTTP verbs as a second. Adding this authorization filtering to our authentication can be done this way: [java] [urls] /safe/** = authcBasic, rest[safe] [/java] The first filter look at authentication with headers, the second one allows resources authorization with permissions like resource:httpmethod (it is necessary to configure it with the resource name). To use this with the existing code, we have to modify our StaticRealm: [java] public class StaticRealm extends AuthorizingRealm { [...] @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection prins) { checkNotNull(prins, "PrincipalCollection method argument cannot be null."); String username = (String) prins.getPrimaryPrincipal(); SimpleAuthorizationInfo info = new SimpleAuthorizationInfo( Safe.getRoles(username)); info.setStringPermissions(Safe.getPermissions(username)); return info; } } [/java] And the referential must add permissions to users: [java] public class Safe { [...] static{ passwords.put("paul", encrypt("blue")); permissions.put("paul", "safe:*"); } } [/java] To keep it simple, the authorization is left to the authentification Realm. This is possible because filters accumulate roles and permissions of authenticated users. The authcBasic filter gives authenticated users to the rest filter. This one, having no Realm, only rely on its predecessor to grant access or not to a resource. So, the filter order is important. To gain authentication and authorization, a call may be valid for every filter in the chain. Furthermore, it is optional to bound Realms to filters as we have done with authcBasic. It is possible to declare them independently the following way: [java] [main] staticRealm = com.xebia.shiro.StaticRealm anotherRealm = com.xebia.shiro.AnotherRealm matcher = com.xebia.shiro.ReverseCredentialsMatcher staticRealm.credentialsMatcher = $matcher securityManager.realms = $staticRealm, $anotherRealm strategy = org.apache.shiro.authc.pam.FirstSuccessfulStrategy securityManager.authenticator.authenticationStrategy = $strategy [urls] /safe/** = authcBasic, rest[safe] [/java] The above strategy returns the user roles and permissions as soon as a Realm matches. Only those rights are used, they are accumulated with the following Realms rights as it used to be in the default configuration (AtLeastOneSuccessfulStrategy).

Authorize with annotations

The absence of integration of Shiro's AspectJ annotations @RequiresRoles and @RequiresPermissions --- though well documented --- in web environment should lead to use, in our case, Jersey's integrated security. As it has already been said, it uses Jaas; it is configured with marks security-constraint in the web.xml with detailled roles. The class RolesAllowedResourceFilterFactory responsible for Jersey authentication can be modified in order to use @RolesAllowed in place of SecurityUtils.getSubject().hasRole(..). This can helps to simplify authorization, which, without that, can become a labyrinth of conditions. Sadly, this approach restricts the filtering to roles; no annotation is define for permissions. And because roles have to be explicit, this approach won't be recommended. That said, as it has been shown by the last paragraph, resource authorization can be configured without coding. This can help reduce the inconvenience of Shiro's annotation absence in web environment.

Shiro, a true challenger

Shiro is not defects free. Its first lacuna is its absence of a proper Digest authentication filter (hard to code it yourself). Talking about documentation, several classes miss JavaDoc which force to rely on source code; besides, the lack of a thorough documentation of all the possibilities of configuration (looking at the inheritance graph of IniShiroFilter for the SecurityManager strategy is a good example of that). Talking about code, the impossibility to have two security systems can bother (Realms can be chained in a SecurityManager, but in a simple chain), lastly, the large usage of inheritance complicate its usage. Despite is youth defects, Shiro still is a robust framework, easy to integrate to every kind of environment and effortless to adapt to a given context, even complex. The lightness of this wide-ranging security toolbox gives it a minor impact on performance (cryptography tools, not presented here, make its use effortless). Its pragmatic architecture and its integration to Spring make it a true challenger to Spring Security that deserves to be seriously considered.

Get in touch with us to learn more about the subject and related solutions

Explore related posts