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