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)