Веб-приложение WSO2 - добавляем проверку прав доступа

Сегодня мы продолжим рассматривать пример создания веб-приложения с использованием технологий WSO2, начатый некоторое время назад в этом посте: http://www.emdev.ru/-/primer-veb-prilozenia-s-primeneniem-tehnologij-wso2

Как и в прошлый раз - я взял за основу оригинальный код, по минимуму изменяя его. Все улучшения будут потом. Мимо одно я не смог только пройти (так как в свое время потратил на это очень много времени) - это то, как формируется имя ресурса в EntitlementFilter - но об этом чуть ниже.

WSO2 Identity Server

Как и в первой статье, для решение специфической задачи мы будем использовать специфический продукт в семействе  WSO2: WSO2 Identity Server. Этот продукт отвечает за целый спектр задач, связанных как раз с аутентификацией и авторизацией пользователей.

Установка и старт WSO2IS происходят штатным для всех продуктов WSO2 способом. Только (в рамках нашего демо) необходимо изменить Offset в файле repository/conf/carbon.xml на 1 - что бы сервер не конфликтовал с другими серверами по портам. После этого WSO2IS будет доступен по адресу https://localhost:9444/carbon

Настройка пользователей и ролей

Первое, что необходимо сделать - это создать роли и пользователи, которыми будет оперировать наше приложение. Предположим у нас будет две роли - read - дает право на чтение записей о пациентах, и write - создавать новые записи.

Идем в Configure -> Users and Roles -> Roles -> Add New Role и добавляем роль read

Затем аналогично добавляем роль write

Теперь черед пользователей. Добавляем там же следующих пользователей с ролями согласно таблице

Username Password Roles
evanthika evanthika read
senaka senaka write
shammi shammi internal/everyone

Добавляем в приложение аутентификацию

Теперь мы можем добавить в наше приложение аутентификацию - вход в приложение по имени пользователя и паролю. Так как приложение у нас минимально использует какие либо framework-и (типа того же Spring Security) - то реализовывать будем по простому, своими силами, следующим образом

  • Повешен AuthenticationFilter, который проверяет сессию (залогинен пользователь или нет) и если нет -то делает редирект на  страницу login.html c формой логина.
  • Фильтр добавляем в web.xml.
  • После сабмита формы логина вызывается LoginServlet - в нем мы получаем переданные имя пользователя и пароль и делаем вызов сервиса RemoteUserStoreManagerService, предоставленного WSO2IS
  • Для этого используется следующий код:
if (!(credential instanceof String)) {
        throw new Exception("Unsupported type of password");
}

try {
        if(stub == null) {
            stub = new RemoteUserStoreManagerServiceStub(null, serverUrl + "RemoteUserStoreManagerService");
            HttpTransportProperties.Authenticator basicAuth = new HttpTransportProperties.Authenticator();
                basicAuth.setUsername(basicAuthUserID);
                basicAuth.setPassword(basicAuthPassword);
                basicAuth.setPreemptiveAuthentication(true);

                final Options clientOptions = stub._getServiceClient().getOptions();
                clientOptions.setProperty(HTTPConstants.AUTHENTICATE, basicAuth);
                stub._getServiceClient().setOptions(clientOptions);
    }
        return stub.authenticate(userName, (String) credential);
} catch (Exception e) {
        handleException(e.getMessage(), e);
}
return false;
 
  • В pom.xml добавляем dependency, чтобы нам стал доступен указанный класс.
  • При клике на кнопку Logout  вызывается LogoutServlet, который просто инвалидирует сессию.
 
Готово - для работы с приложением теперь требуется ввести имя пользователя и пароль.

XACML

ОК, теперь мы знаем кто к нам пришел, и (в принципе) можем получить роль этого пользователя. Но, привязывать доступность той или иной функциональности к определенной роли - неправильно. Правила распределения прав доступа могут изменится - и если мы привяжемся в коде к определенной роли - при каждом изменении правил нам придется вносить изменения в код, что как минимум неудобно, а как максимум может привести к большим "дыркам" в безопасности, когда мы вдруг забудем что-то поменять.
Правильней анализировать "Разрешения" -  Permissions - на выполнение определенных действий (Actions) c определенными ресурсами (Resources).
В нашем случае ресурсы - это страницы, к которым мы можем либо разрешить, либо запретить доступ в соответствии с некоторыми правилами.
на самом деле существует даже специальный стандарт для описания указанных правил - XACLM -  и WSO2IS  поддерживает его.
А мы со своей стороны будем его использовать.

Загрузка правил XACML в WSO2IS

Сами правила описаны в файле health-xacml-policy.xml - на самом деле он очень простой - в нем определено два правила ( Rule1 и Rule2). Первое разрешает доступ GET к странице (ресурсу) /addPatient.jsp только для пользователя с ролью write. Второе - доступ к страницам /getPatientInfo.jsp и /getPatientDetails.jsp для ролей read и write.
В WSO2IS идем в Main -> PAP -> Policy Administration -> Add New Entitlement Policy -> Import Existing Policy и загружаем указанный файл.
После этого для созданной policy кликаем Publish to my PDP
После чего идем в Main -> PDP -> Policy View и кликаем для созданной Policy Enable.

EntitlementFilter

Последний шаг - проверка прав доступа в приложении. Для этого в WSO2 есть EntitlementFilter, который мы можем использовать в нашем приложении. Но со штатной реализацией возникла проблема (я протупил и убил на нее непозволительно много времени).
Дело в том, что EntitlementFilter когда отправляет запрос на сервер WSO2IS - использует request.getRequestURI() для формирования ресурса. Полученное значение включает и путь внутри приложения, и context path - тот контекст, под которым приложение было задеплоено. Таким образом и в XACML  файле было необходимо указывать пути вместе с контекстом приложения (например /wso2-health/addPatient.jsp) - но в случае если мы деплоим приложение с другим контекстом или (например) используем суффикс версии (при деплое приложения с разными версиями) - правила перестают работать.
Поэтому я изменил штатный EntitlementFilter - реализовав проверку именно по пути внутри приложения - исходный код фильтра доступен в github: https://github.com/emdev-limited/entitlement-filter
 
Все - теперь пробуем зайти разными пользователями - при клике на ссылки просмотра или добавления пациента в зависимости от роли пользователя мы либо будем получать доступ к странице - либо отправляться обратно на страницу логина.
 
Данный пример очень простой - но он демонстрирует возможности использования WSO2 Identity Server для централизованного хранения не только данных о пользователей - но и правил доступа - которые потом могут использоваться всеми приложениями в нашей инфраструктуре.