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

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

WebDEV

Основные сценарии следующие:

  1. ограничение доступа к данным для пользователей не прошедших аутентификацию
  2. ограничение доступа к данным для аутентифицированных, но не обладающих необходимыми привелегиями пользователей
  3. предотвращение несанкционированного доступа с помощью прямых обращений к API
  4. фильтрация данных в поисковых запросах и списковых элементах UI (таблицы, списки)
  5. предотвращение изменения данных, принадлежащих одному пользователю другими пользователями

Сценарии 1-3 хорошо описаны и обычно решаются с помощью встроенных средств фреймворков, например role-based или claim-based авторизации. А вот ситуации, когда авторизованный пользователь может по прямому url получить доступ к данным «соседа» или совершить действие в его аккаунте случаются сплошь и рядом. Происходит это чаще всего из-за того что программист забывает добавить необходимую проверку. Можно понадеяться на код-ревью, а можно предотвратить такие ситуации применив глобальные правила фильтрации данных. О них и пойдет речь в статье.


Списки и таблицы

Типовой контроллер для получения данных в ASP.NET MVC может выглядеть как-то так:

В данном случае вся ответственность за фильтрацию данных ложится только на программиста. Вспомнит ли он о том, что необходимо добавить условие в Where или нет?

Можно решить проблему с помощю глобальных фильтров. Однако, для ограничения доступа нам потребуется информация о текущем пользователе, а значит конструирование DbContext придется усложнить, чтобы проинициализировать конкретные поля. 

Если правил много, то реализации DbContext неизбежно придется узнать «слишком много», что приведет к нарушению принципа единственной ответственности.


Слоеная архитектура

Проблемы с доступом к данным и копипастой возникли, потому что в примере мы проигнорировали разделение на слои и из контроллеров сразу потянулись к слою доступа к данным, минуя слой бизнес-логики. Такой подход даже окрестили «толстыми тупыми уродливыми контроллерами». В этой статье я не хочу касаться вопросов, связанных с репозиториями, сервисами и структурированием бизнес-логики. Глобальные фильтры хорошо справляются с этой задачей, нужно только применить их к абстракции из другого слоя.


Добавляем абстракцию

В .NET для доступа к данным уже есть IQueryable. Заменим прямой доступ к DbContext на доступ вот к такому провайдеру:

А для доступа к данным сделаем вот такой фильтр:

Реализуем провайдер таким образом, чтобы он искал все объявленные фильтры и автоматически применял их:

Код получения и создания фильтров в примере не оптимален. Вместо Activator.CreateInstance а лучше использовать скомпилированные Expression Trees. В некоторых IOC-контейнерах реализована поддержка регистрации открытых generic'ов. Я оставлю вопросы оптимизации за рамками этой статьи.


Реализуем фильтры

Реализация фильтра может выглядеть, например, так:

Исправляем код контроллера

Изменений совсем не много. Осталось запретить прямой доступ к DbContext из контроллеров и если фильтры правильно написаны, то вопрос доступа к данным можно считать закрытым. Фильтры достаточно маленькие, поэтому покрыть их тестами не составит труда. Кроме того эти-же самые фильтры можно использовать, чтобы написать код авторизации, предотвращающий несанкционированный доступ к «чужим» данным.

Report Page