Контроль доступа в ASP.NET MVC с использованием [Flags] на основе Enum для управления разрешениями int в SQL

Этот вопрос вызван этим вопросом SO относительно контроля доступа в ASP.NET MVC. Здесь я пытаюсь привести принятый ответ в осязаемое решение.

В ответе упоминается использование FileSystemSecurity в качестве источника вдохновения для управления разрешениями. Здесь я также использую перечисление с атрибутом Flags для определения ACL для всех моих объектов. Кроме того, каждый член моих объектов будет храниться в столбце в SQL. Предположим, что используется упрощенное сопоставление ORM Linq2SQL, EF или nHibernate.

Изменить: добавлено следующее преимущество / обоснование этого подхода

Эта модель безопасности была основана на FileSystemRights , подход .NET к управлению разрешениями на уровне файлов.

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

List<myObject> PermissionsList  = GetACLForObject(ItemID, UserID);
foreach (var acl in PermissionsList)
{
   // The following enum has the [FlagsAttribute] applied so the .ToString() is pretty 
   PermissionWithFlagsEnum sampleForSO = (PermissionWithFlagsEnum )acl.Permission;

   Console.Writeline ("Setting " + sampleForSO.ToString() + " permission for group: " + acl.ACLName);

   ResultingPermission = resultPermission |  acl.Permission ; 
}
public int ResultingPermission {get;set;}

/ Конец редактирования

Затем мне пришло в голову, что я могу сравнить менее привилегированных пользователей по числовому значению enum с более привилегированными пользователями enum.

Я думаю, что это определение позволит быстро и легко идентифицировать пользователей в базе данных SQL без необходимости анализировать перечисление на бэкэнде. (Найдите непривилегированных пользователей через select users from permissions where security < DBAceessRights.DetailRead)

Вот как я определил флаги (наименьшее значение имеет наименьшее разрешение)

[Flags]
public enum DBAccessRights
{
    DenyAll  =1 << 1,

    WikiMode = 1 << 4,

    AppearInListing = 1 << 8,

    DetailRead = 1 << 12,

    CreateNew = 1 << 18,

    DetailEdit = 1 << 22,

    DeleteChild = 1 << 26,

    DeleteThis = 1 << 30,

    EditPermissions = 1 << 31,

}

У меня есть таблица разрешений, которая объединяет идентификатор пользователя с ACE для конкретного объекта. Это должно снизить потребность в одновременных обновлениях определенной строки. введите описание изображения здесь

Вопрос

  • Это хорошая идея?

  • Кто-то делал это раньше (лучше, чем это)? (Изменить: Это принятый ответ здесь)

  • Если это стандартный способ реализации разрешений, как он называется?


person halfbit    schedule 02.11.2011    source источник
comment
Обратите внимание: вы не сможете эффективно использовать индекс для запроса комбинаций флагов - это нормально?   -  person Marc Gravell    schedule 03.11.2011
comment
Также будет очень сложно проводить аудит изменений ACL.   -  person Conrad Frix    schedule 03.11.2011
comment
@MarcGravell Я могу запросить, используя относительное значение перечисления, преобразованное в int. Это должно эффективно использовать индекс, хотя я придумываю этот подход на лету.   -  person halfbit    schedule 03.11.2011
comment
@MarcGravell Я удалил сериализацию и заменил термин ORM в целом   -  person halfbit    schedule 03.11.2011
comment
Почему такие большие цифры? вместо этого вы можете использовать 1,2,4,8, ....   -  person Saeed Amiri    schedule 03.11.2011
comment
@Saeed Amir - Я не завершил модель разрешений. Таким образом, я могу вставить разрешение между существующими завивками, если я решу сделать это позже. Я определенно открыт для предложений о том, какие химические завивки включить помимо этих.   -  person halfbit    schedule 03.11.2011
comment
Указатели / примеры для других разрешений будут оценены   -  person halfbit    schedule 03.11.2011
comment
@MarcGravell - Поскольку я использую групповые идентификаторы GUID для применения разрешений к объектам, я не думаю, что мне нужно запрашивать комбинации флагов. Если я просто запрошу группы, а затем проанализирую флаги, я думаю, что это подходящий компромисс.   -  person halfbit    schedule 03.11.2011
comment
Связано: я объединяю таблицы вместе через Linq и EF во время выполнения: stackoverflow.com/q/8016773/328397   -  person halfbit    schedule 05.11.2011


Ответы (2)


Я бы сказал нет.

  1. Вы предполагаете, что вам понадобится не более 32 (или 64, если вы используете bigint) привилегии. Для меня это звучит произвольно. Перемещение к varbinary в базе данных может преодолеть это, но тогда ваше перечисление будет вверх по ручью (не может создавать перечисления на byte[])! И вы не сможете проводить числовые сравнения.

  2. Вы предполагаете, что привилегия 0x1 всегда будет логически меньше, чем 0x2, 0x80 и так далее. По моему опыту, такое бывает редко. Чаще всего привилегии совершенно независимы друг от друга (то есть: наличие привилегии «добавить пользователя» не имеет ничего общего с привилегией «загружать изображение»; одна группа пользователей (администраторы) будет иметь первое, а другие (издатели контента) будут иметь Это означает, что ваше числовое сравнение не так полезно, как вы думали вначале.

  3. То, что вы делаете, может дать выигрыш в производительности, но вы еще не продемонстрировали, что существует проблема с производительностью! Большинство моих систем привилегий работают с одной записью в базе данных для каждого пользователя, чтобы предоставить или запретить каждую привилегию. Получение 100 записей не требует больших затрат на мою базу данных. Лучше просто кэшировать разрешения каждого пользователя между запросами, чем использовать битовую маску.

  4. Что касается производительности обновления: как часто все равно меняются разрешения пользователей? По моему опыту, когда система установлена, они, как правило, становятся довольно статичными.

Я обнаружил, что битовые маски наиболее полезны при попытке упаковать большое количество данных в небольшое пространство. Но часто моя точка 1 возвращается, чтобы укусить меня, когда я получаю более 64 вещей.

Обратите внимание, что я использовал эту технику при записи статистики действий пользователя (отслеживая, какие элементы пользователи находят при поиске, какие объекты они просматривают и т. Д.). Моя причина заключалась исключительно в том, чтобы убедиться, что длина записей базы данных была фиксированной и небольшой, чтобы вставка была быстрой. И я не делал числовых сравнений. (И, честно говоря, я никогда не проверял, есть ли разница между int столбцом и несколькими bit столбцами).

ИЗМЕНИТЬ. Базовая альтернатива (которую я использую): отношение M: N между пользователями и привилегиями (я называю их правами).

Схема доступа пользователей к правам БД

(Прошу прощения за уши Микки Мауса на моем пользователе!)

Наличие записи в UserRight указывает на то, что этому пользователю предоставлено право. Отсутствие указывает на отсутствие права. Этот запрос дает вам все права, назначенные пользователю.

SELECT [dbo].[User].Username, [dbo].[Right].Id, [dbo].[Right].Name
FROM [dbo].[Right]
INNER JOIN [dbo].[UserRight] ON [dbo].[Right].Id = [dbo].[UserRight].RightId
INNER JOIN [dbo].[User] ON [dbo].[User].Id = [dbo].[UserRight].UserId
WHERE [dbo].[User].Id = @pUserId

Затем в коде для подтверждения права пользователя:

var grantedRights = RunTheAboveQuery(currentUser.Id);
if (grantedRights.Any(r => r.Id == requiredRight))
    // User has the right.
else
    // User does not have the right. 

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

Это не искусственно ограничивает количество привилегий, поддерживаемых вашей системой. И он не предполагает каких-либо отношений между привилегиями (поэтому вы не можете проводить числовые сравнения; в моей системе все выполняется IEnumerable<Right>). И, если вам действительно нравятся битовые маски, вы можете создать Dictionary<User, BitArray> в кеше!

У меня также есть концепция Role, которая предоставляет пользователям логическую группу прав. Это просто еще одна таблица M: N. Это упражнение для читателя!

person ligos    schedule 02.11.2011
comment
Спасибо! Любая альтернативная реализация приветствуется. Чтобы вы посоветовали? - person halfbit; 03.11.2011
comment
@makerofthings: Я обновил свой ответ, включив в него альтернативу. Ничего особенного, но не страдает упомянутыми мною проблемами. - person ligos; 03.11.2011
comment
Да, и вы можете удалить два соединения из этого запроса, если вас интересует только сопоставление UserId с RightId. Должен работать так же, как ваш исходный запрос. - person ligos; 03.11.2011

В общем, размещение нескольких значений (флагов) в одном поле - плохая идея.

Если вы планируете проводить аудит этих данных, это действительно плохая идея, поскольку сложно эффективно определить, какие флаги меняются от обновления к обновлению.

Проблемы, с которыми вы можете столкнуться, даже если не проводите аудит.

  • Многие простые запросы (какие пользователи используют авторизацию DeleteThis) не являются SARGable, потому что вам необходимо выполнить побитовая операция перед сравнением.

  • Также select users from permissions where security < DBAceessRights.DetailRead может не возвращать правильные результаты, потому что AppearInListing & CreateNew больше, чем DetailRead, но не имеет включенного DetailRead. Таким образом, выгода, на которую вы надеялись, вы можете не получить

  • Управление параллелизмом (множественные записи в ACL) сложнее, поскольку изменение одного «логического значения» фактически изменяет все значения.

person Conrad Frix    schedule 02.11.2011
comment
Никогда не слышал о SARG ... это инструмент Sourceforge? - person halfbit; 03.11.2011
comment
Извините добавил ссылку. Это сокращение от Search ARGument Able. По сути, это означает, что он не сможет использовать индексы. Между прочим, последний проект, над которым я работал, сделал это, и мы справились со всеми проблемами, которые я описал. - person Conrad Frix; 03.11.2011
comment
Спасибо за понимание, это недостаток, но не препятствие. Какие альтернативы вы смотрели. Что касается параллелизма, у меня есть таблица пользователей (гиперссылка в вопросе), которая должна уменьшить вероятность конфликтующих обновлений. - person halfbit; 03.11.2011
comment
к сожалению imgur заблокирован там, где я. Но простого отношения "многие ко многим" должно хватить. например Пользователи, авторизации (ваше перечисление), авторизации пользователей. Это не избавляет от проблемы параллелизма (ничего не помогает), но это делает меньшую проблему ИМХО - person Conrad Frix; 03.11.2011