This is the mobile version of my blog. The non-mobile version can be found at http://mark.koli.ch. (hide)
Home | Older (Prev)

Spring 3 and Spring Security: Setting your Own Custom /j_spring_security_check Filter Processes URL

2010-07-25T02:20:00Z

While working on a new personal project, I decided to pick up and dig into Spring 3 MVC and Spring Security.  I've touched both of these technologies here and there in a number of other projects, but this new opportunity has really opened the door for a deep dive into Spring.  I know the Ruby fanatics out there would say I'm crazy, but once I got all of the awful XML configuration mess out of the way, I'm really enjoying Spring's stability and reliability.  I know Ruby and Rails is hip and what not, but Spring 3 has really made an impression on me.  It's hard to beat such a mature enterprise framework.

I setup a few Spring 3 controllers, and integrated Spring Security into my web-app.  All went great and so I added a simple form-based login to my Spring Security XML configuration.

Problem: Overriding UsernamePasswordAuthenticationFilter

When setting up a form-based login via a default Spring Security <http:security> configuration, Spring auto generates and configures a UsernamePasswordAuthenticationFilter bean.  This filter, by default, responds to the URL /j_spring_security_check when processing a login POST from your web-form.  First, I want to override Spring Security's default login process URL to /login instead of /j_spring_security_check.  Second, I've configured a Spring 3 controller to display my login web-form when a user visits /login.

That said, here's the underlying problem with Spring Security's default UsernamePasswordAuthenticationFilter: I want it to accept and process POST's to /login, but a GET or any HTTP method to /login should be forwarded to the next filter in the chain.  Not surprisingly, you cannot do this with Spring Security's default UsernamePasswordAuthenticationFilter because it does not @Override the doFilter() method of AbstractAuthenticationProcessingFilter.  In short, there's no way to get and check the incoming HTTP request method and re-route it using the default UsernamePasswordAuthenticationFilter.

Solution: Write your own Spring Security Authentication Filter

If you want a Spring controller to process GET requests to /login, but Spring Security to intercept and process a POST to /login, then you'll need to write your own Spring Security authentication filter.  Here's the idea:

public class MyFilter extends AbstractAuthenticationProcessingFilter {

private static final String DEFAULT_FILTER_PROCESSES_URL = "/login";
private static final String POST = "POST";

public MyFilter () {
super(DEFAULT_FILTER_PROCESSES_URL);
}

@Override
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException,
IOException, ServletException {
// You'll need to fill in the gaps here. See the source of
// UsernamePasswordAuthenticationFilter for a working implementation
// you can leverage.
}

@Override
public void doFilter(ServletRequest req, ServletResponse res,
FilterChain chain) throws IOException, ServletException {
final HttpServletRequest request = (HttpServletRequest) req;
final HttpServletResponse response = (HttpServletResponse) res;
if(request.getMethod().equals(POST)) {
// If the incoming request is a POST, then we send it up
// to the AbstractAuthenticationProcessingFilter.
super.doFilter(request, response, chain);
} else {
// If it's a GET, we ignore this request and send it
// to the next filter in the chain. In this case, that
// pretty much means the request will hit the /login
// controller which will process the request to show the
// login page.
chain.doFilter(request, response);
}
}

}

Note the good stuff inside of doFilter().  If the incoming request method is a POST, then we send it up to our AbstractAuthenticationProcessingFilter to actually process the login.  If it's a GET, or any other HTTP request method for that matter, we simply send it to the next filter in the chain.

Finally, remember that you'll need to define your own FORM_LOGIN_FILTER inside of your <security:http> Spring Security XML configuration to override the default /j_spring_security_check URL:

<security:http auto-config="false" use-expressions="true"
entry-point-ref="LoginUrlAuthenticationEntryPoint">
<security:custom-filter position="FORM_LOGIN_FILTER" ref="MyFilter" />
</security:http>

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

Enjoy!