Loading...

February 5, 2009

Use Spring or Acegi security to protect RESTful webservices

Simple protection of RESTful webservices is accomplished by using HTTP Basic authentication. We can use Acegi 1.x or Spring 2.x security to define an URL pattern to protect with a username and password. For example we have developed a RESTful webservice with the URI /resources/articles and we want to protect it with a username and password. Let's look at the necessary Spring definition to achieve this with Acegi 1.x security:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="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.xsd">

    <bean id="filterChainProxy" class="org.acegisecurity.util.FilterChainProxy">
        <property name="filterInvocationDefinitionSource">
            <value>
              <![CDATA[
                CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
                PATTERN_TYPE_APACHE_ANT
                /resources/**=httpSessionContextIntegrationFilterWithASCFalse,basicProcessingFilter,exceptionTranslationWithASCFilter,filterInvocationInterceptor
              ]]>
            </value>
        </property>
    </bean>

    <bean id="httpSessionContextIntegrationFilterWithASCFalse"
        class="org.acegisecurity.context.HttpSessionContextIntegrationFilter">
        <property name="allowSessionCreation" value="false"/>
    </bean>

    <bean id="exceptionTranslationWithASCFilter"
        class="org.acegisecurity.ui.ExceptionTranslationFilter">
        <property name="createSessionAllowed" value="false"/>
        <property name="authenticationEntryPoint" ref="authenticationEntryPoint"/>
        <property name="accessDeniedHandler">
            <bean class="org.acegisecurity.ui.AccessDeniedHandlerImpl"/>
        </property>
    </bean>

    <bean id="basicProcessingFilter" class="org.acegisecurity.ui.basicauth.BasicProcessingFilter">
        <property name="authenticationManager">
            <ref bean="authenticationManager"/>
        </property>
        <property name="authenticationEntryPoint">
            <ref bean="authenticationEntryPoint"/>
        </property>
    </bean>

    <bean id="filterInvocationInterceptor"
        class="org.acegisecurity.intercept.web.FilterSecurityInterceptor">
        <property name="authenticationManager" ref="authenticationManager"/>
        <property name="accessDecisionManager" ref="httpRequestAccessDecisionManager"/>
        <property name="objectDefinitionSource">
            <value>
            <![CDATA[
                CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
                PATTERN_TYPE_APACHE_ANT
                /resources/**=ROLE_ADMIN
            ]]>
            </value>
        </property>
    </bean>

    <bean id="authenticationManager" class="org.acegisecurity.providers.ProviderManager">
        <property name="providers">
            <list>
                <ref local="daoAuthenticationProvider" />
            </list>
        </property>
    </bean>

    <bean id="daoAuthenticationProvider"
        class="org.acegisecurity.providers.dao.DaoAuthenticationProvider">
        <property name="userDetailsService" ref="inMemoryDaoImpl" />
        <property name="forcePrincipalAsString">
            <value>true</value>
        </property>
    </bean>

    <bean id="inMemoryDaoImpl" class="org.acegisecurity.userdetails.memory.InMemoryDaoImpl">
        <property name="userMap">
            <value>
                <![CDATA[
                    admin=crypticpassw,ROLE_ADMIN
                ]]>
            </value>
        </property>
    </bean>

    <bean id="authenticationEntryPoint" class="org.acegisecurity.ui.basicauth.BasicProcessingFilterEntryPoint">
        <property name="realmName">
            <value>Restricted resources</value>
        </property>
    </bean>

    <bean id="httpRequestAccessDecisionManager" class="org.acegisecurity.vote.AffirmativeBased">
        <property name="allowIfAllAbstainDecisions" value="false"/>
        <property name="decisionVoters">
            <list>
                <ref bean="roleVoter" />
            </list>
        </property>
    </bean>

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

</beans>

This is just a basic configuration. To learn more about the different components see the official Acegi 1.x reference documentation. For now the most important thing to remember is that we don't need HTTP sessions to be created when invoking a RESTful webservice. We don't want this to happen, because it will take up a lot of resources on the server and eventually our application can become very slow. The nice thing about a RESTful webservice is that it doesn't need an HTTP session to work correctly. But to accomplish this we must tell Acegi explicitly we don't want HTTP sessions to be created. If we don't, Acegi will create an HTTP session to save authentication information.

When we look at the configuration we notice at line 11 we reference httpSessionContextIntegrationFilterWithASCFalse and exceptionTranslationWithASCFilter. The definition can be found at lines 17 and 22. Here we assign the property allowSessionCreation the value false. Now Acegi will no longer create an HTTP session to save authentication information and that is exactly we wanted to achieve!

Spring 2.0 security is the new Acegi security, but we can use namespaces to configure the security which results in a much easier to read configuration file:

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

    <security:http create-session="never" auto-config="false" realm="Restricted resources">
        <security:intercept-url pattern="/resources/**" access="ROLE_ADMIN"/>
        <security:http-basic />
        <security:logout />
    </security:http>
    
    <security:authentication-provider>
        <security:user-service>
            <security:user name="admin" password="crypticpassw" authorities="ROLE_ADMIN"/>
        </security:user-service>
    </security:authentication-provider>

</beans>

Wow that is much easier to read (and maintain) than the previous configuration. To read more about the configuration options see the official Spring 2.0 security reference documentation. At line 9 we assign the attribute create-session the value never to make sure no HTTP session is created when using the RESTful webservice.