Introduction

In this blog, I will explain how to do role-based access control (RBAC) in a web application using Servlets, Spring IoC and Apache Shiro.

Functional Model

As shown in the above figure, the application have four users; root, guest, gandhi and bose. Each one of them have different roles. The application provides a simple web-based user interface to access a protected service (some kind of facade). The users need special privileges to access each of the operation provided by the protected service. A screenshot of the application is shown below.

Application

The key steps in implementing the application using Apache Shiro and Spring IoC are

  1. Defining the Shiro Realm
  2. Protecting the Web Application
  3. Protecting the Service

In the following sections, I will explain what is involved in doing the above three steps, what are the Maven dependencies and finally how to run the application.

Defining the Shiro Realm

The protected service provides access to four different resources. Each resource may represent certain set of entities and there could be one or more actions associated to the resource, either to access the entities managed by the resource or to simply utilize the features of the resource. The following table summarizes them.

Resource Description Actions Entities
user-roles A resource to manage users and roles in the application. read users and roles Each user in the application is an entity. Further, each role defined in the application may be considered as entity as well. Basically, users need special privileges to manage user & role data.
calculator A resource to do addition and subtraction. subtract or add two numbers none
filesystem A resource to access to the files in the server. For example, listing the files in the user home directory. read files home directory is an entity. In fact each file or directory managed by the file system is an entity
system A resource to access the system data of the server. For example, server system time. read time time is an entity. Apart from this one may consider attributes like OS name, processor type, OS version, IP Address etc., are entities

Based on the above resource definitions, a Realm for the application is defined as shown below

USERS

Username Password Roles
root secret admin
guest guest -none-
gandhi 12345 role1,role2
bose 67890 role2

ROLES

Role Permissions
admin All permissions
role1 filesystem:*
system:*
role2 calculator:add,subtract

For this application, I have used INI Realm defined in shiro.ini.

Protecting the Web Application

As I used Spring IoC, the Servlet filter and context listeners of Shiro, need to be defined as shown below in the web.xml.

<web-app version="2.5">
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    
    <listener>
        <listener-class>org.apache.shiro.web.env.EnvironmentLoaderListener</listener-class>
    </listener>    
    
    <!-- The filter-name matches name of a 'shiroFilter' bean inside applicationContext.xml -->
    <filter>
        <filter-name>shiroFilter</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
        <init-param>
            <param-name>targetFilterLifecycle</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>
    
    <filter-mapping>
        <filter-name>shiroFilter</filter-name>
        <url-pattern>/*</url-pattern>
        <dispatcher>REQUEST</dispatcher> 
        <dispatcher>FORWARD</dispatcher> 
        <dispatcher>INCLUDE</dispatcher> 
        <dispatcher>ERROR</dispatcher>
    </filter-mapping>
    ...
    ...
</web-app>

Then in the applicationContext.xml file, I had to define the Realm and configure Shiro to use it.

<beans>
    <bean id="iniRealm" class="org.apache.shiro.realm.text.IniRealm">
        <property name="resourcePath" value="classpath:/shiro.ini" />
    </bean>

    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <property name="realm" ref="iniRealm" />
    </bean>
    
    <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor" />

    <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" 
          depends-on="lifecycleBeanPostProcessor" />
    
    <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
        <property name="securityManager" ref="securityManager" />
    </bean>
    
    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <property name="securityManager" ref="securityManager" />
        <property name="loginUrl" value="/login" />
        <property name="successUrl" value="/home/" />

        <property name="filterChainDefinitions">
            <value>
                /home/** = authc
            </value>
        </property>
    </bean>
    ...
    ...
</beans>

For filterChainDefinitions, I have mapped authc filter to the URL pattern /home/**/. This configuration ensures that all URLs with pattern /home/** are protected by a form-based authentication filter. The complete list of readymade Shiro filters are listed here.

Protecting the Service

I have used Shiro Annotation API named @RequiresPermissions, to protected each method as shown below.

public class ProtectedService {
    private static final List<String> USERS = Arrays.asList("root","guest","gandhi","bose");
    
    private static final List<String> ROLES = Arrays.asList("admin","guest","role1","role2");
    
    @RequiresPermissions("user-roles:read")
    public List<String> getUsers() {
        return USERS;
    }
    
    @RequiresPermissions("user-roles:read")
    public List<String> getRoles() {
        return ROLES;
    }
    
    @RequiresPermissions("system:read:time")
    public Date getSystemTime() {
        return Calendar.getInstance().getTime();
    }
    
    @RequiresPermissions("calculator:add")
    public int sum(int a, int b) {
        return a+b;
    }
    
    @RequiresPermissions("calculator:subtract")
    public int diff(int a, int b) {
        return a-b;
    }
    
    @RequiresPermissions("filesystem:read:home")
    public List<String> getHomeFiles() {
        File homeDir = new File(System.getProperty("user.home"));
        return Arrays.asList(homeDir.list());
    }

    public String getGreetingMessage(String name) {
        return String.format("Hello %s",name);
    }
}

Then it need to be defined in the applicationContext.xml as shown below.

<beans>
    ...
    ...
    <bean id="protectedService" class="com.sangeethlabs.shiro.simplerbac.ProtectedService">
    </bean>
</beans>

Now Shiro with Spring, will take care of protecting the bean !

Maven Dependencies

The list of Maven artifacts required for using Shiro and Spring in this application are listed below.

Group Artifact Version Comments
org.apache.shiro shiro-core 1.2.1
shiro-web 1.2.1 Includes Servlet filters and context listeners to protected the web application
shiro-spring 1.2.1 As the name suggests it is required for integrating with Spring
shiro-aspectj 1.2.1 Required for Shiro annotations
org.slf4j slf4j-api 1.6.4 shiro-core needs SLF4J
slf4j-jdk14 1.6.4
org.springframework spring-context 3.1.2.RELEASE shiro-spring needs these artifacts
spring-web 3.1.2.RELEASE
commons-beanutils commons-beanutils 1.8.0 shiro-core needs this artifact
commons-lang commons-lang 2.4 shiro-aspectj needs this artifact
cglib cglib 2.2 CGLIB is required for Spring AOP. Please read this topic for more details

In order to view complete set of dependencies for this application, please refer the pom.xml.

Running the Application

In order to build and run the application, referred in this blog, follow the steps mentioned below

$ git clone https://github.com/sangeeth/apache-shiro-samples.git
$ cd apache-shiro-samples/simple-rbac
$ mvn install

Deploy the war on any servlet container and access it.

Reference

More Screenshots

Root user login

Root user accessing getUsers

Guest user accessing getUsers

Guest user accessing getGreetingMessage