SourceForge.net Logo

 

Acegi Portlet Integration

 

Problem

 

The biggest problem in using Acegi Security Framework out of the box in a portal environment is that Acegi is based on ServletFilter, HttpServletRequest, and HttpServletResponse constructs. Unfortunately, there is no ServletFilter like mechanism in portal environment. Moreover, portlets deal with PortletRequest and PortletResponse objects to perform their jobs. Finally, there is no URL resource like concept for portlet requests and responses.

 

Solution

 

The solution consists of two parts. The first part is configuring portal web application, in this case Liferay, to use Acegi Security for its authentication needs. Acegi will also secure HTTP requests coming to portal. By that way Acegi will guarantee that all requests arriving at portal originates from authenticated users. As a result there will always be a SecurityContext, containing a valid Authentication token for current web request.

 

The second part of the solution is to propagate that SecurityContext with valid Authentication object to individual portlet web application from portlet container that is Liferay Portal. As we stated previously, both portlet containers and portlets are typical web applications. Portals need to provide an implementation specific way to communicate with those portlet web applications. Usually there is a well known servlet configured in portlet web application, and portal calls this servlet, and inside it web requests are converted into PortletRequest objects and target portlet is invoked.

 

If we are able to intercept the point in which this communication is initiated, put Acegi SecurityContext object into HTTP request, retrieve it in that well known servlet, and finally make it available to Acegi again in portlet side, then it is possible to protect individual portlet resources, possible to run method level and domain instance level authorization features in portlet web application.

Acegi Authorization within Portlets

Protecting Portlet Resources

 

As I said at the beginning, there is no URL resource like concept in portlets. Therefore we need to devise another means to authorize incoming portlet requests. One possibility is to use portlet modes. We might intercept portlet requests before they arrive at their final portlet destination and check if currently authenticated user has access rights for the current portlet mode.

 

Spring Portlet MVC provides a mechanism to intercept portlet requests. I won’t give a detailed explanation of Spring Portlet MVC here. You may look at Spring Application Framework’s Reference Documentation for it.

 

In short, Spring Portlet MVC is built around DispatcherPortlet. DispatcherPortlet resolves incoming portlet requests to appropriate Handlers based on handler mappings defined in its WebApplicationContext.

 

Inside those handler mappings, we can define interceptors that implements org.springframework.web.portlet.HandlerInterceptor interface, which can pre or post process portlet requests. One out of the box handler mapping class is org.springframework.web.portlet.handler.PortletModeHandlerMapping by which we are able to map portlet requests to appropriate handlers based on portlet modes.

 

Spring Portlet MVC provides us with a way to map requests to already existing portlets with its org.springframework.web.portlet.mvc.PortletWrappingController class. By that way we can easily configure our already existing JSF Portlet within Spring Portlet MVC environment.

 

I have created a PortletSecurityInterceptor class which implements HandlerInterceptor interface mentioned above. PortletSecurityInterceptor is devised to be configured as a pre-processor in interceptors list of portletModeHandlerMapping bean. It expects portletMode=role* key value pairs as input parameter, and checks current portlet request’s portlet mode against them. Current user is only allowed if he/she has role which is listed in those pairs. Otherwise, request processing is cancelled, and accessDeniedPage is rendered instead.

 

      <bean id="portletSecurityInterceptor" class="org.acegisecurity.portlet.interceptors.PortletSecurityInterceptor">

            <property name="portletModeRoleMappingSource">

                  <value>

                        VIEW=ROLE_READER,ROLE_WRITER

                        EDIT=ROLE_WRITER

                  </value>

            </property>

            <property name="accessDeniedPage">

                  <value>/accessDenied.jsp</value>

            </property>

      </bean>

 

For example, in the above bean configuration users with ROLE_READER or ROLE_WRITER roles will be able to access portlet resource when current portlet mode is VIEW. On the other hand, only users with ROLE_WRITER role will be able to access portlet resource when current portlet mode is EDIT. accessDenied.jsp will be shown to other users.

 

      <bean id="portletModeHandlerMapping" class="org.springframework.web.portlet.handler.PortletModeHandlerMapping">

            <property name="interceptors">

                  <list>

                        <ref bean="portletSecurityInterceptor"/>

                  </list>

            </property>

            <property name="portletModeMap">

                  <map>

                        <entry key="view" value-ref="basicJspPortlet"/>

                        <entry key="edit" value-ref="basicJspPortlet"/>

                  </map>

            </property>

      </bean>

 

We wire our portletSecurityInterceptor into portletModeHandlerMapping. DispatcherPortlet uses this handlerMapping to resolve current portlet request to its destination. Our portletSecurityInterceptor is applied as pre-processor in this case. Appropriate requests with VIEW or EDIT mode is targeted to our basicJspPortlet.

 

      <bean id="basicJspPortlet" class="org.springframework.web.portlet.mvc.PortletWrappingController">

            <property name="portletClass">

                  <value>com.ksevindik.acegi.portlet.samples.basicjsp.BasicJspPortlet</value>

            </property>

            <property name="useSharedPortletConfig">

                  <value>false</value>

            </property>

            <property name="portletName">

                  <value>basicJspPortlet</value>

            </property>

      </bean>

 

Method Level Security

 

I will mention about how to protect service methods using Acegi Security in this section. Method level security and domain instance level security, which will be mentioned in the next section don’t require any arrangement specific to portlet environment.

 

      <bean id="methodSecurityInterceptor"

            class="org.acegisecurity.intercept.method.aopalliance.MethodSecurityInterceptor">

            <property name="validateConfigAttributes">

                  <value>false</value>

            </property>

            <property name="authenticationManager">

                  <ref bean="authenticationManager" />

            </property>

            <property name="accessDecisionManager">

                  <ref bean="accessDecisionManager" />

            </property>

            <property name="objectDefinitionSource">

                  <ref bean="objectDefinitionSource" />

            </property>

      </bean>

 

Acegi Security uses AOP proxy to wrap specified service beans managed by Spring and authorize method calls according to specified method attributes. methodSecurityInterceptor is defined for this purpose. It uses accessDecisionManager to authorize current method invocations.

 

      <bean id="autoProxyCreator"

            class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">

            <property name="beanNames">

                  <value>*Service</value>

            </property>

            <property name="exposeProxy">

                  <value>true</value>

            </property>

            <property name="proxyTargetClass">

                  <value>true</value>

            </property>

            <property name="interceptorNames">

                  <value>

aclManagementInterceptor,

methodSecurityInterceptor,

transactionInterceptor

</value>

            </property>

      </bean>

 

I use Spring’s BeanNameAutoProxyCreator to automatically wrap service beans based on specified beanNames pattern with interceptor list. You can have a look at Spring’s Reference Documentation for a detailed explanation related concepts mentioned here.

 

There are several ways to specify method attributes, such as inside bean configuration file, using Jakarta Commons Attributes, or Java Annotations if you are able to use Java 5. I prefer to use Java 5 annotations.

 

      <bean id="objectDefinitionSource"

            class="org.acegisecurity.intercept.method.MethodDefinitionAttributes">

            <property name="attributes">

                  <ref local="attributes" />

            </property>

      </bean>

 

      <bean id="attributes"

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

 

public interface LibraryService {

      @Secured({"ROLE_READER","ROLE_WRITER"})

      public List<Book> getAvailableBooks();

}

 

We define allowed roles for this method to be executed using org.acegisecurity.annotation.Secured annotation of Acegi. As you see from above sample, LibraryService.getAvailableBooks() can only be executed with users having ROLE_READER or ROLE_WRITER roles. Otherwise you will get AccessDeniedException.

 

You can test method level security out of container using Spring’s AbstractDependencyInjectionSpringContextTests class. It is enough to create a TestingAuthentication token with appropriate roles and place it into SecurityContextHolder for Acegi to perform its job. TestingAuthenticationToken is validated only by testingauthenticationProvider which is provided into authenticationManager.

 

      public void testGetAvailableBooksForRoleReader() {

            Authentication auth = new TestingAuthenticationToken(

                        "portal_user",

                        "portal_user",

                        new GrantedAuthority[] {

new GrantedAuthorityImpl("ROLE_PORTAL_USER") });

            SecurityContextHolder.getContext().setAuthentication(auth);

            try {

                  List<Book> books = libraryService.getAvailableBooks();

                  fail("Execution of method should have been failed.");

            } catch(AccessDeniedException ex) {

            } catch(Exception ex) {

fail("This exception is not expected :" + ex.getMessage());

            }

                 

      }

Domain Instance Level Security (ACLs)

 

It is possible to protect access to individual domain objects using Acegi Security Framework. It is also called as ACL (Access Control Lists) security. Access Control Lists define who (based one username or roles) can perform what operations (read, write, create, administer) on a specific domain object. It is a bit more complex part of Acegi compared with other parts we mentioned above. I will mention about only important parts of ACL Security configuration here.

 

public interface LibraryService {

      @Secured({"ROLE_READER","ROLE_WRITER","ACL_UPDATE"})

      @Transactional(propagation=Propagation.REQUIRED)

      public void updateBook(Book book);

}

 

LibraryService.updateBook method can only be executed by users with ROLE_READER or ROLE_WRITER roles. We already know that as it is covered in method level security. But there is an additional value exists in the Secured annotation; ACL_UPDATE indicates that any domain object provided as input parameter to the updateBook method should be checked against ACL entries and the operation should only be allowed if there are only appropriate rights for the current user which corresponds to that domain object.

 

<bean id="accessDecisionManager"

            class="org.acegisecurity.vote.AffirmativeBased">

 

            <property name="allowIfAllAbstainDecisions">

                  <value>false</value>

            </property>

 

            <property name="decisionVoters">

                  <list>

                        <ref bean="basicRoleVoter" />

                        <ref bean="aclReadVoter" />

                        <ref bean="aclUpdateVoter" />

                        <ref bean="aclDeleteVoter" />

                  </list>

            </property>

 

      </bean>

 

I already mentioned about accessDecisionManager in this document, but if you compare above bean configuration with the previous one, you will notice that there are additional decisionVoters defined here. Actually I had removed those from the previous one intentionally in order to simplify explanation of that section. aclReadVoter, aclUpdateVoter, and aclDeleteVoter beans are consulted during those domain instance level security checks.

 

      <bean id="aclUpdateVoter" class="org.acegisecurity.vote.BasicAclEntryVoter">

            <property name="processConfigAttribute">

                  <value>ACL_UPDATE</value>

            </property>

            <property name="aclManager">

                  <ref bean="aclManager" />

            </property>

            <property name="requirePermission">

                  <list>

                        <ref local="org.acegisecurity.acl.basic.SimpleAclEntry.ADMINISTRATION" />

                        <ref local="org.acegisecurity.acl.basic.SimpleAclEntry.WRITE" />

                  </list>

            </property>

      </bean>

 

Looking at any of those voters’ bean configuration will help us to understand what is going on. aclUpdateVoter processes only ACL_UPDATE attribute. This corresponds to what we have seen in method decleration of updateBook. It will use aclManager bean to fetch any available ACL entries for the current domain object, and if there is any ACL entry for it, they must be WRITE or ADMINISTRATION permissions corresponding to the current user’s username or his owned roles.

 

 

 

 

 

      <bean id="aclManager" class="org.acegisecurity.acl.AclProviderManager">

            <property name="providers">

                  <list>

                        <ref local="basicAclProvider" />

                  </list>

            </property>

      </bean>

     

      <bean id="basicAclProvider" class="org.acegisecurity.acl.basic.BasicAclProvider">

            <property name="basicAclDao">

                  <ref local="basicAclExtendedDao" />

            </property>

            <property name="basicAclEntryCache">

                  <ref local="basicAclEntryCache" />

            </property>

      </bean>

 

aclManager bean is configured to lookup ACL entries. Above it refers to basicAclProvider for the search. basicAclProvider looks at database for those ACL entries, and caches previously queried entries against performance issues.

After Invocation Security

 

It is also possible to protect domain objects returned from service methods. This is called as after invocation security.

 

<bean id="methodSecurityInterceptor"

            class="org.acegisecurity.intercept.method.aopalliance.MethodSecurityInterceptor">

            <property name="validateConfigAttributes">

                  <value>false</value>

            </property>

            <property name="authenticationManager">

                  <ref bean="authenticationManager" />

            </property>

            <property name="accessDecisionManager">

                  <ref bean="accessDecisionManager" />

            </property>

            <property name="afterInvocationManager">

                  <ref bean="afterInvocationManager" />

            </property>

            <property name="objectDefinitionSource">

                  <ref bean="objectDefinitionSource" />

            </property>

      </bean>

 

We define an afterInvocationManager and wire it to our methodSecurityInterceptor bean.

 

      <bean id="afterInvocationManager"

            class="org.acegisecurity.afterinvocation.AfterInvocationProviderManager">

            <property name="providers">

                  <list>

                        <ref local="afterAclRead" />

                        <ref local="afterAclCollectionRead" />

                  </list>

            </property>

      </bean>

 

      <bean id="afterAclRead"

            class="org.acegisecurity.afterinvocation.BasicAclEntryAfterInvocationProvider">

           

            <property name="aclManager">

                  <ref local="aclManager" />

            </property>

            <property name="requirePermission">

                  <list>

                        <ref local="org.acegisecurity.acl.basic.SimpleAclEntry.ADMINISTRATION" />

                        <ref local="org.acegisecurity.acl.basic.SimpleAclEntry.READ" />

                  </list>

            </property>

      </bean>

 

      <bean id="afterAclCollectionRead"

            class="org.acegisecurity.afterinvocation.BasicAclEntryAfterInvocationCollectionFilteringProvider">

           

            <property name="aclManager">

                  <ref local="aclManager" />

            </property>

            <property name="requirePermission">

                  <list>

                        <ref local="org.acegisecurity.acl.basic.SimpleAclEntry.ADMINISTRATION" />

                        <ref local="org.acegisecurity.acl.basic.SimpleAclEntry.READ" />

                  </list>

            </property>

      </bean>

 

afterInvocationManager refers to afterAclRead and afterAclCollectionRead beans to authorize any returned domain objects from service methods. afterAclRead checks if current user has READ or ADMINISTRATION permissions for the single domain object which is to be returned. If user doesn’t have enough permission it throws an AccessDeniedException. afterAclCollectionRead bean checks a Collection of domain objects and removes any object that is not allowed to read or administer.

 

public interface LibraryService {

      @Secured({"ROLE_READER","ROLE_WRITER","AFTER_ACL_COLLECTION_READ"})

      public List<Book> getAvailableBooks();

     

      @Secured({"ROLE_READER","ROLE_WRITER","AFTER_ACL_READ"})

      public Book getBook(Long id);

}

 

AFTER_ACL_COLLECTION_READ and AFTER_ACL_READ config attributes enables after invocation security for service method calls. It is also possible to test ACL security issues out of container using already mentioned Spring integration test classes.