Customizing the Alfresco login webscript

Posted on Sunday, January 3rd, 2010 at 16:35

One of the reasons I like Alfresco is that it’s opensource and as every opensource software you can get the source code, inspect it and learn how things work. Another reason is that Alfresco can be highly customized, being it based on the Spring framework; thanks to the IoC principle, Spring makes it very simple to replace components in Alfresco.

To demonstrate how simple this could be in practice, I’ll explain how to override the login webscript so that users could authenticate themeselves using a custom authentication component and, in case this fails, using the Alfresco default one. This kind of configuration is known as “authentication chain”.

The authentication system in Alfresco has gone under a major reorganization which has made the instructions that follow outdated for Alfresco Community 3.2 (and outcoming Alfresco Enterprise). Nonetheless, what’s written here is still valid for Alfresco Enterprise 3.1.1 and Alfresco 3Labs final and could be considered as a helpful example for those who want to start learning how Alfresco plays with Spring. Maybe in a future post I’ll write about how this could be done for release 3.2.

Since this post is quite long, if you are impatient you can download the source code and defer explanations. Package is tailored to Alfresco release 3Labs final but code will work for Alfresco Enterprise 3.1.1 as well. Edit build.properties and fix the path to $TOMCAT_HOME. Run ant deploy to build, package and deploy.

0. Summary

This is a brief summary of the steps we’ll take:

  1. understand how the login bean works and which components are implied
  2. understand how authentication components work and track the components’ dependencies down to the smallest component we can replace
  3. create a custom authentication component
  4. redefine necessary beans (login webscript and other components)
  5. deploy and test

1. How the login bean works

There’re two login webscripts in the Alfresco repository; the one I’m considering here is that whose classpath is alfresco/templates/webscripts/org/alfresco/repository/login.post.desc.xml. This is a java-backed webscript and is defined in web-scripts-application-context.xml under $TOMCAT_HOME/webapps/alfresco/WEB-INF/classes/alfresco. If you look at its source code, you’ll see that it delegates the authentication to the authenticationService instance (see AbstractLoginBean.java).

2. How authentication works

Which component is actually injected as the authenticationService instance depends on how the login webscript bean is declared. There’s a small difference here between the Enterprise and the Labs versions: in the former authenticationService references the bean called AuthenticationService which is a proxy object that targets the authenticationService bean (see how Spring realizes the AOP paradigm); in the Labs version a reference to the authenticationService bean is directly injected in the login bean. Anyway, we are interested in the authenticationService bean.

Its definition is found in authentication-services-context.xml under $TOMCAT_HOME/webapps/alfresco/WEB-INF/classes/alfresco and looks like this:

    <bean id="authenticationService" class="org.alfresco.repo.security.authentication.AuthenticationServiceImpl">
        <property name="authenticationDao">
            <ref bean="authenticationDao" />
        </property>
        <property name="ticketComponent">
            <ref bean="ticketComponent" />
        </property>
        <property name="authenticationComponent">
            <ref bean="authenticationComponent" />
        </property>
        <property name="sysAdminCache">
            <ref bean="sysAdminCache"/>
        </property>
    </bean>

Alfresco documentation and source code come into handy. The authenticationService bean is actually an instance of the AuthenticationServiceImpl class. This one uses the authenticationDao object to create, update and retrieve authentication information (such as the MD4 checksum of the password). The ticketComponent is responsible for issuing and renewing tickets. The user-password authentication is delegated to the authenticationComponent; by default this bean is an instance of AuthenticationComponentImpl which implements the AuthenticationComponent interface (this also can be inferred from authentication-services-context.xml).

Following the same reasoning it turns out that authenticationComponent depends on authenticationManager which is an instance of net.sf.acegisecurity.providers.ProviderManager. This class uses a list of authentication provider objects to validate the credentials. The logic is as follows: every provider is tested in turn in the order they are declared; if one is found to accept the credentials then authentication succeeds.

3. Create our own authentication component

Now that we’ve got the bottom component in the delegation chain of beans, we need to create our custom authentication provider and insert it into the list of providers used by the provider manager. In order to achieve this we have two chances:

  • override just the authenticationManager bean, or
  • redefine every bean involved in the afore paragraphs, starting from the authentication manager up to the authentication service and the login webscript

In the first case we’ll force the overriding authentication manager to be used whenever the authenticationManager bean is referenced (that is every authentication request). For the purpose of this example we’ll pick the second choice so that changes will only affect those beans that use the new authentication service (in this case the login webscript bean).

We start defining a CustomAuthenticationProvider provider class:

public class CustomAuthenticationProvider implements AuthenticationProvider {
    private MutableAuthenticationDao authenticationDao;

    @Override
    public Authentication authenticate(Authentication auth) throws AuthenticationException {
        if (!supports(auth.getClass()))
            return null;
        UsernamePasswordAuthenticationToken upat = (UsernamePasswordAuthenticationToken) auth;
        String username = (String) upat.getPrincipal();

        if (authenticationDao.getAccountHasExpired(username)
                || authenticationDao.getAccountlocked(username)
                || !authenticationDao.getEnabled(username)
                || !username.equals((String) upat.getCredentials()))
            return null;
        return auth;
    }

Providers must implement the authenticate method of the AuthenticationProvider interface; this method must return a non-null value if credentials are acceptable and false otherwise. We referenced the authenticationDao object to check that user is not locked or disabled and that their account is not expired. Authentication logic is trivial on purpose: credentials are valid if username equals the password. In a real case scenario we would rather ask the dao component to retrieve the salt and the password hash and then compare the latter with the hash of the input password (combined in some way with the salt), but for this example this will do.

4. Use the provider

We create a new file named my-auth-component.xml under $TOMCAT_HOME/shared/classes/alfresco/extension. These are the most important beans that must be declared (refer to the source code for the others):

    <bean id="CustomAuthenticationService" class="org.springframework.aop.framework.ProxyFactoryBean">
        <property name="proxyInterfaces">
            <value>
                org.alfresco.service.cmr.security.AuthenticationService
            </value>
        </property>
        <property name="target">
            <ref bean="customAuthenticationService" />
        </property>
        <property name="interceptorNames">
            <list>
                <idref local="AuthenticationService_transaction" />
                <idref local="AuditMethodInterceptor" />
                <idref local="exceptionTranslator" />
                <idref bean="AuthenticationService_security" />
            </list>
        </property>
    </bean>

    <bean id="customAuthenticationService"
        class="org.alfresco.repo.security.authentication.AuthenticationServiceImpl">
        <property name="authenticationDao">
            <ref bean="authenticationDao" />
        </property>
        <property name="ticketComponent">
            <ref bean="ticketComponent" />
        </property>
        <property name="authenticationComponent">
            <ref bean="customAuthenticationComponent" />
        </property>
        <property name="sysAdminCache">
            <ref bean="sysAdminCache" />
        </property>
    </bean>

    <bean id="customAuthenticationComponent"
        class="org.alfresco.repo.security.authentication.AuthenticationComponentImpl"
        parent="authenticationComponent">
        <property name="authenticationManager">
            <ref bean="customAuthenticationManager" />
        </property>
    </bean>

    <bean id="customAuthenticationManager"
     class="net.sf.acegisecurity.providers.ProviderManager">
        <property name="providers">
            <list>
                <ref bean="authenticatedAuthenticationPassthroughProvider" />
                <ref bean="customAuthenticationProvider" />
                <ref bean="daoAuthenticationProvider" />
            </list>
        </property>
    </bean>

    <bean id="customAuthenticationProvider"
     class="eu.fabiostrozzi.auth.CustomAuthenticationProvider">
        <property name="authenticationDao">
            <ref bean="authenticationDao" />
        </property>
    </bean>

Finally, we override the login webscript bean in $TOMCAT_HOME/shared/classes/alfresco/extension/custom-web-context.xml:

<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE beans PUBLIC '-//SPRING//DTD BEAN 2.0//EN' 'http://www.springframework.org/dtd/spring-beans-2.0.dtd'>
<beans>
    <bean id="webscript.org.alfresco.repository.login.post" class="org.alfresco.repo.web.scripts.bean.LoginPost"
        parent="webscript">
        <property name="authenticationService" ref="CustomAuthenticationService" />
    </bean>
</beans>

Instead of referencing authenticationService we now reference CustomAuthenticationService which has been declared in my-auth-component.xml.

5. Deploy and test

Download the source code and deploy the customizations (see afore instructions). Restart Alfresco and create a new user account (say johndoe) with a password different from the username (say password). Now open a terminal and use curl to call the login webscript, be sure to use the username as password:

echo '{username:"johndoe", password:"johndoe"}' | curl -d@- -H'Content-type: application/json' -XPOST http://localhost:8080/alfresco/s/api/login

If authentication worked correctly the response should be a json message containing the Alfresco ticket:

{
   data:
   {
        ticket:TICKET_3522058be33d5bf50b1b7eee3a3df7eafcd3f112
   }
}

Aknowledgments

Thanks to Mauro Strozzi who supervised translation.

Bookmark and Share

RSS 2.0 You can trackback from your own site.

Leave a Reply