Мы уже рассмотрели авторизацию на основе 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.