Thursday, April 26, 2012

An alternative to Spring Security ACL

Spring Security ACL might be good, but the drawback is obvious too:
1. You need to save permission along with saving each business object
2. Need annotation on each method
3. Once you decide to change the permission setup, there is huge work to modify production data; and it is problematic and dangerous
4. You probably have the permission structure in place already. For example, Contract is created by UserA, so only UserA can change it. You will save duplicate info in ACL. Why not just reuse existing structure you already have.

So, I'd like to go back to the traditional Filter. However, I also want to utilize the power of Spring Security. It actually is easy. All you need is to add some configuration below Spring Security configuration in web.xml. The trick here is DelegatingFilterProxy actually access your bean name.

    <filter>
        <filter-name>springSecurityFilterChain</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    </filter>

    <filter-mapping>
        <filter-name>springSecurityFilterChain</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    <filter>
        <filter-name>aclSecurityFilterChain</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
        <init-param>
            <param-name>targetBeanName</param-name>
            <param-value>myUserAccessFilter</param-value>
        </init-param>
    </filter>

    <filter-mapping>
        <filter-name>aclSecurityFilterChain</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

myUserAccessFilter is just a bean you defined in the Spring applicationContext.xml:

    <bean id="myUserAccessFilter" class="com.foo.MyUserAccessFilter">
        <property name="myUserSerive" ref="myUserService" />
    </bean>

You just need to implement your MyUserAccessFilter like this:

package com.foo;

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;

public class MyUserAccessFilter implements Filter{
    protected final Log logger = LogFactory.getLog(getClass());
    
    @Autowired
    private MyUserService myUserService;
    
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        try{
            User currentUser = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
            String[] contractIds = request.getParameterValues("contractId");

            checkUser(contractIds, currentUser);
            
        } catch(AccessDeniedException e){
            throw e;
        } catch(Exception ignored){
            logger.error(ignored, ignored);
        }
        
        chain.doFilter(request, response);
        
    }
}

Followers