Защита методов — аннотация @PreAuthorize

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

Добавление от имени user
Добавление от имени user

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

Добавление от имени admin
Добавление от имени admin
Basic Auth выбрана потому, что с ней сразу происходит и аутентификация, и авторизация. То есть не надо отправлять запрос дважды, как это было бы с формой логина (в этом случае пришлось бы первым запросом отправлять form-data на /login, а потом уже тестировать /animal).

Другие SpEL-выражения

Как упоминалось вначале, внутри аннотации @Preauthorize можно задать не только права. Можно задать любое SpEL-выражение.

SpEL — Spring  Expression Language

Добавим в контроллер еще один метод. И в аннотацию включим  такое условие (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.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *