mohchi Blog

Tighter Spring MVC Integration with Spring Security’s @PreAuthorize Annotation

By: Andy Chang

imageHave you ever wanted to integrate @PreAuthorize into Spring MVC’s controller mapping mechanism?

Unfortunately, using the @PreAuthorize annotation on controller methods out-of-the-box is not very straightforward nor is it recommended by the Spring Security team to use it with the <global-method-security> element (read more here). Regardless, with <global-method-security pre-post-annotations="true"> enabled in the DispatcherServlet's context you can do the following:

@RequestMapping("/")
@PreAuthorize("isAuthenticated()")
public String authenticatedHomePage(Principal principal) {
    return "authenticatedHomepage";
}

However, all this achieves is blocking invocation of this method. There’s little integration between Spring Security and Spring MVC in terms of authorization-based request mapping. For instance, if you wanted to map two different pages to the same URI based on whether the user has authenticated, one (rather clunky) way would be:

@RequestMapping("/")
public String homePage(Principal principal) {
    if (principal instanceof UsernamePasswordAuthenticationToken) {
        return "authenticatedHomepage";
    } else {
        return "unauthenticatedHomepage";
    }
}

For every mapping with different pages based on authorization, you’d need to write an if/else block and code to check the authentication. Alternatively, you can use Spring MVC’s interceptors or servlet filters to re-route requests based on the current authentication. Still, that’s not perfect since separating the logic from the controller makes it more difficult to understand how requests are being routed.

We wanted to keep everything neatly in one place and to leverage Spring Security’s powerful security expressions to concisely define the level of authorization desired at the controller-class and controller-method levels. Here’s what we achieved (without using <global-method-security>):

@RequestMapping("/")
public String unauthHomePage() {
    return "unauthenticatedHomePage";
}

@RequestMapping("/")
@PreAuthorized("isAuthenticated()")
public String authHomePage() {
    return "authenticatedHomePage";
}

The behavior is what you might expect. If the “isAuthenticated()" expression evaluates to true, the second method is invoked when a user navigates to "/”. Otherwise, the first method is invoked.

What happens if you only have a single method?:

@RequestMapping("/")
@PreAuthorized("isAuthenticated()")
public String homePage() {
    return "homePage";
}

If the “isAuthenticated()" expression evaluates to true, the method is invoked. Otherwise, since there are no mappings that can handle an unauthenticated request, an AccessDeniedException is thrown. This exception can, in turn, be used by Spring Security’s ExceptionTranslationFilterto determine how to proceed (e.g. redirect the user to a login page, return a 401 error code, etc.).

How do you do it?

Spring MVC 3.1 makes it easy to accomplish this. We looked into how Spring Security’s <authorize> JSP tag works and plugged that functionality into Spring MVC using a custom RequestMappingHandlerMapping. The result does everything that <mvc:annotation-driven> can do + handle @PreAuthorized annotations.

Since the classes are a bit long, we won’t inline the code in the post. You can take a look at them here:

If you have your security configuration isolated to your parent application context, all you need to do is replace <mvc:annotation-driven> in DispatcherServlet's context with the following:

<bean id="extendedWebMvcConfig" 
  class="com.mohchi.web.framework.ExtendedWebMvcConfiguration" />

and let the magic happen.

Feel free to adapt this code for your own project, but keep in mind that it has not been thoroughly tested. In return, we would like to get your feedback (bugs, suggestions, and design comments welcome). If you write about this, please include a reference to us.

Tagged: #java  #web programming  #spring mvc  #spring security  #mohchi