Spring Security + MVC: тот же @RequestMapping, другой @Secured

Скажем, у нас есть конечная точка API, настроенная с использованием Spring MVC и Spring Security. Мы хотели бы иметь возможность обрабатывать пары @RequestMapping и @Secured аннотаций, где единственные значения @Secured annotation отличаются от пары к паре. Таким образом, мы могли бы вернуть другой орган ответа в зависимости от правил безопасности для одного и того же запроса.

Это может позволить нашему коду быть более удобным для обслуживания, избегая проверки правил безопасности непосредственно в теле метода.

С нерабочим примером, вот что мы хотели бы сделать:

@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
    }
}

Как мы можем достичь этого? И если это можно сделать: как управлять приоритетом для пользователя, который имеет как ROLE_A, так и ROLE_B?

Ответ 1

Предполагая, что вы используете Spring 3.1 (или вверх) вместе с RequestMappingHandlerMapping (и RequestMappingHandlerAdapter), вы можете расширить механизм сопоставления запросов. Вы можете сделать это, создав собственную реализацию интерфейса RequestCondition и расширьте RequestMappingHandlerMapping, чтобы построить его на основе аннотации @Secured в вашем методе.

Вам нужно будет переопределить метод getCustomMethodCondition на методе RequestMappingHandlerMapping и на основе метода, а существование @Secured аннотации создаст вашу пользовательскую реализацию RequestCondition. Вся эта информация затем учитывается при сопоставлении входящих запросов с методами.

Связанные ответы (хотя и не специфические для @Secured аннотаций, но механизм тот же) также можно найти здесь или здесь

Ответ 2

Я не думаю, что вы можете сделать это весной-mvc, так как оба маршрута имеют точно такое же @RequestMapping (@Secured), которое не учитывается движком маршрута spring-mvc. Самое простое решение - сделать это:

@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;
    }
}

Тем не менее, я бы изменил ваш дизайн, так как ответ отличается для каждой роли, почему бы не иметь 2 отдельных сопоставления запросов с несколько разными URL-адресами? Если в какой-то момент у вас есть пользователи с ролью A и B одновременно, вы не можете позволить пользователю выбрать, какой ответ получить (подумайте, например, о публичных и частных профилях LinkedIn)