Wednesday, February 17, 2010

Extending UsernamePasswordAuthenticationFilter in Spring Security

So, you are using Spring security, and you want to process some information after a user has logged in (say, load something in session) or after failed login attempts (such as lock an account after a set number of failed logins.. ), here's how it can be done.. Hope this helps.

Before I go on to describe what goes into it, thanks to the following. These links are a bit dated, but they did help me along.

Custom AuthenticationProcessingFilter for spring security to perform actions on login
An example of the security config for a custom auth filter (see last post)
Spring's documentation on adding own filters

Now, here we go. What is described below works for Spring Security 3.0.0.RELEASE.

1. We need to extend UsernamePasswordAuthenticationFilter

It used to be AuthenticationProcessingFilter, which is depricated.

In this example, we just write to the console. You would do something more important.

public class CustomUsernamePasswordAuthenticationFilter extends
UsernamePasswordAuthenticationFilter {

@Override
protected void successfulAuthentication(HttpServletRequest request,
HttpServletResponse response, Authentication authResult)
throws IOException, ServletException {
super.successfulAuthentication(request, response, authResult);

System.out.println("==successful login==");
}

@Override
protected void unsuccessfulAuthentication(HttpServletRequest request,
HttpServletResponse response, AuthenticationException failed)
throws IOException, ServletException {
super.unsuccessfulAuthentication(request, response, failed);

System.out.println("==failed login==");
}

}

2. In spring security context, we will be overriding the form login filter FORM_LOGIN_FILTER.

This used to be AUTHENTICATION_PROCESSING_FILTER, which is not used now.

<http ...
<custom-filter position="FORM_LOGIN_FILTER" ref="customUsernamePasswordAuthenticationFilter">

Note: Since we are replacing the default FORM_LOGIN_FILTER, we should not use <form-login login-page... , as that will try to create the default filter and you would get an exception (that there are two filters defined at the same position).

3. Since we are using the custom FORM_LOGIN_FILTER, we need to set the following property on <http ..

<http auto-config="false"

4. Another thing to set at <http is entry-point-ref

Again, since we are altering the default behavior, we need to tell what is the entry point for the form login.

<http entry-point-ref="loginUrlAuthenticationEntryPoint"

And, correspondingly, define the loginUrlAuthenticationEntryPoint bean. Note that LoginUrlAuthenticationEntryPoint used to be AuthenticationProcessingFilterEntry which is depricated.

<beans:bean id="loginUrlAuthenticationEntryPoint"
class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint">
<beans:property name="loginFormUrl" value="/login.html">
</beans:bean>

5. Remember the filter 'customUsernamePasswordAuthenticationFilter ' we used in in step 2 and extended in step 1, we have to define it.
<beans:bean id="customUsernamePasswordAuthenticationFilter"
class="com.yourapp.web.security.CustomUsernamePasswordAuthenticationFilter" >
<beans:property name="authenticationManager" ref="authenticationManager">
<beans:property name="authenticationFailureHandler" ref="failureHandler">
<beans:property name="authenticationSuccessHandler" ref="successHandler">
</beans:bean>

<beans:bean id="successHandler" class="org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler">
<beans:property name="defaultTargetUrl" value="/login.html">
</beans:bean>
<beans:bean id="failureHandler" class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler">
<beans:property name="defaultFailureUrl" value="/login.html?login_error=true">
</beans:bean>

6. In the definition of 'customUsernamePasswordAuthenticationFilter' we are identifying the 'authenticationManager', so when you define your authentication manager provide an 'alias' for it:

<authentication-manager alias="authenticationManager">
...
</authentication-manager>

There you have it. Share and enjoy.

17 comments:

  1. Thank you very much -- this was helpful!

    ReplyDelete
  2. Thanks ... came in handy :)

    ReplyDelete
  3. Thank you very much.

    The move from AuthenticationProcessingFilter too UsernamePassword things was just to painful for me until I got your blog

    ReplyDelete
  4. hi i am updating 2.X to 3.0.3 spring security
    as you told i need to remove the following line from


    but where should i mention 'login-processing-url="/j_security_check" '
    or there is not a problem removing that line .. please help me

    itsdipak@gmail.com

    ReplyDelete
  5. Hi,

    I have fallowed your steps, but at the end my login page goes into redirect loop. Can you help me.

    ReplyDelete
  6. This was of great help and good learning.

    ReplyDelete
  7. Thanks a lot for bean wiring examples!
    By the way there is another way of retrieving user information to session or using authentication information - extending UserDetailsService class:
    http://www.codercorp.com/blog/spring/security-spring/writing-custom-userdetailsservice-for-spring-security.html

    ReplyDelete
  8. David, this is true, but with real session management your things there are stateful. Everything in the security context is immutable unless you create a new authentication object. I think you only really want to do this when you're changing the password or some other specific security setting. It's not transactional the way a session can be.

    ReplyDelete
  9. Love this tutorial, but if the overriding of the default username password authentication filter was for the purpose of adding something into the session, I don't know how effective it will be. The login filter is fired before the session_management filter in the filter chain and if you have Spring Security configured to avoid session fixture attacks your session objects added right after successful authentication might get dropped when that filter runs.

    ReplyDelete
  10. This comment has been removed by the author.

    ReplyDelete
  11. For all of you guys wondering about j_security_check and login page looping. Default url for sping security is j_spring_security_check.

    Just replace your login form with "form method="POST" action="j_spring_security_check".
    Or add property name="filterProcessesUrl" value="/j__security_check" to your filter configuration.

    I think it's a common question. Would be nice to include it in the post.

    ReplyDelete
  12. Thank you so much. This post is very detailed one. If we use some technique, we should know all the work around also. You have explained it clearly. Thanks again. Please post more.

    ReplyDelete
  13. Thank you so much! You're a lifesaver.

    ReplyDelete
  14. Wow.. to use just one custom bean, the whole security config has to be manually wired. Would have been nice to use auto-config while manually injecting only this bean.

    ReplyDelete