JBoss.orgCommunity Documentation

Chapter 63. Spring Security Integration

63.1. Introduction
63.2. Installation
63.3. Configuration
63.3.1. JAAS disabling
63.3.2. Enabling spring security
63.3.3. security-context.xml
63.4. Login portlet example
63.4.1. Building the portlet
63.4.2. Setting up the login portal page
63.4.3. Customization of portal login and logout urls
63.4.4. A look at the login page
63.5. Integration strategies
63.5.1. Replication
63.6. Integration with eXo portal
63.7. Security context propagation to portlets
63.7.1. Portal side filter
63.7.2. Portlet side filter
63.8. Conclusion

How to Integrate the spring security framework in the eXo portal?

This tutorial will guide you through a few steps and show you how easy it is to integrate spring security (or the Spring framework in general) in eXo portal. We will create a login portlet example as a support all along the document reading. The login portlet example has been developed and deployed using the eXo WCM product running on the application server JBoss 4.2.3. But it can easily be adapted to another eXo product (such as ECM) and to other servers such as tomcat. Moreover, the example, claiming to be a real world example, is implemented using JSF 1.2, the JBoss portlet bridge and Spring and can serve as a example project from where you can start your own portlet development targeting the eXo platform.

This tutorial assumes that you have a working eXo WCM installation running under JBoss 4.2.x.

Download the spring framework: http://s3.amazonaws.com/dist.springframework.org/release/SPR/spring-framework-2.5.6-with-dependencies.zip

Download spring-security: http://sourceforge.net/project/showfiles.php?group_id=73357&package_id=270072&release_id=630203

Unzip the 02portal.war file in the jboss server/default/deploy/exoplatform.sar directory and copy the following jars in WEB-INF/lib:

  • spring.jar

  • spring-security-core.jar

  • aspectjrt-1.5.4.jar

  • exo-spring.jar (contains the filters and event handlers described in this tutorial - see the attachment section of this page)

To enable spring security in exo we need to go through a few configuration steps:

We need to configure the spring security filter chain for our purposes. Create a file named security-context.xml in 02portal.war WEB-INF directory containing the following lines:

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/security"
   xmlns:beans="http://www.springframework.org/schema/beans"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
                       http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-2.0.1.xsd">

   <http auto-config="true">
      <intercept-url pattern="/private/**" access="ROLE_USER" />   
      <form-login login-page='/public/classic/Login' default-target-url='/private/classic/home' />
   </http>
  
   <authentication-provider>
      <user-service>
         <user name="rod" password="koala" authorities="ROLE_SUPERVISOR, ROLE_USER, ROLE_TELLER" />
         <user name="root" password="exo" authorities="ROLE_USER" />
      </user-service>
   </authentication-provider>   

</beans:beans>

The file contains two elements. The http node which is responsible of configuring the filter chain. The auto-config mode set to true allows us to do just a minimal configuration, everything else being smartly initialized by default. We just set an intercept URL pointing to '/private/**' with the ROLE_USER authority which corresponds to secured resources in exo. In case of successful auhentication, the user will be redirected to the specified default target URL.

The second element defines a simple authentication provider based on the spring security InMemoryDaoImpl implementation of the UserDetailsService. Note that we define the exo root user in the configuration which will allow us to log in with admin privileges in the exo portal.

Now that we have successfully installed and configured spring security in exo, we need a login portlet example to capture user credentials and serve as an entry point in the authentication process. The login portlet itself is based on JSF 1.2, Jboss portlet bridge and the spring framework, but you can obviously use whatever web framework you want to achieve the same.

So we need a login form to capture user credentials inputs. The portlet login form consists of the following lines of xml:

<f:view xmlns:f="http://java.sun.com/jsf/core"
   xmlns:h="http://java.sun.com/jsf/html"
   xmlns:ice="http://www.icesoft.com/icefaces/component"
   xmlns:liferay-faces="http://liferay.com/tld/faces"
   xmlns:ui="http://java.sun.com/jsf/facelets"
   xmlns:c="http://java.sun.com/jstl/core"
   xmlns:fn="http://java.sun.com/jsp/jstl/functions"
   xmlns:t="http://myfaces.apache.org/tomahawk">   
          
   <style type="text/css" media="screen">   
      @import "/loginportlet/css/starter.css";
      @import "/loginportlet/css/uni-form.css";
   </style>

   <script src="/loginportlet/js/jquery.js" type="text/javascript"></script>
   <script src="/loginportlet/js/uni-form.jquery.js" type="text/javascript"></script>
       
   <h:form styleClass="uniForm" >
      <fieldset class="inlineLabels">
         <legend>Sign in</legend>       

         <div class="ctrlHolder">            
            <h:outputLabel for="login" style="width: 70px"><em>*</em>Login:</h:outputLabel>
            <h:inputText id="login" value="#{loginBean.login}" required="true" styleClass="textInput" />         
            <h:message for="login" styleClass="portlet-msg-error" />
         </div>
         <div class="ctrlHolder">        
            <h:outputLabel for="password" style="width: 70px"><em>*</em>Password:</h:outputLabel>
            <h:inputSecret id="password" value="#{loginBean.passwd}" required="true" styleClass="textInput" />
            <h:message for="password" styleClass="portlet-msg-error" />        
         </div>       
      </fieldset>
   
      <div class="buttonHolder" style="margin-top: 20px; margin-right: 20px">
         <h:commandButton styleClass="primaryAction" value="Submit" action="#{loginBean.login}" />                          
      </div>
   </h:form>
</f:view>

The interesting part resides in the backing bean which implements the login action triggered when the user clicks the login form submit button.

package org.exoplatform.loginportlet;

import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Controller;

@Controller
@Scope("request")
public class LoginBean {

    String login;

    String passwd;

    public String login() throws Exception {
        String redirect = "/portal/j_spring_security_check?j_username=" + login + "&j_password=" + passwd;
        PortalUtils.sendRedirect(redirect);       
        return null;
    }

    ...
}

The login action simply sends a HTTP redirect to the spring security login URL passing the user login and password as parameters. This URL informs the filter to try to authenticate the supplied user credentials. This is the Spring security authentication process entry point.

Now that we have a login portlet available we need to set it up into a portal page.

In the portal header, there is a login or logout action displayed depending whether you are already logged in or not. We need to customize those actions so that when the user clicks on it she or he will be redirected either to our login page or to the spring security logout url. Edit the article, go to the default.js tab and apply the following changes to the code:

function validateUser() {

    var user = eXo.env.portal.userName;
    var rootObj = document.getElementById("classic-access");
    var loginContentObj = eXo.core.DOMUtil.findFirstDescendantByClass(rootObj, "div", "UIWCMLoginPortlet");
    var welcomeObj = eXo.core.DOMUtil.findFirstDescendantByClass(rootObj, "span", "Welcome");
    var userObj = eXo.core.DOMUtil.findFirstDescendantByClass(rootObj, "span", "LoggedUser");
    var languageObj = eXo.core.DOMUtil.findFirstDescendantByClass(rootObj, "a", "LanguageIcon");
    var logXXXObj = eXo.core.DOMUtil.findPreviousElementByTagName(languageObj, "a");

    if (user != "null") {
        welcomeObj.innerHTML = "Welcome: ";       
        userObj.innerHTML = user;
        logXXXObj.innerHTML = "Logout";
        if (eXo.core.DOMUtil.hasClass(logXXXObj, "LoginIcon")) {
            eXo.core.DOMUtil.removeClass(logXXXObj, "LoginIcon");
            eXo.core.DOMUtil.addClass(logXXXObj, "LogoutIcon");
        }
        logXXXObj.onclick = function() { document.location.href = '/portal/j_spring_security_logout' }
    } else {
        if (eXo.core.DOMUtil.hasClass(logXXXObj, "LogoutIcon")) {
            eXo.core.DOMUtil.removeClass(logXXXObj, "LogoutIcon");
            eXo.core.DOMUtil.addClass(logXXXObj, "LoginIcon");
        }
        logXXXObj.innerHTML = "Login";
        logXXXObj.onclick = function() { document.location.href = '/portal/public/classic/Login' };
    }

    languageObj.onclick = function () { if(document.getElementById('UIMaskWorkspace')) ajaxGet(eXo.env.server.createPortalURL('UIPortal', 'ChangeLanguage', true)); }
}

eXo.core.Browser.addOnLoadCallback("validateUser", validateUser);

As you can see, the two onclick event handler function bodies have been changed to a simple redirect to the login page or the logout URL.

Until now we haven't discussed about any integration strategies concerning a potential existing security realm outside of the eXo platform. To address this problem we have the choice between at least two different strategies:

1.1.1 Direct integration We can directly integrate eXo with the external realm. Everything related to organisation and user management in exo is cleanly separated in its own abstraction accessible through the OrganisationService. The authentication process itself is encapsulated in the Authenticator abstraction which sits on top of the organization service. eXo provides several implementations of both. So whether your realm is based on LDAP or JDBC and because the default implementations are generic enough, you will be able to use them and fits them to your needs with a matter of a little configuration. You can even develop a custom implementation to meet your more specific needs.

Being successfully authenticated against an external realm is not sufficient by itself. We also need to propagate the newly created security context to the portal own security mechanism. In eXo portal terminology, it means we have to create an Identity object for the user and register it into the Identity Registry.

Spring framework provides a simple notification model where a bean can listen to application events published by other beans. Fortunately, spring security uses this mechanism and publishes an InteractiveAuthenticationSuccessEvent in case of successful authentication. That will allow us to hook up custom code to that event.

Furthermore, we need to replicate the user details from the external realm to the eXo portal one according to the integration strategy defined above.

We create a SpringSecurityEventHandler bean that implements the ApplicationListener interface and listens to the InteractiveAuthenticationSuccessEvent event.

package org.exoplatform.spring.security.web;

...

public class SpringSecurityEventHandler implements ApplicationListener {

    private String portalContainerName = "portal";

    public void onApplicationEvent(ApplicationEvent event) {
        if (event instanceof InteractiveAuthenticationSuccessEvent) {
            try {
                InteractiveAuthenticationSuccessEvent successEvent = (InteractiveAuthenticationSuccessEvent) event;
                ExoContainer container = getContainer();

                String login = successEvent.getAuthentication().getName();
                String passwd = successEvent.getAuthentication().getCredentials().toString();

                IdentityRegistry identityRegistry = (IdentityRegistry) container.getComponentInstanceOfType(IdentityRegistry.class);
                Authenticator authenticator = (Authenticator) container.getComponentInstanceOfType(Authenticator.class);
                OrganizationService orgService = (OrganizationService) container.getComponentInstanceOfType(OrganizationService.class);

                User user = orgService.getUserHandler().findUserByName(login);
                if (user == null) {
                    user = orgService.getUserHandler().createUserInstance(login);               
                    user.setFirstName(login);
                    user.setLastName(login);
                    orgService.getUserHandler().createUser(user, false);   
                    orgService.getUserHandler().saveUser(user, false);
                    //TODO: put some more integration code here
                }

                Identity identity = authenticator.createIdentity(login);

                Subject subject = new Subject();
                subject.getPrivateCredentials().add(passwd);
                subject.getPublicCredentials().add(new UsernameCredential(login));

                identity.setSubject(subject);
                identityRegistry.register(identity);

            } catch (Exception e) {
                e.getMessage();
            }
        }
    }

    protected ExoContainer getContainer() {
        // TODO set correct current container
        ExoContainer container = ExoContainerContext.getCurrentContainer();
        if (container instanceof RootContainer) {
            container = RootContainer.getInstance().getPortalContainer(portalContainerName);
        }
        return container;
    }

...

}

Basically the bean retrieves user login and password from the InteractiveAuthenticationSuccessEvent object and tries to get the user from the organization service. In case he cannot find it in the repository, he simply creates it on the fly. In this example the user is created with just a few details, but you can put some custom integration code with the external realm here, and create the user with all the details (email, birth date, roles, etc.) it seems appropriate to you. After that, the bean creates an Identity object with the help of the authenticator service, populates it with a subject containing the user credentials and registers it. That's all we have to do to make the portal aware of the user logging in.

Registering our bean is done the usual way in security-context.xml file:

...
<beans:bean id="myEventHandler" class="org.exoplatform.spring.security.web.SpringSecurityEventHandler" />
...

Part of the problem is the question of security context propagation between on one side the portal webapp and at the other side the portlets webapps. This means that the security context has to be available in the portlet side allowing the application logic to deal the with current user principal and granted authorities. By default, Spring security uses a thread local variable to partially achieve this. But a problem may arise due to the fact that the portal invokes the portlet through a webapp cross context call. This means that it can lead to a class cast exceptions (two different classloaders involved), or that the security context is simply not propagated at all. To accommodate this, we will need to set up two request filters, one at the portal webapp side and the other at the portlet webapp side and use the http request to propagate the context in between.

We will use the spring security extensible filter chain to plug in our filter.

package org.exoplatform.spring.security.web;

...

public class PortalSideSecurityContextFilter extends SpringSecurityFilter {

    @Override
    protected void doFilterHttp(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
        //fill request with security context
        SecurityContext context = SecurityContextHolder.getContext();       
         request.setAttribute(HttpSessionContextIntegrationFilter.SPRING_SECURITY_CONTEXT_KEY, context);       
        
         //fill request with security last exception
         Object e = request.getSession().getAttribute(AbstractProcessingFilter.SPRING_SECURITY_LAST_EXCEPTION_KEY);
        request.setAttribute(AbstractProcessingFilter.SPRING_SECURITY_LAST_EXCEPTION_KEY, e);
       
        chain.doFilter(request, response);
    }

    public int getOrder() {
        // TODO Auto-generated method stub
        return 0;
    }
}

The PortalSideSecurityContextFilter simply fills the request with the security context and security last exception using the HttpSessionContextIntegrationFilter.SPRING_SECURITY_CONTEXT_KEY and AbstractProcessingFilter.SPRING_SECURITY_LAST_EXCEPTION_KEY attribute names. The portlet can have a look to the AbstractProcessingFilter.SPRING_SECURITY_LAST_EXCEPTION_KEY attribute to check if a security exception has occured.

The following lines in the security-context file register our custom filter in the chain at the last position.

...
   <beans:bean id="myCustomFilter" class="org.exoplatform.spring.security.web.PortalSideSecurityContextFilter">
      <custom-filter after="LAST" />
   </beans:bean>
...

In the portlet webapp we create a regular filter named PortletSideSecurityContextFilter.

package org.exoplatform.spring.security.web;

...

public class PortletSideSecurityContextFilter implements Filter {

    public void destroy() {       
       
    }

    public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
        Object object = request.getAttribute(HttpSessionContextIntegrationFilter.SPRING_SECURITY_CONTEXT_KEY);
        SecurityContext context = (SecurityContext) serializeDeserialize(object);
        if (context != null) {
            SecurityContextHolder.setContext(context);
        } else {
            SecurityContextHolder.clearContext();
        }       
       
        filterChain.doFilter(request, response);
    }

    public void init(FilterConfig arg0) throws ServletException {       
       
    }

    private Object serializeDeserialize(Object obj) {
        Object result = null;       
        try {
            ByteArrayOutputStream bout = new ByteArrayOutputStream();
            ObjectOutputStream out = new ObjectOutputStream(bout);

            out.writeObject(obj);

            ByteArrayInputStream bin =    new ByteArrayInputStream(bout.toByteArray());
            ObjectInputStream in = new ObjectInputStream(bin);

            result = in.readObject();           
        } catch (Exception e) {
            //TODO: handle exception
        }
        return result;
    }
   
}

The PortletSideSecurityContextFilter retrieves the security context from the request and proceeds to a serialization de-serialization of it to avoid a potential class cast exception that may occur when propagating an object across webapps. Then the context is simply set or cleared whether the context is null or not.

To register your filter simply add the following lines to your portlet webapp web.xml file.

...
   <filter>
      <filter-name>portletSideSecurityContextFilter</filter-name>
      <filter-class>org.exoplatform.spring.security.web.PortletSideSecurityContextFilter</filter-class>
   </filter>
     
   <filter-mapping>
      <filter-name>portletSideSecurityContextFilter</filter-name>
      <url-pattern>/*</url-pattern>
      <dispatcher>REQUEST</dispatcher>
      <dispatcher>INCLUDE</dispatcher>
      <dispatcher>FORWARD</dispatcher>
   </filter-mapping>
...

We are done! Now we know how to integrate the spring security framework in the eXo portal. Thanks to the the great integration capabilities of both eXo portal and Spring framework. You can have a look to the attachment section on this page and get the source code of this tutorial.