Spring Security — доступ на основе владения

Допустим, у меня есть минимальный контроллер RESTful, подобный следующему, кстати, с использованием Java 8 и Spring Boot 2,...

@RestController
class MyController {

    @Autowired
    private PostService service;    

    @GetMapping
    public Post get() {

        return service.getLatest();
    }
}

Я успешно защитил этот маршрут с помощью модуля Spring Security. Теперь я хочу разрешить доступ к этому ресурсу только владельцу ресурса. Под владельцем ресурса я подразумеваю создателя или говоря просто:

Post myPost = new Post();
...
myPost.getCreator().equals(currentUser); // Should be true when access is granted

Я много нашел о доступе на основе ролей, но почти ничего о проверке владения... Конечно, я мог бы поместить оператор if внутри контроллера и выдать исключение, но я намеревался использовать что-то вроде Spring Управление доступом на основе выражений.

Любые другие идеи? Есть ли у кого-нибудь хорошая идея или пример проверки права собственности на ресурс?

Я думать (но я не уверен), "доступ на основе владельца" называется «Дискретный контроль доступа», потому что владелец всегда имеет доступ, и владелец решает, кто еще может получить доступ к ресурсу. Сравните это с «Обязательный контроль доступа», где системные политики определяют, кто имеет доступ к ресурсу. В системе обязательного контроля доступа владелец/создатель ресурса может не иметь доступа к ресурсу.

jww 03.07.2019 18:32
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
6
1
3 027
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

В подходе, основанном на ролях, роли предопределены независимо от ответа/ресурса. Но в вашем случае нам нужно сначала получить ресурс, чтобы проверить, является ли пользователь владельцем или нет. Мы не можем решить заранее.

Допустим, если у пользователя есть ROLE_ADMIN, он будет администратором всех ресурсов. В вашем случае право собственности зависит исключительно от ресурсов. Советую идти с ручной if проверкой. В случае, если объект Post тяжелый, вы можете вести отдельную таблицу всего с двумя столбцами resource_id и owner. затем, если владелец совпадает, вы можете получить ресурс позже, в противном случае вы можете отправить пользователю 403 запрещенный ответ.

Да, как я уже упоминал, я пробовал это, но для многих маршрутов и разных ресурсов вам нужно написать много шаблонного кода...

0x1C1B 04.07.2019 08:45
Ответ принят как подходящий

Для простой операции получения вы можете просто вернуть сообщение, связанное с вашим текущим зарегистрированным пользователем.

@GetMapping
public Post getPost(Authentication authentication) {
    return service.getPostByUser(authentication.getName());
}

Для обновления существующего сообщения вы можете проверить в PreAuthorize, является ли создатель вошедшим в систему пользователем. authentication.getName() возвращает электронное письмо в моем примере

@PutMapping
@PreAuthorize("#post.getCreator() == authentication.getName()")
public void update(@RequestBody Post post, Authentication authentication) {
    service.updatePost(post);
}

Базовый пример использования @Component

@Autowired
private CreatorCheck creatorCheck;

@PutMapping
@PreAuthorize("@creatorChecker.check(#post,authentication)")
public void update(@RequestBody Post post, Authentication authentication) {
    service.updatePost(post);
}

И компонент.. Может быть расширен, чтобы получить исходный пост и проверить этого создателя..

@Component
public class CreatorCheck {

    public boolean check(Post post, Authentication authentication) {
       return post.getCreator().equals(authentication.getName());
    }
}

Для получения более подробного руководства ознакомьтесь с этой ссылкой руководство, найденной 0x1C1B.

Но это означает, что клиент устанавливает создателя сообщения внутри запроса, верно? Как я могу предотвратить, что он манипулирует создателем? Скажем, используя свой собственный идентификатор для предоставления доступа... Или для запроса DELETE нет DTO/полезной нагрузки в качестве аргумента...

0x1C1B 03.07.2019 17:03

Вы можете написать другой общедоступный класс @Component CreatorChecker для обработки проверок. @PreAuthorize("@creatorChecker.check(#post,authentication)") В этом компоненте вы извлекаете сообщение из БД и проверяете создателя service.getPostId().getCreator() == authentication.getName() вот так

Joeri Boons 03.07.2019 17:19

Где используется этот компонент? Как часть цепочки фильтров? Это общий подход, я ничего подобного не нашел... Но, похоже, интересное решение

0x1C1B 04.07.2019 08:50

Он будет оцениваться так же, как некоторые вещи по умолчанию, такие как hasRole('FOO'), поскольку это просто другое выражение, которое необходимо оценить. Ссылка на документы Spring

Joeri Boons 04.07.2019 09:47

Это мое любимое решение... Я нашел базовый пример об использовании внешних компонентов для авторизации. Не могли бы вы обновить свой ответ, чтобы я мог его принять?

0x1C1B 04.07.2019 11:00

Добавлен базовый пример для вашего конкретного варианта использования и ссылка на найденный вами учебник.

Joeri Boons 04.07.2019 11:18

Здесь — небольшой гайд, который помог мне решить эту проблему. Если вы используете репозиторий CRUD:

@Override
@PreAuthorize("#entity.id == authentication.principal.id")
<S extends User> S save(S entity);

Для владельца ресурса и администратора:

@PreAuthorize("#entity.id == authentication.principal.id || hasAuthority('ADMIN')")

поэтому просто переопределите требуемый метод в дочернем репозитории CRUD. Это работает и для @RepositoryRestResource

Другие вопросы по теме