Я создаю основанный на сообществе сайт в Rails для членов реальной организации. Я пытаюсь придерживаться лучших практик дизайна RESTful, и большинство из них более или менее посторонние. Проблема, которая заставляет мой мозг работать в аккуратных кругах RESTful, заключается в авторизации. Аутентификация - это легкая, давно решаемая проблема с широко распространенными решениями RESTful, но авторизация RESTful кажется чем-то вроде черного искусства. Я пытаюсь найти подход, который обеспечит наиболее общую и гибкую структуру для управления доступом к ресурсам, при этом будучи максимально простым, и при этом соответствует архитектуре RESTful. (Также пони.)
Вопросы:
- Мне нужно контролировать доступ к различным ресурсам, таким как «Пользователи», «Страницы», «Сообщения» и т. Д.
- Авторизация для данного ресурса должна быть более детальной, чем простой CRUD.
- Я хочу позволить себе и другим редактировать правила авторизации из приложения.
- Правила авторизации должны иметь возможность зависеть от предикатов, таких как (концептуально) Владелец (Пользователь, Ресурс) или Заблокировано (Тема)
Вопрос (2) касается меня больше всего. Кажется, существует несоответствие импеданса между моей концепцией разрешений и RESTful концепцией действий. Например, возьмите сообщения (как на доске объявлений). REST определяет наличие четырех операций с ресурсом Post: создание, чтение, обновление и удаление. Просто сказать, что пользователь должен иметь возможность обновлять свои собственные сообщения, но только определенным пользователям (или ролям, или группам) следует разрешить их блокировать. Традиционный способ представления блокировки находится в состоянии Почты, но это приводит к появлению запаха, что Пользователь в тех же условиях может или не может обновить Почту в зависимости от (полностью действительных) значений, которые он предоставляет. Мне кажется ясным, что на самом деле есть два разных действия, чтобы изменить состояние Почты, и подать их в чистом виде - просто замаскировать нарушение принципов RESTful.
(Следует заметить, что эта проблема весьма отличается от проблемы сбоя обновления из-за неверных или несогласованных данных - запрос блокировки от непривилегированного пользователя в принципе вполне допустим, просто запрещен.)
Не является ли разложение еще одним словом для гнили?
Это может быть преодолено путем декомпозиции поста: блокировка - это подресурс определенного поста, и для создания или уничтожения можно иметь отдельные разрешения. Это решение имеет кольцо REST, но несет в себе как теоретические, так и практические трудности. Если я исключу блокировки, то как насчет других атрибутов? Предположим, я решил в капризе, что только члену Администратора может быть разрешено изменять заголовок Поста? Простое изменение в авторизации тогда потребовало бы реструктуризации базы данных, чтобы приспособить это! Это не очень много решения. Чтобы обеспечить такую гибкость в рамках стратегии декомпозиции, требуется, чтобы каждый атрибут был ресурсом. Это представляет собой небольшую дилемму. Я неявно предполагал, что ресурс представлен в базе данных в виде таблицы. Согласно этому предположению, ресурс для каждого атрибута означает таблицу для каждого атрибута. Понятно, что это не практично. Однако устранение этого предположения представляет собой несоответствие импеданса между таблицами и ресурсами, что может открыть собственную червячную червь. Использование этого подхода потребовало бы гораздо более глубокого рассмотрения, чем я его дал. С одной стороны, пользователи ожидают, что смогут редактировать несколько атрибутов одновременно. Куда идет запрос? Наименьший ресурс, который содержит все атрибуты? Для каждого отдельного ресурса параллельно? На луну?
Некоторые из этих вещей не похожи на другие ...
Предположим, что я не разлагаю атрибуты. Кажется, что альтернативой является определение набора привилегий для каждого ресурса. Однако в этот момент однородность REST теряется. Чтобы определить правила доступа для ресурса, система должна обладать конкретными знаниями о возможностях этого ресурса. Кроме того, теперь невозможно обобщенно распространять разрешения на ресурсы-потомки - даже если у дочернего ресурса была привилегия с тем же именем, между привилегиями нет внутренней семантической связи. Определение REST-подобного набора стандартных привилегий кажется мне худшим из обоих миров, поэтому я застрял с отдельной иерархией разрешений для каждого типа ресурса.
Ну, мы сделали нос. И шляпа. Но это ресурс!
Одно замечание, которое я видел, что смягчает некоторые недостатки вышеупомянутого подхода, - это определение разрешений как создание / удаление для ресурсов и чтение / запись для атрибутов . Эта система представляет собой компромисс между атрибутами-ресурсами-ресурсами и привилегиями-ресурсами: в одной из них по-прежнему остается только CRUD, но для целей авторизации чтение и обновление относятся к атрибутам, которые можно рассматривать как псевдоресурсы. Это обеспечивает многие практические преимущества подхода «атрибуты как ресурсы», хотя концептуальная целостность в определенной степени нарушена. Разрешения могут по-прежнему распространяться от ресурса к ресурсу и от ресурса к псевдо-ресурсу, но никогда от псевдо-ресурса. Я не полностью изучил последствия этой стратегии, но кажется, что она может быть многообещающей. Мне приходит в голову, что такая система лучше всего функционирует как неотъемлемая часть Модели. Например, в Rails это может быть модификация ActiveRecord
. Мне это кажется довольно радикальным, но авторизация является настолько фундаментальной сквозной проблемой, что это может быть оправдано.
О, и не забывайте про пони
Все это игнорирует проблему предикативных разрешений. Очевидно, что пользователь должен иметь возможность редактировать свои собственные сообщения, но никого другого. Также очевидно, что таблица разрешений, написанная администратором, не должна иметь отдельных записей для каждого пользователя. Это вряд ли необычное требование - уловка делает его общим. Я думаю, что всю необходимую мне функциональность можно получить, сделав только предикат правил , чтобы можно было быстро и сразу определить применимость правила. Правило "allow User write Post where Author(User, Post)
" будет переводиться в "for all User, Post such that Author(User, Post), allow User write Post
", а "deny all write Post where Locked(Post)
" в "for all Post such that Locked(Post), deny all write Post
". (Было бы grand , если бы все такие предикаты могли быть выражены в терминах одного пользователя и одного ресурса.) Концептуально вытекающие "окончательные" правила были бы не предикативными. Это поднимает вопрос о том, как реализовать такую систему. Предикаты должны быть членами классов Model, но я не уверен, как можно изящно ссылаться на них в контексте правил. Чтобы сделать это безопасно, потребуется какое-то отражение. Здесь я снова чувствую, что для этого потребуется модификация реализации модели.
Как вы пишете это снова?
Последний вопрос - как лучше всего представить эти правила авторизации в качестве данных. Таблица базы данных может помочь, с помощью столбцов enum для allow / deny и C / R / U / D (или, возможно, битов CRUD? Или, возможно, {C, R, U, D} × {allow, deny, наследовать}?) и столбец ресурса с путем. Возможно, для удобства, немного «унаследовать». Я в растерянности, насколько предикаты. Отдельная таблица? Конечно, много кеширования, чтобы оно не было слишком безбожно медленным.
Полагаю, об этом много просят. Я пытался сделать свою домашнюю работу, прежде чем задавать вопрос, но на данный момент мне действительно нужна внешняя перспектива. Буду признателен за любой вклад, который любой из вас может иметь для решения проблемы.