How to implement your own Security provider with the Acegi framework.
Posted by Okke Harsta in the early morning: March 4, 2007
In a previous blog I described the minimal basic configuration of the Acegi framework. In this blog I'll show you how easy it is to implement your own security provider. There can be many reasons why you would want to implement such a customized security provider. In my case I had to secure an application using user information that was being maintained by an external php-based application. The user information could only be retrieved using a web service. In this blog I will demonstrate several ways to implement your own security provider.
The starting point is -again- the simple web application from the previous blog. The part of the acegi-security.xml that is relevant is shown below.
[xml]
[/xml]
We will replace this fragment with the following code:
[xml]
[/xml]
What does our CustomAuthenticationManager has to do? Well actually the interface AuthenticationProvider that needs to be implemented is quite straightforward:
[java]
public Authentication authenticate(Authentication authentication)
throws AuthenticationException;
[/java]
Lets begin with the most simple implementation possible:
[java]
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
authentication.setAuthenticated(true);
return authentication;
}
[/java]
When we start jetty (see previous blog) we can tweak Maven to give us a remote debugging hook by setting the MAVEN_OPTS variable:
[code]
$ echo $MAVEN_OPTS
-XX:MaxPermSize=512m -Xmx1024m -Xdebug -Xnoagent -Djava.compiler=NONE -Xrunjdwp:transport=dt_socket,address=4000,server=y,suspend=n
[/code]
The first two arguments allocate more memory to the Maven process and the last arguments provide us a way to remotely debug the application. Using eclipse we can configure a remote debug application:
The implementation of the Authentication interface used by Acegi is an instance of UsernamePasswordAuthenticationToken as we can see when debugging the application.
The UsernamePasswordAuthenticationToken is constructed in the AuthenticationProcessingFilter -as configured in acegi-secutiry.xml-, but we could also subclass this filter to use more exotic implementations like the X509AuthenticationToken.
When checking out the result at http://localhost:8080/blog-acegi/site/admin.html it appears the implementation we have provided is to simple:
[code]
HTTP ERROR: 500
Cannot set this token to trusted - use constructor containing GrantedAuthority[]s instead
RequestURI=/blog-acegi/j_acegi_security_check
Caused by:
java.lang.IllegalArgumentException: Cannot set this token to trusted - use constructor containing GrantedAuthority[]s instead
at org.acegisecurity.providers.UsernamePasswordAuthenticationToken.setAuthenticated(UsernamePasswordAuthenticationToken.java:85)
at com.xebia.acegi.CustomAuthenticationManager.authenticate(CustomAuthenticationManager.java:25)
at org.acegisecurity.ui.webapp.AuthenticationProcessingFilter.attemptAuthentication(AuthenticationProcessingFilter.java:71)
at org.acegisecurity.ui.AbstractProcessingFilter.doFilter(AbstractProcessingFilter.java:199)
at org.acegisecurity.util.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:274)
at org.acegisecurity.intercept.web.FilterSecurityInterceptor.invoke(FilterSecurityInterceptor.java:107)
at org.acegisecurity.intercept.web.FilterSecurityInterceptor.doFilter(FilterSecurityInterceptor.java:72)
at org.acegisecurity.util.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:274)
at org.acegisecurity.ui.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:110)
at org.acegisecurity.util.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:274)
at org.acegisecurity.util.FilterChainProxy.doFilter(FilterChainProxy.java:148)
at org.acegisecurity.util.FilterToBeanProxy.doFilter(FilterToBeanProxy.java:98)
at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1065)
[/code]
Acegi insists we create our instance of the Authentication interface. So let's change our custom authentication manager using the following code:
[java]
return new UsernamePasswordAuthenticationToken(
authentication.getPrincipal(),
authentication.getCredentials(),
new GrantedAuthority[] { new GrantedAuthorityImpl("ROLE_ADMIN") });
[/java]
Of course a more serious implementation would do some lookup of the User and apply business logic to authenticate the user.
Acegi provides also an abstract AbstractUserDetailsAuthenticationProvider class that can be subclassed in able to benefit from the existing functionality from the Acegi framework. We change the relevant xml part in the acegi-security.xml with this fragment:
[xml]
[/xml]
The abstract methods that need to be implemented are simple:
[java]
protected abstract void additionalAuthenticationChecks(UserDetails userDetails,
UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException ;
protected UserDetails retrieveUser(String username,
UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException ;
[/java]
And a -again very simple- implementation could be:
[java]
protected UserDetails retrieveUser(String username,
UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException {
Assert.hasText(username);
Assert.notNull(authentication.getCredentials());
if (username.equals("admin")
&& authentication.getCredentials().equals("secret")) {
return new User("admin", "secret", true, true, true, true,
getAuthorities());
}
throw new BadCredentialsException("invalid username/password");
}
/*
* return GrantedAuthorities
*/
private GrantedAuthority[] getAuthorities() {
return new GrantedAuthority[] { new GrantedAuthorityImpl("ROLE_ADMIN") };
}
[/java]
The hook that Acegi offers when subclassing the AbstractUserDetailsAuthenticationProvider allows you to call a web service to get the relevant information, query a legacy database or do whatever it takes to populate the User object. Note that because the authentication providers/managers are normal Spring beans you can inject them with configured data sources or any other -more exotic- beans.
A more complex use case could require the following security constraints to be implemented:
- An user account will be locked if the number of consecutive faulty login attempts exceeds 3.
- The password of an user account must be stored encrypted.
- An user account can be disabled/enabled for a certain period.
- An user account expiries after a certain period of inactivity.
- A password expiries after a certain period.
The actual implementation (not provided ;-), but you can use this as a starting point ) of such a security policy is quite simple using the Acegi framework and the many hooks Acegi provides. When you consider implementing your own authentication provider do have a good look at the stuff that Acegi provides. There is good chance you can use/re-use existing functionality. There is a lot more to tell about the Acegi Framework like securing your services, securing specific part of your pages, accessing the user data stored in the session, integration with LDAP and using Acegi for standalone desktop applications but not now......
Filed under: Security
March 7th, 2007 at 8:00 pm
I have gone through the hints you provided, Actually my situation is where I have to call a web service for authentication, so there will be a java class which will call the webservice and i want that class to act as a authentication provider.
So please help help help
Thank you
March 8th, 2007 at 1:08 am
I have figued it out. My listner was giving some weired problem
Now its working excelent.
Thank you very much for providing this blog and helping my understanding.
Really Appreciated
God Bless you