Skip navigation.
Home

Row level security using Spring and Hibernate

PadlockRecently I was tasked with adding data security to an existing application. The security rules were complex, users within the same user role were supposed to be able to view and edit data differently. I decided to implement this task with a permissions based system whereby users were grouped into roles and each role had a certain set of permissions. Here's how I implemented the solution using Spring and Hibernate...

Specifications

A little background on how the client thought about application security: USERs in the system could belong to one or more ROLEs. Each ROLE had a set of data level VIEW permissions and a potentially different set of EDIT permissions (the EDIT permissions were always a subset of the VIEW permissions).

At first, the application was developed using a static set of ROLEs, with each ROLE having a set of data filters defined. The problem with that strategy, was that every time a new ROLE was added, a new set of data filters needed to be defined (and coded). The client wouldn't accept this solution because they didn't want to have to go through an entire code, test, deploy cycle everytime they decided to define a new ROLE. Furthermore, the ROLEs were kind of in flux and we didn't know when they would stabilize.

So we proposed a permissions based model and tied the application to a fixed set of permissions. Once the application was bound to permissions only, it was decoupled from USER and ROLE assignments. Ultimately, we ended up tying granted permissions to ROLEs for ease of management, even though the application could care less about the ROLE. Now only a change to the permissions set would require a full code cycle. This was pretty low risk because the business processes being handled by this application were quite mature, the permission set was already very stable.

Architectural Goals

  1. Make the DAO class implementations unaware of the filters.
  2. The filters should be externally configurable.
  3. The filters should be tied to DAO methods.

Possible Implementation Layers

After deciding upon the permissions based system, I did a little bit of research and didn't find much on the topic. Really there are two common spots used to implement row level security: the database (e.g. Oracle Virtual Private Database) or in the application tier. VPD looks pretty cool but we weren't using Oracle, so no go on that one. We could have still implemented this data security in the database as a stored procedure/function or in the application layer.

We chose the application layer because of the number of possible permutations for the filters. We had a bunch of orthogonal permissions each with their own data condition. Trying to code nasty permutations in straight procedural code is messy and error prone. The best fit for this was something that could dynamically build filters without a boatload of if/else blocks. Stored procedures were out, application layer code was the answer for me. I immediately thought of the Criteria API in Hibernate as a good fit.

Application Implementation

Now that I had decided to do this at the code layer, I needed to figure out exactly how I was going to implement this solution. A couple of things came to mind:

  • an aspect style, wrapper implementation
  • a brute force style, inject code into the DAO methods style

The wrapper implementation seemed to be on par with my architectural goals, so I decided to research that option a little further (there really wasn't much to research on the brute force style). After reading Rick Hightower's article-- a blueprint for row level security using Hibernate filters, I had a decent blueprint with which to work.

Implementation

Okay first things first, I needed a a way to intercept certain method calls in order to generate the dynamic filters (based upon a User's granted permissions). What I wanted to emulate was how spring transactions were configured in our manager layer:


<bean id="txProxyTemplate" abstract="true"
    class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
    <property name="transactionManager" ref="transactionManager"/>
    <property name="transactionAttributes">
        <props>
            <prop key="save*">PROPAGATION_REQUIRED</prop>
            <prop key="remove*">PROPAGATION_REQUIRED</prop>
            <prop key="*">readOnly</prop>
        </props>
    </property>
</bean>
<bean id="doSomeWorkManager" parent="txProxyTemplate">
    <property name="target">
        <bean class="com.mattfleming.service.impl.WorkeManagerImpl" autowire="byName"/>
    </property>
</bean>

In the list above, any method starting with save or remove would require a transaction, everything else no transaction required. Out of this was born the RowLevelFilter and PermissionPrefixRowLevelFilterMethodInterceptor classes. Here are the full implementations of those classes:

RowLevelFilter


package com.mattfleming.security;

import java.util.List;

/**
 * Something which filters data at a row level.
 */
public interface RowLevelFilter {

    /**
     * Do everything necessary to apply the filter here.  If you need other things
     * to accomplish this task, make sure to set up the spring config files so that
     * your implementation has access to these other resources.
     *
     * @param keys a List of keys to be passed to the filter
     * @param arguments arguments passed to the filtered method
     */
    void prepare(List<String> keys, Object[] arguments);
}

PermissionPrefixRowLevelFilterMethodInterceptor


package com.mattfleming.security;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.util.PatternMatchUtils;
import org.springframework.util.StringUtils;

import java.lang.reflect.Method;
import java.util.*;

/**
 * This class allows you to wrap a bean so that a RowLevelFilter will be invoked if
 * the method name matches the bean configuration file.  The easiest way to use
 * this bean is by configuring it as an abstract parent. For example,
 *
 *  <bean id="rowLevelSecurityProxyTemplate" abstract="true"
 *         class="com.mattfleming.security.RowLevelFilterProxyFactoryBean">
 *       <property name="methodToPermissionPrefix">
 *           <props>
 *               <prop key="save*">EDIT</prop>
 *               <prop key="remove*">EDIT</prop>
 *               <prop key="*">VIEW</prop>
 *           </props>
 *       </property>
 *   </bean>
 *   <bean id="workDao" parent="rowLevelSecurityProxyTemplate">
 *       <property name="target">
 *           <bean class="com.mattfleming.dao.hibernate.WorkDaoHibernate" autowire="byName"/>
 *       </property>
 *       <property name="filter">
 *           <bean class="com.mattfleming.dao.hibernate.WorkSecurityFilter" autowire="byName"/>
 *       </property>
 *   </bean>
 *
 * In the example above, all methods named save* and remove* will invoke the prepare(List<String> keys) method
 * on the specified filter (WorkSecurityFilter) and pass the keys specified in the
 * prop definition.  If you want multiple keys to be passed, they should be comma separated and the keys
 * cannot contain spaces. The method name patterns are inforced via the PatternMatchUtils class which currently
 * supports the following simple pattern styles: "xxx*", "*xxx" and "*xxx*" matches, as well as direct equality.
 * @see org.springframework.util.PatternMatchUtils#simpleMatch(String, String)
 */
public class PermissionPrefixRowLevelFilterMethodInterceptor implements MethodInterceptor {
    private Map<String, List<String>> methodToPermissionPrefixMap = new HashMap<String, List<String>>();
    private RowLevelFilter filter;

    public Object invoke(MethodInvocation methodInvocation) throws Throwable {
        //check to see if method should be intercepted
        List<String> permissionPrefixes = getPermissionPrefixes(methodInvocation.getMethod());
        if (permissionPrefixes != null){
            //apply RowLevel filters
            filter.prepare(permissionPrefixes, methodInvocation.getArguments());
        }
        return methodInvocation.proceed();
    }

    public void setMethodToPermissionPrefix(Properties methodToPermissionPrefix) {
        for (Object o : methodToPermissionPrefix.keySet()) {
            String methodName = (String) o;
            String value = methodToPermissionPrefix.getProperty(methodName);
            List<String> prefixes = methodToPermissionPrefixMap.get(methodName);
            if (prefixes == null) {
                prefixes = new ArrayList<String>();
            }
            String[] tokens = StringUtils.commaDelimitedListToStringArray(value);
            for (String token : tokens) {
                // Trim leading and trailing whitespace.
                token = StringUtils.trimWhitespace(token.trim());
                prefixes.add(token);
            }
            methodToPermissionPrefixMap.put(methodName, prefixes);
        }
    }

    private List<String> getPermissionPrefixes(Method method) {
        // look for direct name match
        String methodName = method.getName();
        List<String> prefixes = this.methodToPermissionPrefixMap.get(methodName);
        if (prefixes == null) {
            // Look for most specific name match.
            String bestNameMatch = null;
            Set<String> keys = this.methodToPermissionPrefixMap.keySet();
            for (String mappedName : keys) {
                boolean matches = PatternMatchUtils.simpleMatch(mappedName, methodName);
                if (matches &&
                        (bestNameMatch == null || bestNameMatch.length() <= mappedName.length())) {
                    prefixes = this.methodToPermissionPrefixMap.get(mappedName);
                    bestNameMatch = mappedName;
                }
            }
        }
        return prefixes;
    }

    public void setFilter(RowLevelFilter filter) {
        this.filter = filter;
    }

}

So every method call to a bean using the rowLevelSecurityProxyTemplate as its parent would be evaluated. If a method name matched the configuration, the appropriate filter would be invoked and (in our case) generate a dynamic hibernate Criterion but you could really do whatever you wanted to. To achieve full invisibility to the DAO layer, I would have enabled Hibernate session filters here.

RowLevelFilter Implementation

I really wanted to make use of Hibernate session filters to add row level security. If I were able to use Hibernate filters, any query using that session would have the filters applied (magically). What's great about that is that the DAO classes don't have to know about the filters at all.

But I was unable to do this because of the dynamic nature of the rule set. Hibernate filters are static predicates that take parameters. When more than one filter is defined, the conjunction of the two filters is executed (via an SQL AND). In my case, the data filters were really a bunch of disjunctions (OR clauses) with some conjunctions as well. Every disjunction in my rule set, became another permutation of a static filter-- the number of static filters gets pretty large with only a few disjunctions. What's worse, is that even if I defined all of the filters, I would still have to write code to determine which filter to enable on the session. In this case, Hibernate filters were out.

The goal of invisibility was not going to be met this time. I could still try to minimize the dependencies between the Interceptor and the DAO implementations though. Things to look for in the RowLevelFilter implementation below are how the filter gets passed down to my DAO layer and how the filter key gets turned into an application permission. The below class is not the full implementation but it should be enough for you to get the idea..


package com.mattfleming.dao.hibernate;

public class WorkSecurityFilter implements RowLevelFilter {

    private Map<UsageIntentEnum, Criterion> passedFilters;

    /**
     * Enforce data access permissions for  viewing and modification
     *
     * @param keys to indicate the user's intent
     * @param arguments that are passed to the method being invoked
     */
    public void prepare(List<String> keys, Object[] arguments) {
        for (String key : keys) {
            UsageIntentEnum action = UsageIntentEnum.valueOf(key);
            if (action != null) {
                passedFilters.put(action, createCriteria(action));
            }
        }
    } 

   /**
     * Create the data filter necessary to view. Here is the clause's pseudocode:
     * WHERE (
     * (createdByMe AND statusAndInvolvementPermissions) OR
     * (statusAndInvolvmentPermissions AND (whichResidents OR whichAgencies))
     * )
     *
     * @param action which permission set to use
     * @return Criteria if a filter exists or null
     */
    private Criterion createCriteria(UirUsageIntentEnum action) {
        String username = ((UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getUsername();
        Disjunction ret = Restrictions.disjunction();
        if (!parsePermissions(MessageFormat.format("PERM_WORK.{0}.CREATED-BY-ANYONE.ALL-STATUS.ALL-", action)).isEmpty()) {
            return ret;
        }
        Criterion createdByMe = createCreatedByMeCriterion(username, action);
        Criterion createdByOthers = createCreatedByOthersCriterion(username, action);
        if (createdByMe == null && createdByOthers == null) {
            ret = null;
        } else {
            if (createdByMe != null) {
                ret.add(createdByMe);
            }
            if (createdByOthers != null) {
                ret.add(createdByOthers);
            }
        }
        if (log.isDebugEnabled()) {
            log.debug("Row Level Data Filter created for " + username + " is: " + ret);
        }
        return ret;
    }

    public enum UsageIntentEnum {
        VIEW, EDIT
    }
}

The UsageIntentEnum has the same values that are in our bean definition for the rowLevelSecurityProxyTemplate's methodToPermissionPrefix property. These same values are then embedded into our application permissions are also used as the key to a passedFilters Map. This Map is the vehicle by which the Criterion created in the filter, are passed to the DAO classes.

DAO Implementation

In the DAO implementations you make use of the passedFilters like this..


public class WorkDaoHibernate extends BaseDaoHibernate implements WorkDao {

    private Map<UsageIntentEnum, Criterion> passedFilters;

    public void setPassedFilters(Map<UsageIntentEnum, Criterion> passedFilters) {
        this.passedFilters = passedFilters;
    }

    private Criteria getCriteria(UsageIntentEnum intent) {
        Criterion restrictions = passedFilters.get(intent);
        if (restrictions != null) {
            Criteria filter = getSession().createCriteria(Work.class);
            filter.add(restrictions);
            return filter;
        } else {
            throw new AccessDeniedException("Security filters are not properly enabled.");
        }
    }
}

The passedFilters are the same Map that was created in the RowLevelFilter implementation. Spring is the glue that ties this all together though; it is in the Spring configuration file where the filter is tied to both the RowLevelFilter and the DAO implementation.

Spring Configuration

Here is where we tie it all together. The xml file below:

  • Defines the interceptor (MethodInterceptor implementation).
  • Defines the security filter (RowLevelFilter implementation).
  • Defines the DAO implementation and hooks it to the interceptor.
  • Defines the means to pass the filters to the DAO implementation (passedFilters).

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd">

    <bean id="rowLevelSecurityProxyTemplate" abstract="true"
          class="com.mattfleming.security.RowLevelFilterProxyFactoryBean">
        <property name="methodToPermissionPrefix">
            <props>
                <prop key="save*">EDIT</prop>
                <prop key="remove*">EDIT</prop>
                <prop key="*">VIEW, EDIT</prop>
            </props>
        </property>
    </bean>

    <bean id="workSecurityFilter" class="com.mattfleming.dao.hibernate.WorkSecurityFilter" autowire="byName"/>

    <bean id="workDao" parent="rowLevelSecurityProxyTemplate">
        <property name="target">
            <bean class="com.mattfleming.dao.hibernate.WorkDaoHibernate" autowire="byName"/>
        </property>
        <property name="filter" ref="workSecurityFilter"/>
    </bean>

    <bean id="passedFilters" class="java.util.HashMap" scope="request">
        <aop:scoped-proxy/>
    </bean>
</beans>

Since the filters are specific to the user making the request, we wouldn't want to have multiple requests sharing the same filters. So how can we guarantee that the passedFilters are unique for each and every request? That's where Spring helps us out... Notice that passedFilters bean is defined as a request scoped bean. This means that on each request a new Map will be created. In order to use the aop tag (necessary for the request scope), you will need to use Spring's xsd in the beans declaration instead of the dtd.

Conclusion

I'm pretty happy with the way the row level filter generation turned out. The only thing that would be better would be if we didn't have to pass the filters to the DAO layer and make the DAO layer (or any layer for that matter) have any knowledge of them. The original goal was to invisibly (from the DAOs point of view) add row level security and I didn't quite get all the way there. If Hibernate started to support dynamic filters, I could achieve the goal easily. There are other ways to do this, but I like the localization of the solution; the filters are defined in one spot, configured easily, and the DAO classes are the only ones dealing with them.

Missing class in your source code

Hi,

I'm studying your very interesting post and I'm trying to implement it in an application, that I develop with Spring and Hibernate. It seems the class com.mattfleming.security.RowLevelFilterProxyFactoryBean referenced in the bean rowLevelSecurityProxyTemplate is missing, and it is difficult for me to re-implement it. Could you please provide the source code of this class ? It should be a great help for me !

Thank you

Christophe

Matt Fleming's picture

RowLevelFilterProxyFactoryBean

Sorry about that, I forgot to put it in the original article.

package com.mattfleming.security;
 
import org.springframework.aop.framework.AbstractSingletonProxyFactoryBean;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.aop.Pointcut;
 
import java.util.Properties;
 
/**
 * Stub class that wraps the PermissionPrefixRowLevelFilterMethodInterceptor.  This exists so
 * we can have easy spring configuration for row level filtering.
 */
public class RowLevelFilterProxyFactoryBean extends AbstractSingletonProxyFactoryBean {
 
    private PermissionPrefixRowLevelFilterMethodInterceptor permissionsInterceptor = new PermissionPrefixRowLevelFilterMethodInterceptor();
 
    public void setMethodToPermissionPrefix(Properties methodToPermissionPrefix) {
        permissionsInterceptor.setMethodToPermissionPrefix(methodToPermissionPrefix);
    }
 
    public void setFilter(RowLevelFilter filter) {
        permissionsInterceptor.setFilter(filter);
    }
 
    protected Object createMainInterceptor() {
        return new DefaultPointcutAdvisor(Pointcut.TRUE, permissionsInterceptor);
    }
}

Missing methods required to understand better.

Hi,

The article is really good. It would be more helpful, if we can get the code for the following methods:
'createCreatedByMeCriterion()','createCreatedByOthersCriterion()' and 'parsePermissions()' of 'WorkSecurityFilter.java'. to understand the things better.

Thanks a lot,
KS.

We are migrating our

We are migrating our application from WAS 5.1 to WAS6.1 , my application uses hibernate 2.1.7 , Do we have to change something in our application?
Thank you!!
_____________________________

Omidiu part of Traduceri team

Matt Fleming's picture

I doubt it...

I doubt it but nothing that testing wouldn't find.

-Matt