Содержание


Использование бизнес-правил в качестве механизма авторизации

Comments

В статье описывается, как использовать механизм бизнес-правил Nools при принятии решений об авторизации в приложении на базе Node.js. Это позволяет изменять политику авторизации приложения без внесения изменений в его исходный код, что значительно упрощает поддержание политики в актуальном состоянии.

Что вам потребуется для создания своего приложения

  • Учетная запись Bluemix
  • Знание HTML, JavaScript и стека веб-приложений MEAN
  • Среда разработки, способная загрузить приложение Node.js в Bluemix, например, Eclipse

Версии приложения

Рассматриваемое в статье приложение имеет две демонстрационные версии. Первая версия, соответствующая приложению с жестко встроенной авторизацией, иллюстрирует исходную политику безопасности. Вторая версия, соответствующая приложению после введения авторизации на основе бизнес-правил, иллюстрирует гораздо более гибкую политику безопасности. Обратите внимание, что политика безопасности во второй версии приложения является общим ресурсом, поэтому вам не следует полагаться на то, что она всегда будет оставаться такой же, как в начальных разделах этой статьи.

Запустить первоначальное приложение Запустить измененное приложение

В этой статье я показываю, как реализовать политики приложения Node.js в виде базы правил и как реализовать пользовательский интерфейс для этой базы правил. Это существенно упрощает внесение изменений в политику авторизации и избавляет от необходимости привлечения программистов для решения этой задачи

Демонстрационное приложение

Функциональность базы правил моего приложения демонстрируется на примере вымышленного банка под названием World's Silliest Bank (Самый глупый банк в мире). Система этого банка доступна через Интернет для кассиров и клиентов. Пользователям предоставлено право выбрать свои идентификационные данные в меню в верхней части окна браузера. После этого браузер отправляет серверу REST-запрос на отображение списка счетов и балансов по этим счетам. Сервер принимает решение относительно того, какие списки счетов должны быть видны пользователю и должны ли быть видны балансы этих счетов, а затем отсылает ответ с информацией, которую может видеть пользователь. Браузер отображает эту информацию.

На данный момент функция authorize возвращает значение true в следующих двух случаях.

  • Если субъект (пользователь приложения) имеет роль Teller (кассир) и банковское отделение субъекта совпадает с банковским отделением объекта (счет). Это позволяет кассирам видеть все счета в своем отделении и показывать клиентам их балансы.
  • Если субъект имеет роль Customer (клиент) и субъект совпадает с объектом. Это позволяет клиентам видеть свои балансы.
 var authorizeAction = function(subject, verb, object) { // Получить дополнительную информацию var subjectInfo = users[subject]; var objectInfo = users[object]; // Позволить клиентам видеть свой баланс, а кассирам видеть балансы всех клиентов своего отделения. if (subjectInfo.role == "Teller") return subjectInfo.branch == objectInfo.branch; else if (subjectInfo.role == "Customer") return subject == object; // Если нет правила, разрешающего доступ, отказать в доступе. return false; };

Функция authorize игнорирует глагол, поскольку у приложения есть только один глагол, предназначенный для отображения баланса счета.

Шаг 1. Начало использования механизма правил Nools

Политика, реализуемая в этом примере, весьма проста, поэтому я выбрал Nools – по-видимому, самый популярный механизм правил для платформы Node.js (механизмы правил обычно реализуются как библиотеки, что обуславливает их зависимость от конкретных языков). Чтобы узнать больше об этом механизме правил, обратитесь на страницу Nools.

  1. Чтобы сделать механизм правил доступным, добавьте запись "nools": "*" в раздел dependencies файла package.json.
  2. Чтобы использовать механизм правил, добавьте следующий код в файл app.js. Пока этот код ничего не делает.
     // Использовать библиотеку Nools var nools = require('nools'); // Создать новый поток, базу правил. // На данный момент оставить базу правил пустой. var flow = nools.flow("authz", function(flow) { ; }); // Создать новый сеанс. Чтобы прийти к решению, сеанс сочетает базу правил с фактами. var session = flow.getSession(); // Добавить факты в сеанс session.assert("Hello"); session.assert("Goodbye"); // Попытаться использовать базу правил session.match().then( function() { console.log("Successfully ran the flow"); }, function(err) { console.log("Error" + err); } ); // Ликвидировать сеанс, удалить все факты, чтобы обеспечить возможность будущего использования session.dispose();
  3. Отправьте приложение (загрузите приложение и запустите его).
  4. Просмотрите содержимое файла журнала. Если вы используете Eclipse, оно находится в окне консоли. Если вы используете интерфейс командной строки cf, выполните следующую команду:
    cf logs <application name> --recent
  5. Убедитесь в получении сообщения об успехе (Success in running the flow).

Шаг 2. Создание классов объектов

Факты, которые механизм Nools использует для оценки правил или в качестве заключений, могут храниться в объектах. Для принятия решений об авторизации проще всего иметь два класса:

  • AuthzRequest – класс для запроса информации (включая любую релевантную дополнительную информацию)
  • AuthzResponse – класс для ответа

Добавьте следующий код в файл app.js, поместив его перед кодом шага 1.

 // Класс объекта для запроса на авторизацию var AuthzRequest = function(subject, verb, object) { this.subect = subject; this.verb = verb; this.object = object; }; // Класс объекта для ответа относительно авторизации var AuthzResponse = function(answer) { this.answer = answer; }

Классы объектов определяются с помощью стандартного механизма JavaScript –конструктора, заполняющего необходимые поля.

Шаг 3. Создание разрешающего потока

Для этого шага создайте разрешающий поток и запустите его.

  1. Измените определение переменной flow, добавив к потоку разрешающее правило:
     // Создать новый поток, базу правил. // Сделать правило разрешающим var flow = nools.flow("authz", function(flow) { this.rule("Permissive", // Имя правила // TФакты, на основе которых функционирует правило. Если в списке фактов имеется два элемента, то первый элемент – это класс объекта, а второй элемент – это имя переменной. Если в этом списке три элемента, то третий элемент – это условие, которое должно оценивать истинность правила, подлежащего применению. // // Если факт всего один, он может находиться в одноуровневом списке; вложенный список используется здесь лишь для иллюстрации общего случая с несколькими фактами. [[AuthzRequest, "req"]], // Функция, вызываемая в случае соблюдения правила function(facts) { // Этот параметр содержит факты. // Подтвердить, что мы получили параметр console.log(facts.req.verb); // Всегда разрешать this.assert(new AuthzResponse(true)); } ); });
  2. Замените два существующих вызова session.assert одним вызовом, включающим AuthzRequest.
     // Добавить факт AuthzRequest в сеанс session.assert(new AuthzRequest("subject", "verb", "object"));
  3. После вызова session.match добавьте запрос, чтобы прочитать результат.
     console.log(session.getFacts(AuthzResponse));
  4. Отправьте программный код и просмотрите журнал. Убедитесь в том, что вы получаете строку, подобную приведенной ниже.
     2015-05-27T20:20:50.64-0500 [App/0] OUT [ { answer: true } ]

Шаг 4. Изменение функции authorizeAction с целью вызова механизма правил

  1. Удалите из файла app.js все вызовы, которые обращаются к переменной session. Они были нужны для изучения механизма правил, а сейчас они больше не требуются.
  2. Замените функцию authorizeAction следующим кодом.
     // Функция, осуществляющая фактическую авторизацию пользователя (субъекта) для выполнения неких действий, таких как просмотр баланса (глагол) счета (объект). var authorizeAction = function(subject, verb, object) { // Получить дополнительную информацию var subjectInfo = users[subject]; var objectInfo = users[object]; // Добавить имена к информации, чтобы облегчить использование базы правил. subjectInfo.name = subject; objectInfo.name = object; // Создать новый сеанс. Чтобы прийти к решению, сеанс сочетает базу правил с фактами. var session = flow.getSession(); // Добавить факт AuthzRequest в сеанс session.assert(new AuthzRequest(subjectInfo, verb, objectInfo)); // Вызвать поток для принятия решения session.match().then( function() { console.log("Successfully ran the flow"); }, function(err) { console.log("Error" + err); } ); // Получить решение. session.getFacts(<type>) получает все факты этого типа. В данном случае имеется один класс AuthzResponse. var resultList = session.getFacts(AuthzResponse); var decision; if (resultList.length == 0) // Если никакое правило не инициировано, то класса AuthzResponse не будет. // Если нет правила, разрешающего действие, действие запрещается. decision = false; else decision = resultList[0].answer; // Ликвидировать сеанс, удалить все факты, чтобы обеспечить возможность будущего использования session.dispose(); return decision; };
  3. Отправьте приложение.
  4. Используйте приложение. Убедитесь в том, что можете просмотреть все счета, вне зависимости от выбранного вами пользователя.

Шаг 5. Возвращение к исходной политике

Чтобы вернуться к исходной политике, замените вызов nools.flow вызовом, в политике которого имеются правила.

 // Создать новый поток, базу правил. var flow = nools.flow("authz", function(flow) { this.rule("Teller", // Имя правила // Обратите внимание на добавленный третий элемент списка, ограничивающий это правило случаями, в которых субъект является кассиром. [[AuthzRequest, "req", "req.subject.role=='Teller'"]], // Функция, вызываемая в случае соблюдения данного правила function(facts) { // Разрешить субъекту и объекту относиться к одному отделению. this.assert(new AuthzResponse( facts.req.subject.branch == facts.req.object.branch)); } ); this.rule("Customer", // Имя правила // Обратите внимание на добавленный третий элемент списка, ограничивающий это правило случаями, в которых субъект является клиентом. [[AuthzRequest, "req", "req.subject.role=='Customer'"]], // Функция, вызываемая в случае соблюдения данного правила function(facts) { // Если субъект и объект имеют одно и же имя, разрешить клиенту видеть собственный баланс. this.assert(new AuthzResponse( facts.req.subject.name == facts.req.object.name)); } ); });

Шаг 6. Хранение политики в объекте

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

Эту задачу можно решить двумя способами. Первый способ состоит в использовании собственного DSL-языка (domain specific language – предметно-ориентированный язык) механизма Nools. Однако этот язык является очень гибким и, как результат, весьма сложным. Это не соответствует нашей цели – дать возможность изменять политику пользователям, не владеющим программированием.

Второй способ состоит в том, чтобы поместить всю политику в JavaScript-объект, а затем предоставить пользовательский интерфейс для изменения этого объекта. Этот способ требует навыков программирования такого же типа, что и для самого приложения.

Правила могут быть выражены несколькими способами. В случае данного конкретного приложения запрос на авторизацию всегда имеет следующие шесть переменных:

  • subject.name
  • subject.role
  • subject.branch
  • object.name
  • object.role
  • object.branch

Поскольку количество переменных столь невелико, самый простой способ задания правил состоит в том, чтобы указать значения переменных, необходимых для авторизации действия. Каждое из этих значений может быть константой (например, subject.role имеет значение Teller), другой переменной (subject.branch имеет значение object.branch), или специальным значением, означающим, что данная переменная не участвует в данном правиле.

  1. Добавьте объект для политики безопасности.
     // Политика безопасности. Данная политика включает три параметра. // // Vars - это переменные, образующие политику. // Constants – это константы, которые могут присутствовать в политике // (обратите внимание, они требуются только для пользовательского интерфейса) // // Rules – это фактические правила. Каждое правило содержит переменные // (заключенные в кавычки, чтобы можно было использовать точки в имени переменной) // и значения, которым они должны соответствовать. Они могут быть сопоставлены с константами или с другими переменными. Если значение переменной несущественно для правила, она не появляется в этом правиле. // // Все правила являются разрешающими. Если запрос не соответствует никакому правилу, он отклоняется. var secPolicy = { vars: ["subject.name", "subject.role", "subject.branch", "object.name", "object.role", "object.branch" ], constants: ["Teller", "Customer", "Austin", "Boston"], rules: [ { // Правило кассира "subject.role": {type: "constant", value: "Teller"}, "subject.branch": {type: "variable", value: "object.branch"} }, { // Правило клиента "subject.role": {type: "constant", value: "Customer"}, "subject.name": {type: "variable", value: "object.name"} } ] };
  2. Обработка правил достаточно сложна, поэтому наличие доступа к внешним функциям было бы весьма полезно. К сожалению, из шаблона Nools предусмотрен доступ только к фактам. Таким образом, чтобы поместить функции в факты, создадим класс объекта функции.
     // Класс объекта для функций, позволяющий применять их как "факты" в рамках Nools var FunObj = function(name, fun) { this.name = name; this.fun = fun; }
  3. В функции authorizeAction декларируйте новый факт с помощью функции matchRuleRequest:
     // Добавить необходимую функцию как "факт" session.assert(new FunObj("matchRuleRequest", matchRuleRequest));
  4. Добавьте реально действующую функцию matchRuleRequest и служебную функцию, которую она использует.
     // Получить значение в запросе от имени переменной в стиле правила var getRequest = function(request, varName) { // Внешние и внутренние имена переменной в запросе // Правило имеет имя rule["subject.role"], // но запрос AuthorizationRequest имеет для этого имя // request["subject"]["role"] reqVarNames = varName.split("."); // Значение в запросе return request[reqVarNames[0]][reqVarNames[1]]; } // Проверить, соответствует ли правилу запрос на авторизацию var matchRuleRequest = function(ruleNumber, request) { var rule = secPolicy.rules[ruleNumber]; for (variable in rule) { // Получить значение ruleValue = rule[variable]; // Если это константа, проверить на равенство этой константе if (ruleValue.type == "constant" && ruleValue.value != getRequest(request, variable)) return false; // Если это переменная, получить значение этой переменной и сравнить с ним if (ruleValue.type == "variable" && getRequest(request, ruleValue.value) != getRequest(request, variable)) return false; } // Если мы добрались до этого места, несоответствия отсутствуют. return true; };
  5. Измените функцию, создающую поток, таким образом, чтобы использовать политику.
     // Создать новый поток, базу правил. var flow = nools.flow("authz", function(flow) { // Создать правила из политики for(var i=0; i<secPolicy.rules.length; i++) { this.rule("Rule #" + i, // Имя правила [ // Найти два факта, каждый с шаблоном. Первый факт, match, делает функцию matchRuleRequest доступной, // если контекст шаблона соответствует второй функции, которая проверяет, соответствует ли запрос на авторизацию правилу. [FunObj, "match", "match.name == 'matchRuleRequest'"], [AuthzRequest, "req", "match.fun(" + i + ", req)"] ], function(facts) { // Если мы добрались до этого места, то имеет место соответствие правилу, поэтому разрешить this.assert(new AuthzResponse(true)); } ); } });
  6. Отправьте приложение и удостоверьтесь в том, что оно по-прежнему соответствует политике безопасности.

Шаг 7. Создание пользовательского интерфейса для политики

Наконец, чтобы дать возможность изменять политику администраторам, не владеющим программированием, необходимо создать для нее пользовательский интерфейс. Файлы для интерфейса политики безопасности (public/policy.html и public/scripts/policy.js) присутствуют в исходном коде. Они используют Angular вполне стандартным способом, поэтому я не буду рассматривать их слишком глубоко. Подробная информация по Angular содержится в цикле статей "Build a self-posting Facebook application with Bluemix and the MEAN stack" на сайте developerWorks.

Один из интересных моментов состоит в том, что HTML-тег select лучше всего работает со строковыми значениями. По этой причине вместо того, чтобы идентифицировать значения параметров как объекты (например, {type: "constant", value: "Teller"}), политика в браузере использует строки с префиксом, который указывает, чем является соответствующее значение – переменной или константой (например, c:Teller). Передача политики осуществляется средствами REST, что также объясняется в вышеупомянутом цикле статей "Build a self-posting Facebook application with Bluemix and the MEAN stack".

Еще одна проблема состоит в том, что демонстрационное приложение нигде не сохраняет политику; оно лишь размещает эту политику в оперативной памяти. Это означает, что в случае перезапуска приложения политика теряется. Реальное приложение обычно имеет доступ к базе данных, например, MongoDB, и хранит политику в ней. Использование MongoDB также объясняется в вышеупомянутом цикле статей.

Теперь вы можете воспользоваться итоговым приложением по ссылке http://world-silliest-bank-after.mybluemix.net/. Не забывайте, что политика безопасности является глобальной; соответственно, если она вдруг меняется, это может быть вызвано тем, что ее изменил другой пользователь. В вашем распоряжении имеется кнопка для загрузки текущей политики безопасности в файл policy.html, чтобы продемонстрировать эту возможность.

Заключение

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

В случае реального приложения проанализируйте потребности авторизации.

  • Кем являются пользователи – субъекты? Какая информация о пользователе может быть релевантна при принятии решений об авторизации?
  • Какие действия – глаголы – нуждаются в авторизации?
  • На объект какого типа влияет каждое из этих действий? Какие атрибуты этих объектов могут быть релевантны при принятии решений об авторизации?

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


Ресурсы для скачивания


Похожие темы


Комментарии

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

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Security, Облачные вычисления
ArticleID=1025123
ArticleTitle=Использование бизнес-правил в качестве механизма авторизации
publish-date=12302015