Posts tagged 'shiro'



Apache Shiro for authentication in Roller

Shiro logo

This is the third of my 2014 side projects that I'm sharing and one that involves the Apache Roller blog server and the Apache Shiro security framework. You might find this interesting if you're considering using Shiro for authentication and authorization, or if your interested in how security works in Apache Roller.

Inspired by my work with Ember.js in Fall 2014, I started thinking about what it would take to build an Ember.js-based editor/admin interface for Apache Roller. To do that, I'd need to add a comprehensive REST API to Roller, and I'd need a way to implement secrity for the new API. I've enjoyed working with Apache Shiro, so I decided that a good first step would be to figure out how to use Apache Shiro in Roller for Roller's existing web interface.

Working over the winter break I was able to replace Roller's existing Spring security implementation with Shiro and remove all Spring dependencies from my Rollarcus fork of Roller. Below I'll describe what I had to do get Shiro working for Form-base Authentication in Roller.

Creating a Shiro Authorizing Realm

The first step in hooking Shiro into Roller is to implement a Shiro interface called ShiroAuthorizingRealm. This interface enables Shiro to do username and password checks for users when they attempt to login, and to get the user's roles.

Below is the first part of the class, which includes the doGetAuthenticationInfo() method, which returns the AuthenticationInfo for a user specified by an AuthenticationToken that includes the user's username. In other words, this method allows Shiro to look-up a user by providing a username and get back the user's (hashed) password, so that Shiro can validate a user's username and password.

ShiroAuthorizingRealm.java (link)
public class ShiroAuthorizingRealm extends AuthorizingRealm {

    public ShiroAuthorizingRealm(){
        setName("ShiroAuthorizingRealm");
        setCredentialsMatcher(
            new HashedCredentialsMatcher(Sha1Hash.ALGORITHM_NAME));
    }

    @Override
    public AuthenticationInfo doGetAuthenticationInfo(
        AuthenticationToken authToken) throws AuthenticationException {

        UsernamePasswordToken token = (UsernamePasswordToken) authToken;

        User user;
        try {
            user = loadUserByUsername( token.getUsername() );

        } catch (WebloggerException ex) {
            throw new AuthenticationException(
                "Error looking up user " + token.getUsername(), ex);
        }

        if (user != null) {
            return new SimpleAuthenticationInfo( 
                user.getUserName(), user.getPassword(), getName());

        } else {
            throw new AuthenticationException(
                "Username not found: " + token.getUsername());
        }
    }

In the code above you can see how we pull the username out of the authToken provided by Shiro and we call a method, loadUserByUserName(), which uses Roller's Java API to load a Roller user object specified by name.

The next method of interest is doGetAuthorizationInfo(), which allows Shiro to look-up a user's Role. This allows Shiro to detmerine if the user is a Roller admin user or a blog editor.

ShiroAuthorizingRealm.java (continued)

    public AuthorizationInfo doGetAuthorizationInfo(
        PrincipalCollection principals) {

        String userName = (String)
            (principals.fromRealm(getName()).iterator().next());

        User user;
        try {
            user = loadUserByUsername( userName );
        } catch (WebloggerException ex) {
            throw new RuntimeException("Error looking up user " + userName, ex);
        }

        Weblogger roller = WebloggerFactory.getWeblogger();
        UserManager umgr = roller.getUserManager();

        if (user != null) {
            List roles;
            try {
                roles = umgr.getRoles(user);
            } catch (WebloggerException ex) {
                throw new RuntimeException(
                    "Error looking up roles for user " + userName, ex);
            }
            SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
            for ( String role : roles ) {
                info.addRole( role );
            }
            log.debug("Returning " + roles.size() 
                + " roles for user " + userName + " roles= " + roles);
            return info;

        } else {
            throw new RuntimeException("Username not found: " + userName);
        }
    }

In the code above you can see that we use the loadUserByUsername() too look-up a user by username, then we use Roller's Java API to get the user's roles. We add those roles to an instance of the Shiro class SimpleAuthorizationInfo and return it to Shir.

Creating a Shiro Authorizing Filter

Now that we've implementated a realm, we've provided Shiro with everything needed to authenticate Roller users and get access to Roller user role information. Next, we need to configure Shiro to enforce roles for the URL apths found in Roller. Shiro includes a RolesAuthorizationFilter, which is close to what we need but not exactly right for Roller. I had to extend Shiro's roles filter so that we can allow a user who has any (not all) of the required roles for a resource.

RollerRolesAuthorizationFilter.java (link)
public class RollerRolesAuthorizationFilter 
    extends RolesAuthorizationFilter {

    @Override
    public boolean isAccessAllowed( 
        ServletRequest request, 
        ServletResponse response, 
        Object mappedValue) throws IOException {

        final Subject subject = getSubject(request, response);
        final String[] roles = (String[]) mappedValue;

        if (roles == null || roles.length == 0) {
            return true;
        }

        // user is authorized if they have ANY of the roles
        for (String role : roles) {
            if (subject.hasRole(role)) {
                return true;
            }
        }
        return false;
    }
}

Configuring Shiro for Roller

Now that we've seen the Java code needed to hook Shiro into Roller, lets look at how we configure Shiro to use that code. We do that using the Shiro configuration file: shiro.ini, as shown below.

shiro.ini (link)
[main]

defaultRealm = org.apache.roller.weblogger.auth.ShiroAuthorizingRealm
securityManager.realms = $defaultRealm

cacheManager = org.apache.shiro.cache.ehcache.EhCacheManager
securityManager.cacheManager = $cacheManager

authc = org.apache.shiro.web.filter.authc.FormAuthenticationFilter
authc.loginUrl = /roller-ui/login.rol
authc.successUrl = /roller-ui/menu.rol

rollerroles = org.apache.roller.weblogger.rest.auth.RollerRolesAuthorizationFilter

[urls]

/roller-ui/login.rol          = authc
/roller-ui/login-redirect.rol = authc, rollerroles[admin,editor]
/roller-ui/profile**          = authc, rollerroles[admin,editor]
/roller-ui/createWeblog**     = authc, rollerroles[admin,editor]
/roller-ui/menu**             = authc, rollerroles[admin,editor]
/roller-ui/authoring/**       = authc, rollerroles[admin,editor]
/roller-ui/admin/**           = authc, rollerroles[admin]
/rewrite-status/**            = authc, rollerroles[admin]
/roller-services/rest/**      = authcBasic, rollerroles[admin,editor]

In the configuration file above, you see how we hook in the new ShiroAuthorizingRealm on line 3. The next couple lines are boiler-plate code to hook in Shiro's caching mechanism and then, on line 9, we configure an authentication method called authc, which is configured to use Shiro's Form Authentication feature. And, on line 13, we hook in our new RollerRolesAuthorizationFilter.

Next, we tell Shiro that the login page for Roller is /roller-ui/login.rol and which page to direct a user to on a successful login, /roller-ui/menu.rol, if the user did not specify which page they wanted to access. And finally, on lines 17-25, you see the list of Roller URL patterns that need protection, which authentication method to use (authc or authcBasic) and the authorization filter and roles required for access to the URL pattern.

Wrapping up...

That's all there is to the story of Roller and Shiro so far. I was able to get Roller's form-based authentication working with Shiro, but I did not try to test with OpenID or LDAP, so I assume more work will be necessary to get them working. I did the work in my experimental Rollarcus fork of Roller. You can get the code from the shiro_not_spring branch. Pull requests are quite welcome as are suggestions for improvement. Please let me know if you see anything wrong in the above code.

This work may not find its way into Roller proper, but it plays a part in my the next side-project that I will share: A REST API for Roller with JAX-RS.