Web-приложение WSO2 - добавляем шину

В прошлых постах серсии мы рассмотрели:

Теперь рассмотрим как реализовать проверку прав доступа на уровне доступа к данным. Для этого мы будем использовать интеграционную шину WSO2 ESB.

Для чего нужна интеграционная шина

По идее, сам сервер WSO2 DSS  имеет возмоность авторизовать доступ к сервисам - зачем нам промежуточный слой? На самом деле в данном случае - это просто пример. Но, при разработке сложных корпортативных информационных систем является правилом хорошего тона избегать прямого взаимодействия между системами. Иначе возникает интеграционное спаггети.

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

Одно из основных назначений шины - это реализация Proxy Service  - в нашем случае это будет сервис между веб-приложением и WSO2 DSS на котором будет реализована авторизация запросов.

Реализация Proxy Service-а

Для реализации будем использвать WSO2 ESB 4.9.0 - скачать можно с сайта WSO2. Установка шины описана в одном из предыдущих постов - она штатная.

Не забываем прописать Offset=3  в repository/conf/carbon.xml - таким образом сервер не будет конфликтовать с другими серверами WSO2 использующимися в примере.  Соответсвенно панель администратора будет доступна по адресу: https://144.76.92.85:9446/carbon

Далее для создания простейшего Proxy Service:

  • входим в админку (admin/admin)
  • Main -> Manage -> Services -> Add -> Proxy Service
  • Выбираем Pass Through Proxy 
  • Для создания сервиса нам надо знать исходный End-Point - для этого заходим в админку WSO2 DSS,  идем в сервисы, кликаем на нашем сервисе и копируем URL end-point-а ( в нашем случае https://localhost:9445/services/WSO2HealthIT/) : 

  • В WSO2 ESB  при создании сервиса указываем имя: WSO2HealthITProxy и его end-point

  • Кликаем Create - простейший Proxy Service готов.

Изменения в Веб-Приложении

В нашем приложении мы захардкодили URL сервиса в двух местах:

  1. QueryPatientDetailServlet.java - переменная dataServiceEP
  2. RegisterPatientServlet.java - DSSServerURL

Заменим обе переменные на новый URL : http://localhost:8283/services/WSO2HealthITProxy (который теперь указывает на шину).

Деплоим приложение - проверяем что все продолжает работать. При этом в админке шины в информации о нашем proxy service-е мы можем увидеть, что работа идет именно через него (через статистику)

 Интеграция XACML  в Proxy Service

Как мы помним - штатным средством для описания прав доступа является  протокол XACML - дальше мы научим шину управляеть доступом к сервису используя этот протокол.

Передача авторизационной информации из веб-приложения

Но для начала необходимо передать информацию о пользователе из приложения в шину. Для этого вносим изменения в классы QueryPatientDetailServlet и RegisterPatientServlet и инициализацию сервиса - добавляем информацию о пользователе в header-ы запроса

List<Header> headers = new ArrayList<Header>();
Header authHeader = new Header("X-Authorization", (String) request.getSession(false).getAttribute("user"));
headers.add(authHeader);
opt.setProperty(HTTPConstants.HTTP_HEADERS, headers);

 

В дальнейшем ESB получит эти данные из заголовка и будет знать, "кто" вызвал сервис.

Добавляем Entitlement медиатор в Proxy Service

Теперь вносим изменения в наш Proxy Service.  Кликаем Edit на странице сервиса.

Можно использовать графический редактор, но на практике проще использовать XML-редактор (Switch to Source View  в редакторе).

Для начала нам надо получить пользователя из заголовка:

         <property name="username"
                   expression="$trp:X-Authorization"
                   scope="axis2"
                   type="STRING"/>
 

Далее добавляем сам сервис

<entitlementService remoteServiceUrl="https://localhost:9444/services"
                             remoteServiceUserName="admin"
                             remoteServicePassword="enc:kuv2MubUUveMyv6GeHrXr9il59ajJIqUI4eoYHcgGKf/BBFOWn96NTjJQI+wYbWjKW6r79S7L7ZzgYeWx7DlGbff5X3pBN2Gh9yV0BHP1E93QtFqR7uTWi141Tr7V7ZwScwNqJbiNoV+vyLbsqKJE7T3nP8Ih9Y6omygbcLcHzg="
                             callbackClass="org.wso2.carbon.identity.entitlement.mediator.callback.UTEntitlementCallbackHandler"
                             client="basicAuth">
            <onReject>
               <log level="custom">
                  <property name="FAULT" value="ON REJECT CALLED"/>
               </log>
               <makefault version="soap11">
                  <code xmlns:soap11Env="http://schemas.xmlsoap.org/soap/envelope/"
                        value="soap11Env:Server"/>
                  <reason value="UNAUTHORIZED"/>
                  <role/>
                  <detail>XACML Authorization Failed</detail>
               </makefault>
               <property name="RESPONSE" value="true" scope="default" type="STRING"/>
               <header name="To" scope="default" action="remove"/>
               <send/>
            </onReject>
            <onAccept>
               <log level="custom">
                  <property name="FAULT" value="ON ACCEPT CALLED"/>
               </log>
               <send>
                  <endpoint>
                     <address uri="http://localhost:9765/services/WSO2HealthIT/"/>
                  </endpoint>
               </send>
            </onAccept>
            <obligations/>
            <advice/>
         </entitlementService>

Итак - аналогично EntitlementFilter в веб-приложении мы обращаемся к WSO2 IS (https://localhost:9444/services)  запрашивая информацию может ли указанный пользователь выполнять текущую операцию или нет.

Далее два блока:

  • onReject -   мы генерируем ошибку (makefault) которую потом обработаем в веб-приложении.
  • onAccept - мы передаем управление в исходный сервис

В out-sequence нам надо убрать заголовки запроса (относящиеся к безопасности и просто отправить ответ):

      <outSequence>
         <header xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"
                 name="wsse:Security"
                 action="remove"/>
         <send/>
      </outSequence>
 

Добавляем правила доступа в WSO2 IS

Теперь надо добавить правиа доступа в WSO2 IS - аналогично тому, как мы это делали в свое время для веб-приложения

Логинимся в WSO2 IS (admin/admin) и далее Administration -> Add New Entitlement Policy -> write policy in XML

Берем dssOperationsProxy из исходников (https://github.com/akakunin/wso-health-tutorial/blob/3.0.x/is/dssOperationsPolicy.xml), сохраняем, далее "Publish to my PDP" и далее идем в Policy View и включаем Policy.

Не останавливаясь сейчас на формате XACML, в данном XML  мы описали следующие правила:

Permissions user has Access URL Allow/ Deny
read .*/WSO2HealthITProxy/patientDetailsByNumber Allow
read .*/WSO2HealthITProxy/registerPatient Deny
write .*/WSO2HealthITProxy/patientDetailsByNumber Allow
write .*/WSO2HealthITProxy/registerPatient Allow

Добавляем обработку ошибок

Последним шагом будет обработка ошибок.

Обработка ошибок DSS

Наш Proxy Service "оборачивает" вызов к DSS.  Если "что-то пойдет не так" то в сервисе будет вызван faultSequence.  По этому, в наш сервис необходимо добавить 

   <target faultSequence="wso2HealthITFaultHandler">

(весь сервис можно посмотреть в исходниках: https://github.com/akakunin/wso-health-tutorial/blob/3.0.x/esb/WSO2HealthITProxy.xml)

Так же необходимо будет создать в ESB Sequence: Manage -> Sequences -> Add Sequence с именем wso2HealthITFaultHandler и кодом из репозитория: https://github.com/akakunin/wso-health-tutorial/blob/3.0.x/esb/wso2HealthITFaultHandler.xml)

Как итог - если с возникнет какая-либо ошибка ( на стороне DSS  или например ошибка доступа) клиент в веб-приложении получит SoapFault Exception.  Соотвественно везде где мы обращаемся к сервису нам необходимо добавить обработку Exception-а:

  • В QueryPatientDetailServlet надо добавить следующий обработчик:
try {
    <doGet method>
}    catch (Exception e)  {
    RequestDispatcher rd = getServletContext().getRequestDispatcher("/getPatientDetails.jsp");
    PrintWriter out= response.getWriter();
    out.println("<font color="red">Error While Querying Records : "+e.getMessage()+"</font>");
    rd.include(request, response);
}
  • RegisterPatientServlet - просто поймать Exception

Итог

Как итог данного упражнения, мы добавили шину для связи между сервисами данных и веб-приложением, в которой реализовали проверку прав доступа и обработку ошибок.

Все исходники примера доступны в репозитории: https://github.com/akakunin/wso-health-tutorial/tree/3.0.x