Мы уже рассмотрели авторизацию на основе url, но ее бывает недостаточно. В этой статье мы рассмотрим, как защитить отдельные методы (любые — как методы контроллеров, так и сервисов). То есть разрешить вызов метода только пользователю с конкретными правами. (А вообще можно задать любые условия).
Рассмотрим пример.
Пусть у нас есть пользователи user и admin. А также AnimalController, который позволяет выводить список животных и добавлять животное. Обе операции доступны по адресу /animal. Но первая вызывается по GET, а вторая — по POST:
@RestController public class AnimalController { private final List<Animal> list = new ArrayList<>(); { list.add(new Animal("cat")); list.add(new Animal("dog")); } @GetMapping("/animal") public List<Animal> getAnimals() { return list; } @PreAuthorize("hasAuthority('ROLE_ADMIN')") @PostMapping("/animal") @ResponseStatus(HttpStatus.CREATED) public Animal addAnimal(@RequestBody Animal animal) { list.add(animal); return animal; } }
Обоим пользователям разрешен доступ к адресу /animal, что прописано в конфигурации ниже:
@EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) public class SecurityConfig extends WebSecurityConfigurerAdapter { @Bean public PasswordEncoder passwordEncoder() { return NoOpPasswordEncoder.getInstance(); } @Override public void configure(AuthenticationManagerBuilder auth) throws Exception { auth.inMemoryAuthentication() .withUser("user") .password("user") .authorities("ROLE_USER") .and() .withUser("admin") .password("admin") .authorities("ROLE_ADMIN"); } @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers("/animal/**").hasAnyAuthority("ROLE_USER", "ROLE_ADMIN") .antMatchers("/**").permitAll() .and().httpBasic() .and().csrf().disable(); } }
В методе configure(HttpSecurity http) задается авторизация — кому какой url доступен.
Сделаем так, чтобы добавлять животных (вызывать метод addAnimal()) мог вызывать только admin, но не user.
@PreAuthorize
Для этого используем аннотацию @PreAuthorize — поставим ее непосредственно перед методом. Собственно, мы это уже сделали. Обратите внимание на метод в контроллере выше — он аннотирован:
@PreAuthorize("hasAuthority('ROLE_ADMIN')") @PostMapping("/animal") @ResponseStatus(HttpStatus.CREATED) public Animal addAnimal(@RequestBody Animal animal) { list.add(animal); return animal; }
В аннотации сказано, что добавлять животных может только пользователь с правом ROLE_ADMIN. Если ROLE_USER попытается добавить животное, то вернется 403.
Как упоминалось вначале, @PreAuthorize может защищать не только методы контроллера, но любые: аннотацию можно ставить на методы сервисов или dao-методы.
@EnableGlobalMethodSecurity
Кроме того, важно не забыть включить в конфигурацию аннотацию:
@EnableGlobalMethodSecurity(prePostEnabled = true)
Можно было ее поставить и на главный класс приложения.
Без нее аннотация @Preauthorize работать не будет.
Проверка
С помощью POSTMAN попробуем добавить животное от имени user, а потом от имени admin (не забудьте задать Basic Auth на вкладке Authorization).
user получает ошибку 403:

А admin добавляет животное успешно:

Другие SpEL-выражения
Как упоминалось вначале, внутри аннотации @Preauthorize можно задать не только права. Можно задать любое SpEL-выражение.
Добавим в контроллер еще один метод. И в аннотацию включим такое условие (SpEL — выражение):
#animal.name == authentication.name
Метод:
@PreAuthorize("#animal.name == authentication.name") @PostMapping("/special") @ResponseStatus(HttpStatus.CREATED) public Animal addAdmin(@RequestBody Animal animal) { list.add(animal); return animal; }
В условии сказано, что имя пользователя должно совпадать с именем переданного животного. Только в этом случае вызов метода разрешен.
То есть admin сможет добавить только такие данные:
{ "name": "admin" }
А user — только такие:
{ "name": "user" }
В ином случае вернется ошибка 403.
А такое выражение разрешит доступ всем аутентифицированным пользователям:
@PreAuthorize("isAuthenticated()")
То есть запрет будет только анонимным пользователям.
Список доступных в SpEL-выражений есть в документации.
@PreAuthorized vs. @Secured
Аннотация @Secured более старая и не поддерживает SpEL-выражения. В нее можно включить только право доступа:
@Secured("ROLE_ADMIN")
Исходный код
Код примера доступен на GitHub.