12

Let say we have an API endpoint configured using Spring MVC and Spring Security. We would like to be able to handle pairs of @RequestMapping and @Secured annotations where the only @Secured annotation values differ from pair to pair. This way, we would be able to return a different response body depending on security rules for the same request.

This may allow our code to be more maintainable by avoiding to check for security rules directly into the method body.

With a not working example, here is what we would like to do :

@Controller
@RequestMapping("/api")
public class Controller {

    @Secured ({"ROLE_A"})
    @RequestMapping(value="{uid}", method=RequestMethod.GET)
    @ResponseBody
    public Response getSomething(@PathVariable("uid") String uid) {
        // Returns something for users having ROLE_A
    }

    @Secured ({"ROLE_B"})
    @RequestMapping(value="{uid}", method=RequestMethod.GET)
    @ResponseBody
    public Response getSomethingDifferent(@PathVariable("uid") String uid) {
        // Returns something different for users having ROLE_B
    }
}

How can we achieve this ? And if this can be done: How the priority should be managed for a user who has both ROLE_A and ROLE_B ?

Julien Mellerin
  • 412
  • 4
  • 13
  • This approach is not possible because it's the same URL mapping. This will cause a ambiguous mapping error. Try to build a different URL for each user or accept these two permissions on method and do role login inside it. You can retrieve the permissions on `SecurityContextHolder` – Deividi Cavarzan Aug 01 '13 at 14:05
  • 1
    This is possible using custom request conditions. The question appears to be a duplicate of http://stackoverflow.com/questions/10073695/adding-custom-requestconditions-in-spring-mvc-3-1 – Rob Winch Aug 01 '13 at 14:11
  • @Deividi Cavarzan This is exactly why I titled the example as a "not working example" and the suggestion you did I would like to avoid. – Julien Mellerin Aug 02 '13 at 14:44
  • @Rob Winch I do not consider this as a duplicate as I asked how to do it using the @ Secured annotation but thank you for the suggestion. – Julien Mellerin Aug 02 '13 at 14:48
  • @JulienMellerin Fair enough...the differences between the two questions are minor so hopefully this is enough to get you want you need. – Rob Winch Aug 02 '13 at 14:52
  • 1
    Take a look at the accepted answer here http://stackoverflow.com/questions/10312177/how-to-implement-requestmapping-custom-properties. And although it does not technically answer your question exactly - Using pre-existing Annotations to work together, it should help. – Akshay Aug 02 '13 at 15:25
  • @JulienMellerin my bad, I didn't see that. – Deividi Cavarzan Aug 02 '13 at 16:39

2 Answers2

4

Assuming you are using Spring 3.1 (or up) together with the RequestMappingHandlerMapping (and RequestMappingHandlerAdapter) you can extend the request mapping mechanism. You can do this by creating your own implementation of the RequestCondition interface and extend the RequestMappingHandlerMapping to construct this based on the @Secured annotation on your method.

You would need to override the 'getCustomMethodCondition' method on the RequestMappingHandlerMapping and based on the Method and the existence of the @Secured annotation construct your custom implementation of the RequestCondition. All that information is then taken into account when matching incoming requests to methods.

Related answers (although not specific for @Secured annotations but the mechanism is the same) is also to be found here or here

Community
  • 1
  • 1
M. Deinum
  • 115,695
  • 22
  • 220
  • 224
3

I don't think you can do this in spring-mvc, since both routes have exactly the same @RequestMapping (@Secured) is not taken into account by the route engine of spring-mvc. The easiest solution would be to do this:

@Secured ({"ROLE_A", "ROLE_B"})
@RequestMapping(value="{uid}", method=RequestMethod.GET)
@ResponseBody
public Response getSomething(@PathVariable("uid") String uid, Principal p) {
    // Principal p gets injected by spring
    // and you need to cast it to check access roles.
    if (/* p.hasRole("ROLE_A") */) {
        return "responseForA";
    } else if (/* p.hasRole("ROLE_B") */) {
        return "responseForB";
    } else {
        // This is not really needed since @Secured guarantees that you don't get other role.
        return 403;
    }
}

However, I would change your design, since the response is different per role, why not have 2 separate request mappings with slightly different URLs? If at some point you have users with role A and B at the same time, you can't let the user choose what response to get (think, for example, of the public and private profiles of LinkedIn)

Alberto
  • 925
  • 6
  • 4
  • Your suggestion would be my "spare wheel". To answer your question, behind the given example, there is a REST API. Depending on the user role, the returned object will contain partial or full data of an entity. – Julien Mellerin Aug 02 '13 at 14:53