Business Logic Layer Security and Transactions with Annotations

Posted by Tejus Parikh on May 30, 2007

Not too long again, my place of employment switched to using the Acegi framework for security. The major reasons for this switch were the large user community using Acegi and it’s officially the “Security System for Spring.” One of the loose ends that we didn’t tie up as part of the initial migration was method level security declared with Java 1.5 Annotations. Our old security system had it, but it was practically unused (since the web layer performed the same checks), so we just chucked it. Unfortunately, adding the functionality back it was not as easy as we would have liked. This post is basically an overview of how we got Acegi to use Spring AOP for method level security and still play nice with our transactional support. We already had our transactional support configured before we started implementing method level security. Like our security, we had this configured with annotations. Our pattern is to annotate our business logic interfaces. The corresponding implementation remains un-marked. This is an example of how our business interface looked before security.


@Transactional

public interface UserManager {



    User createUser(String login, String password);



    @Transactional(readOnly=true)

    User getUser(String userId);



    User deleteUser(String userId);

}

Spring’s AOP support reads the @Transactional annotation on these interfaces and intercepts the method invocation to add a transaction around it. Getting Spring to perform this dark magic is as simple as adding a few lines to your spring context xml file. First, you need to tell Spring to do it’s AOP magic.

    <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>

This bean definition tells Spring to check for potential AOP advice (ie, method interceptors) to run on a classes method at bean creation time. Anytime your bean is injected into another class, you will get a aop-aware, proxied version, instead of your vanilla concrete implementation. In order to add transactional “advice” to your proxied version, the following lines need to go into your spring context configuration.

    <bean class="org.springframework.transaction.interceptor.TransactionAttributeSourceAdvisor">

        <property name="transactionInterceptor" ref="txInterceptor"/>

    </bean>



    <bean id="txInterceptor" class="org.springframework.transaction.interceptor.TransactionInterceptor">

        <property name="transactionManager" ref="transactionManager"/>

        <property name="transactionAttributeSource">

            <bean class="org.springframework.transaction.annotation.AnnotationTransactionAttributeSource"/>

        </property>

    



    <bean id="transactionManager"

        class="org.springframework.orm.hibernate3.HibernateTransactionManager">

        <property name="sessionFactory" ref="sessionFactory" />

    </bean>

Since we use Hibernate, we use the HibernateTransactionManager. If you don’t use hibernate, you have a smorgasbord to choose from. This is what we had before we tried to add method level security. To add method level security, the first step is to realize that you can only have one AOP Proxy Creator. Therefore, blindly following the tutorials on the Spring website won’t help too much. I’m also assuming that you have a working Acegi setup that does authentication at a URL level and that you have an authentication manager bean configured. Configuring that aspect of Acegi is outside the scope of this post. We decided to use role-based authorization for our methods, so we configured a RoleVoter and an AccessDecisionManager to make decisions based on the users GrantedAuthorities. How users get granted authorities is covered in the Acegi documentation.

    <bean id='accessDecisionManager' class='org.acegisecurity.vote.AffirmativeBased'>

        <property name='decisionVoters'>

            <list><ref bean='roleVoter'/>

        </property>

    </bean>



    <bean id='roleVoter' class='org.acegisecurity.vote.RoleVoter'>

        <property name="rolePrefix" value="PERMISSION_" />

    </bean>

The standard rolePrefix is ‘ROLE_’. We just used ‘PERMISSION_’ since it maps to our egacy model more appropriately. Next, we need to configure something to read the Security annotations:

    <bean id="objectDefinitionSource" class="org.acegisecurity.intercept.method.MethodDefinitionAttributes">

      <property name="attributes">

        <bean class="org.acegisecurity.annotation.SecurityAnnotationAttributes" />

      </property>

    </bean>

    

Then we put it all together by creating a security interceptor and adding it to the chain of advisors that the proxy will call before the method invocation.

    <bean class="org.acegisecurity.intercept.method.aopalliance.MethodDefinitionSourceAdvisor">

        <constructor -arg>

            <ref bean="methodSecurityInterceptor" />

        </constructor>

    </bean>

   

    <bean id="methodSecurityInterceptor" class="org.acegisecurity.intercept.method.aopalliance.MethodSecurityInterceptor">

        <property name="validateConfigAttributes"><value>true</value>

        <property name="authenticationManager"><ref bean="authenticationManager"/>

        <property name="accessDecisionManager"><ref bean="accessDecisionManager"/>

        <property name="objectDefinitionSource"><ref bean="objectDefinitionSource"/>

    </bean> 

Finally, we can annotate our classes. If we wanted to ensure that only users with the permission to create a user (PERMISSION_UserCreate) then we need to add ‘@Secured({“PERMISSION_UserCreate”})’ to the correct method on the interface. Our final interface looks like:

@Transactional

public interface UserManager {



    @Secured({"PERMISSION_UserCreate"})

    User createUser(String login, String password);



    @Transactional(readOnly=true)

    User getUser(String userId);



    User deleteUser(String userId);

}

Viola! Users without the correct permissions will no longer be allowed to create new users. Unfortunately, things aren’t ever that simple, and we ran into one pretty major issue. A good chunk of our beans in the business logic layer didn’t get proxied. Therefore, we got neither transactions nor security on these beans. To add the confusion, the business-logic calls failed in a very odd way. Whenever we did an update or create on a persisted model object, we got a:
org.springframework.dao.InvalidDataAccessApiUsageException: Write operations are not allowed in read-only mode (FlushMode.NEVER/MANUAL): Turn your Session into FlushMode.COMMIT/AUTO or remove ‘readOnly’ marker from transaction definition.
This, of course, was totally unhelpful, since we didn’t have a readOnly marker on our transaction definition, and we were pretty sure that we were setting our FlushMode in our transaction interceptor. Removing the security annotations stuff made the problem go away again. Eventually we, realized the problem was that instead of a proxy with transaction advice and security advice, we received the configured implementation of the interface. Our interceptors hadn’t finished initializing by the time some beans were creating. The culprit turn out to be the manner in which we created our spring context files. The structure of our files was:

   <!--Transaction Stuff -->

   <!-- a lot of bean defs -->

   <!-- Security stuff -->

   <!-- Some more beans -->

Changing that to:

   <!-- Security stuff -->

   <!--Transaction Stuff -->

   <!-- a lot of bean defs -->

   <!-- Some more beans -->

made the problem go away for good. If you’re having weird issues with transactional annotations and security annotations, this would probably be a good place to look.

Tejus Parikh

I'm a software engineer that writes occasionally about building software, software culture, and tech adjacent hobbies. If you want to get in touch, send me an email at [my_first_name]@tejusparikh.com.