Система безопасности Campus.ru

    При разработке почти любого программного продукта рано или поздно перед разработчиками встает проблема ограничения доступа. Например, в веб-приложении некоторые страницы могут быть доступны только администраторам или только зарегистрированным пользователям. В проекте Campus.ru такое разграничение доступа обеспечивается библиотекой Spring Security.

    Spring Security работает со статическими ролями, которые определяются через URI при каждом запросе со стороны HTTP-клиента. Но как быть с динамическими ролями, которые меняются в зависимости от отношений между текущим пользователем и запрашиваемым объектом (например, роль автора по отношении к статье)?


    Для решения задачи доступа к отдельным сущностям может быть использована система безопасности доменных объектов (Domain Object Security) от Spring. Однако данная система жестко привязана к доменной модели, поддерживает ограниченное количество функций над сущностями (до 32) и достаточно сложна в реализации (7 ключевых интерфейсов).

    Нами было решено разработать новый фреймворк CRMedia.Security, который обладал бы большей гибкостью, поддерживал динамические списки доступа, а также был бы более простым в использовании. Идея предлагаемого нами фреймворка заключается в том, что он позволяет защитить некоторые действия пользователя с определенным набором аргументов, смысл которых известен лишь веб-приложению, но не системе безопасности. Такая модель системы позволяет абстрагироваться от сущностей Campus.ru и использовать фреймворк в других проектах.

    CRMedia.Security получает действие, после чего его аргументы из приложения ищут ограничения на эти действия через PermissionProvider и вычисляют пересечение этих ограничений со списком доступа текущего пользователя, предоставляемого ACLProvider. Если пересечение является пустым множеством, то вызывается обработчик, соответствующий случаю отказа в доступе. В случае непустого пересечения вызов конкретного события Tapestry продолжается.

    Приведем конкретный сценарий, который требует проверки прав: например, просмотр в сообществе с id = 10 статьи под номером 20. В данном случае «просмотр статьи» – это действие, а 10, 20 – его аргументы. Допустим, эта статья доступна только для членов сообщества, а просмотреть ее пытается пользователь, не состоящий в сообществе. Таким образом, PermissionProvider, имплементированный в приложении, должен возвратить ограничение «член сообщества», а ACLProvider – «не состоит в сообществе». Пересечение этих множеств пусто, а, следовательно, доступ к странице поста будет закрыт.

    Рассмотрим программную реализацию системы безопасности с использованием фреймворка CRMedia.Security. Вначале необходимо подключить модуль CRMedia.Security к разрабатываемому приложению. Этот модуль подключает обработчик аннотаций в цепочку преобразователя компонентов Tapestry (ComponentClassTransformWorker).

    Интерфейс провайдера ограничений (PermissionProvider) имеет следующий вид:

    public interface PermissionProvider {

      /**
       * Наложить ограничение на выполнение действия
       * @param action      действие
       * @param permission    ACL
       */
      void restrict(Action action, List<PermissionEntry> permission);

      /**
       * Получить ACL для выполнения указанного действия
       * @param action  действие
       * @return ACL
       */
      List<PermissionEntry> get(Action action);

      /**
       * Снять все ограничения на выполнение указанного действия
       * @param action  действие
       */
      void revoke(Action action);
      
      /**
       * Удалить ограничения на все действия хотя бы с одним аргументом из указанного списка
       * @param params  список именованных аргументов
       */
      void revokeReferenced(Map<String, Object> params);

    }


    * This source code was highlighted with Source Code Highlighter.


    Он может имплементироваться как DAO для хранения ограничений в БД. Здесь Action – это совокупность названия действия и списка именованных аргументов ({view_article; community = 10; article = 20} для приведенного выше примера), а PermissionEntry – это одна запись списка доступа (например, {status = member}).

    ACLProvider содержит единственный метод, с помощью которого CRMedia.Security получает список доступа текущего пользователя:

    public interface ACLProvider {

      /**
       * Получить список проверяемых значений перед выполнением действия для указанного компонента
       * @param component     компонент
       * @param action      действие
       * @return список проверяемых значений; null, если доступ запрещен
       */
      List<PermissionEntry> getACL(Component component, Action action);

    }


    * This source code was highlighted with Source Code Highlighter.


    Для нашего примера на действие {view_article; community = 10; article = 20} метод getACL должен возвращать множество {status = nonmember} на основании соотношений между текущим пользователем и указанным сообществом.

    Теперь необходимо сообщить ядру безопасности, какие именно события следует защитить. Для этого необходимо расставить аннотации CRMedia.Security.

    Пусть пост просматривается через страницу ViewArticle. При обращении к ней по правилам Tapestry вызывается метод onActivate. Чтобы ограничить к нему доступ, нам нужно расставить аннотации таким образом:

    public class ViewArticle {

      @Restricted(action = "view_article")
      Object onActivate(@SecuredParam("community") Community community,
              @SecuredParam("article") Article article) {

        ...

      }

    }


    * This source code was highlighted with Source Code Highlighter.


    Аннотация Restricted ставится перед событием, которое имеет ограничение. Каждое событие соответствует одному действию (в данном случае это «view_article») с определенным набором аргументов («community» и «article»). Аргументы из этого сопровождаются аннотациями SecuredParam.

    По умолчанию при отказе в доступе клиент получает ответ от сервера с кодом 403 (Forbidden). В случае необходимости можно использовать свой обработчик:

    public class ViewArticle {

      @Restricted(action = "view_article")
      void onActivate(@SecuredParam("community") Community community,
              @SecuredParam("article") Article article) {

        ...

      }

      Object onForbidForListArticles() {
          return new TextStreamResponse("text/plain", "access denied");
      }

    }


    * This source code was highlighted with Source Code Highlighter.


    CRMedia.Security поддерживает вложенные свойства в проверяемых аргументах. Например, в следующем примере параметр «community» берется из свойства community сущности Article:

    public class Article {
      
      public Community getCommunity() {
        …
      }
      
    }

    public class ViewArticle {

      @Restricted(
        action = "view_article",
        params = {
              @SecuredProp(name = "community", paramProp = "article.community")
        }
      )
      Object onActivate(@SecuredParam("article") Article article) {

        ...

      }

    }


    * This source code was highlighted with Source Code Highlighter.


    Если страница сохраняет свое состояние между запросами, то значения аргументов действия для защищенного события («view» в примере ниже) можно брать из самой страницы:
    public class ViewArticle {

      @Property
      @Persist
      private Article article;

      @Restricted(
        action = "view_article",
        params = {
            @SecuredProp(name = "article", pageProp = "article"),
            @SecuredProp(name = "community", paramProp = "article.community")
        }
      )
      Object onView() {

        ...

      }

    }


    * This source code was highlighted with Source Code Highlighter.


    Помимо ограничений на события, можно выставлять ограничение на просмотр частей страницы. Для этого во фреймворке существует компонент IfCan, который полностью аналогичен по использованию в шаблоне штатному компоненту If из Tapestry Core:

    <html xmlns="http://www.w3.org/1999/xhtml" xmlns:t="http://tapestry.apache.org/schema/tapestry_5_0_0.xsd">
      <body>
        <t:ifCan actionName="view_article" context="viewArticleContext">
          <t:eventlink event="view">Открыть пост</t:eventlink>
          <t:parameter name="else">
            Просмотр запрещен
          </t:parameter>
        </t:ifCan>
      </body>
    </html>


    * This source code was highlighted with Source Code Highlighter.


    Свойство viewArticleContext аналогично аннотациям SecuredProp определяет список защищенных параметров:

    public class ViewArticle {

      @Property
      @Persist
      private Article article;

      public Object[] getViewArticleContext() {
        return new Object[]{"article", article, "community", article.getCommunity()};
      }

      @Restricted(
        action = "view_article",
        params = {
            @SecuredProp(name = "article", pageProp = "article"),
            @SecuredProp(name = "community", paramProp = "article.community")
        }
      )
      Object onView() {

        ...

      }

    }


    * This source code was highlighted with Source Code Highlighter.


    Таким образом, разработанный фреймворк обладает достаточной степенью абстракции, позволяет защитить от несанкционированного выполнения различные события на страницах Tapestry и настраивать отображение страницы в зависимости от имеющихся у пользователя прав.
    Creative Media
    0,00
    Компания
    Поделиться публикацией
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

    Комментарии 20

      0
      > Но как быть с тем, что требуемые права на доступ к некоторым объектам приложения могут постоянно меняться
      Как часто меняются? Я так понимаю, чаще всего эта задача решается кешированием прав на определенный период. Не проще ли такой подход?
        0
        В статье была допущена неточность. Идея заключалась в том, что одним сопоставлением текущего пользователя с запрашиваемым адресом (URI) не обойтись. Например, пользователи могут зайти по одному и тому же адресу сообщества, но при этом настройки сообщества видят только модераторы, а обычные пользователи — нет.
          0
          Не очень понял вашу мысль.
          Урл — по логике вещей — это всего лишь путь к функции и ее агрументы. Поэтому одного урла мало для безопасного приложения- — надо проверять, есть ли доступ к нему.
            +1
            Не всегда в адресе содержится полный набор аргументов. Рассмотрим конкретный пример. Допустим, пользователь с id = 1 пытается просмотреть альбом сообщества 10 по адресу:

            www.campus.ru/gallery/viewalbum/20

            Здесь 20 — это идентификатор альбома. Допустим, модератор сообщества разрешил просматривать альбомы в сообществе 10 только пользователям 2 и 3. Тогда в таблице пермиссий появится запись вида

            action = view_album, community = 10, acl = {2, 3}

            И для функции view_album аргументом будет являться идентификатор сообщества. В адресе же содержится идентификатор альбома. Ситуация легко разруливается CRMedia.Security:

              0
              @Restricted(
              action = «view_album»,
              params = {
              @SecuredProp(name = «community», paramProp = «album.community»)
              }
              }
              Object onActivate(@SecuredParam(«album») Album album) {...}
                0
                Более того, аргументы функции могут быть вообще не связаны с адресами. Они могут быть получены из внутреннего состояния страницы (из свойств с аннотацией @Persist). Особенно это актуально для AJAX-запросов.
              0
              А так, конечно, задача уменьшения нагрузки может решаться кэшированием. Проверка прав производится довольно часто, поэтому необходимо хорошо оптимизировать работу системы, например, создать индексы на таблицу разрешений, установить кэш и т.д.
              +2
              Будьте добры, подсветите код.
                0
                Подсветил
                0
                А я правильно понял, что библиотека стала свободно распространяемой? А то из приведенного кода не видно всей мощи данной системы. Да и не понятно тогда к чему такая мега универсальность вообще.
                  0
                  Мы собираемся в ближайшем будущем выложить некоторые наши проекты на Google Code (среди которых и CRMedia.Security).

                  Система разрабатывалась под наши нужды. Так уж получилось, что она стала универсальна )
                    0
                    Привет. Вы хотели выложить CRMedia.Security в google.code. Интересно посмотреть. Вы определились, когда вы это сделаете?
                  0
                  Интересно, буду делать подобное. Но только аннотации с ограничениями применю на методы сервисов, зарегистрированных в IoC. Тем самым будет работать при доступе к данным не через страницы tapestry. Плюс сделаю это на базе Ki (JSecurity).
                    0
                    Как успехи? В связи с чем было принято решение налагать ограничения на методы сервисов, а не на события компонентов?

                    Спасибо, что упомянули Ki — теперь есть соблазн о переезде со Spring на него :) Особенно порадовала поддержка Ki мгновенных изменений ролей, а это открывает широкие возможности для интеграции с CRMedia.Security.
                      +1
                      К этому еще не подошёл, поэтому успехов нет. Сейчас много возни с архитектурой платформы. Применять буду как на методы сервисов, так и на страницы (компонеты, микшены), тем более это делается просто :)

                      Ki мне понравился возможностью работы в stanalone режиме без контейнера сервлетов. А когда он начал переходить под крышу Apache, то сомнения выбора отпали окончательно. А Spring я решил не использовать, вся платформа будет строится на Tapestry5 IoC. Может я что-то не понимаю, но у меня сделано несколько проектов без Spring, по сути там только интересен DAO и Acegi. Все остальное overhead для большинства веб 2.0 проектов. Замена DAO у меня есть, осталось повозиться с Ki и плотно вкрутить в Tapestry5 :)
                        0
                        Про Spring — в самую точку. Удачи Вам в разработке :)
                          0
                          Спасибо :) Я как основные моменты сделаю выложу это под Apache 2.0 лицензией и на хабре статью опубликую.
                            0
                            Буду ждать )
                    0
                    Блин как все сложно, тяжело и некрасиво делается когда по науке
                    owner,group,chmod X простейшие битовые операнды = 99% всех потребностей
                    и отдельной веткой moderator\supermoderator\etc

                      0
                      Да, в 99% случаях это работает. Но только в 99% :) Да и разве не проще будет модератору (как и программисту) не думать о том, к чему относится, например, операция вступления в сообщество в терминах chmod — к чтению или записи?

                      Программирование на высоких уровнях приближает разработчика к естественному языку.

                    Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                    Самое читаемое