What I like most about Acegi Security Framework is its configurability and extensibility. I think those two features are very crucial for any framework to be successful. Recently, I have come across a requirement of authenticating users via a web service and giving more detailed authentication failure messages according to result codes returned from that service. Well, it is very easy to develop a custom authentication provider and inject it into the related part in Acegi Security Framework. Let’s look at the details.
First, we code our custom authentication provider. We need to query the web service by giving the social security number
and user password as input. As a result of our query, we receive a return code. At the end of the authentication process,
Acegi should fetch the user from our own database and use it as a UserDetails object during the rest of the authentication
and authorization process. Acegi already provides org.acegisecurity.providers.dao.DaoAuthenticationProvider
, in which
it first retrieves the user with its userDetailsService
object and then performs additional authentication in which
encrypted password comparison occurs. Therefore, the order of retrieving the user from the database and querying
authentication web service won’t matter in our case. Hence, we can extend DaoAuthenticationProvider
and reuse most of
its code. We only need to override its additionalAuthenticationChecks
method to perform the web service query.
protected void additionalAuthenticationChecks(UserDetails userDetails,
UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
try {
Kullanici kullaniciFromDB = (Kullanici) userDetails;
//run your web service client code here to perform auth.
//and check result code to throw AuthenticationExceptions
//or do nothing if result code indicates success auth.
} catch (RemoteException e) {
throw new AuthenticationServiceException ("Problem with accessing ws endpoint :"
+ getAuthServiceUrl(), e);
}
}
After implementing our custom authentication provider, it is simply a matter of creating a Spring managed bean from it
and injecting that bean into Acegi’s ProviderManager
bean as a candidate provider.
<bean id="authenticationManager" class="org.acegisecurity.providers.ProviderManager">
<property name="providers">
<list>
<ref bean="webServiceAuthenticationProvider" />
<ref bean="anonymousAuthenticationProvider" />
<ref bean="testingAuthenticationProvider" />
</list>
</property>
</bean>
What about displaying more detailed authentication failure messages? As you see above, we receive different return codes
from the authentication web service and throw an appropriate AuthenticationException
accordingly. Acegi provides us
with the ability to map AuthenticationExceptions
with different URLs. When authentication fails, it first checks for
those exception mappings, and if it finds one, it redirects the application to that URL; otherwise, to the default failure
URL.
We need to configure Acegi to search for those exception mappings and inject them into the authenticationProcessingFilter
bean.
<bean id="exceptionMappings" class="org.springframework.beans.factory.config.PropertiesFactoryBean">
<property name="location">
<value>classpath:/resources/acegi.authExceptionMappings.properties</value>
</property>
</bean>
<bean id="authenticationProcessingFilter" class="org.acegisecurity.ui.webapp.AuthenticationProcessingFilter">
<property name="authenticationManager">
<ref bean="authenticationManager" />
</property>
<property name="authenticationFailureUrl">
<value>${security.auth.authenticationFailureUrl}</value>
</property>
<property name="defaultTargetUrl">
<value>${security.auth.defaultTargetUrl}</value>
</property>
<property name="exceptionMappings">
<ref local="exceptionMappings"/>
</property>
</bean>
If we look at our acegi.authExceptionMappings.properties
, as you see below, we give each different type of
AuthenticationException
a different URL. If Acegi cannot find a corresponding entry in this list, it will redirect the
application to the authenticationFailureUrl
, which is set into the authenticationProcessingFilter
bean.
```properties org.acegisecurity.concurrent.ConcurrentLoginException=/controller.faces?_flowId=login&loginError=true&cause=concurrentLogin org.acegisecurity.AuthenticationServiceException=/controller.faces?_flowId=login&loginError=true&cause=authServiceException org.acegisecurity.CredentialsExpiredException=/controller.faces?_flowId=login&loginError=true&cause=credentialsExpired ``