WSO2: Настройка прокси к сервису с аутентификацией по логину и паролю

от автора

Задача

Есть приложение, которое может запрашивать данные по определённому протоколу и есть сервис, который может отдавать данные, но по другому протоколу и требует авторизации по логину и паролю.
В приложении нет учётных данных необходимых для доступа к сервису. Необходимо подружить приложение и сервис.

Решение

Использовать шину: WSO2 Enterprise Service Bus, на которой настроить прокси-сервис.

Используем WSO2 ESB версии 4.7.0.

Создаём политики

Так как целевой сервис защищён по стандарту WS-Security, то и доступ к нему будем настраивать по всей строгости стандарта.
Политики создаются как ресурсы в локальном репозитории, в интерфейсе: Manage — Service Bus — Local Entries — Add Local Entries — Add In-lined XML Entry.

Политика для исходящих сообщений

Здесь и в дальнейшем имена могут даваться произвольные, но буду акцентировать на них внимание, потому что через них будут связываться все артефакты в одно целое
Указываем имя: service-policy
Указываем политику:

<wsp:Policy xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy"             xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"             wsu:Id="UTOverTransport">     <wsp:ExactlyOne>         <wsp:All>             <sp:TransportBinding xmlns:sp="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy">                 <wsp:Policy>                     <sp:TransportToken>                         <wsp:Policy>                             <sp:HttpsToken RequireClientCertificate="false"/>                         </wsp:Policy>                     </sp:TransportToken>                     <sp:AlgorithmSuite>                         <wsp:Policy>                             <sp:Basic128/>                         </wsp:Policy>                     </sp:AlgorithmSuite>                     <sp:Layout>                         <wsp:Policy>                             <sp:Lax/>                         </wsp:Policy>                     </sp:Layout>                     <sp:IncludeTimestamp/>                 </wsp:Policy>             </sp:TransportBinding>             <sp:SignedSupportingTokens xmlns:sp="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy">                 <wsp:Policy>                     <sp:UsernameToken sp:IncludeToken="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy/IncludeToken/AlwaysToRecipient"/>                 </wsp:Policy>             </sp:SignedSupportingTokens>             <ramp:RampartConfig xmlns:ramp="http://ws.apache.org/rampart/policy">                 <ramp:user>MyUsername</ramp:user>                 <ramp:passwordCallbackClass>s.e.r.PasswordCallbackHandler</ramp:passwordCallbackClass>             </ramp:RampartConfig>         </wsp:All>     </wsp:ExactlyOne> </wsp:Policy> 

Основной частью в этой политике является указание имени пользователя целевого сервиса и класса, который в нашем случае просто подставит необходимый пароль, класс s.e.r.PasswordCallBackHandler:

package s.e.r;   import org.apache.ws.security.WSPasswordCallback; import javax.security.auth.callback.Callback; import javax.security.auth.callback.CallbackHandler; import javax.security.auth.callback.UnsupportedCallbackException; import java.io.IOException;   public class PasswordCallBackHandler implements CallbackHandler {     private static final String PASSWORD = "MyPassword";     @Override     public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException     {         for (Callback callback : callbacks)         {             if (callback instanceof WSPasswordCallback)             {                 WSPasswordCallback pc = (WSPasswordCallback) callback;                 if (pc.getUsage() == WSPasswordCallback.USERNAME_TOKEN_UNKNOWN)                 {                     if (pc.getPassword().equals(PASSWORD))                         return;                     throw new UnsupportedCallbackException(callback, "Check failed");                 }                 pc.setPassword(PASSWORD);             } else             {                 throw new UnsupportedCallbackException(callback, "Unrecognized Callback");             }         }     } } 

Пакуем в jar и кладём в WSO2_HOME/repository/components/lib/.
Рестартуем WSO2.

Политика для входящих сообщений

Указываем имя: empty-policy.
Указываем политику:

<wsp:Policy xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy"             xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"             wsu:Id="UTOverTransport">     <wsp:ExactlyOne>         <wsp:All>             <sp:TransportBinding xmlns:sp="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy">                 <wsp:Policy/>             </sp:TransportBinding>             <sp:SignedSupportingTokens xmlns:sp="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy">                 <wsp:Policy/>             </sp:SignedSupportingTokens>         </wsp:All>     </wsp:ExactlyOne> </wsp:Policy> 
Создаём Address Endpoint

Указываем имя: my-endpoint.
Указываем адрес сервиса.
В дополнительный опциях отмечаем использование WS-Security и указываем политики для исходящих и входящих сообщений созданные ранее.
Исходник Endpoint’а:

<endpoint name="my-endpoint">   <address uri="http://service/soap/">     <enableSec inboundPolicy="empty-policy" outboundPolicy="service-policy"/>   </address> </endpoint> 
Создаём XSLT-преобразование сообщений

XSLT-преобразования создаются там же где и политики: Manage — Service Bus — Local Entries — Add Local Entries — Add In-lined XML Entry.

Преобразование входящего сообщения

Нам необходимо привести запрос нашего приложения к виду, который понимает сервис:
Указываем имя: in-xslt
Указываем преобразование:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">     <xsl:template match="/">         <!--тут само преобразование-->     </xsl:template> </xsl:stylesheet> 
Преобразование в исходящее сообщение

Нам нужно ответ сервиса привести к виду, понятному для приложения. Шаг идентичен шагу для входящего сообщения, только имя: out-xslt.

Создаём Proxy Service

В UI: Manage — Service — Add — Proxy Service — Custom Proxy
Указываем имя: external-service. По этому имени будет доступен ваш сервис на шине, например: localhost:8280/services/external-service.
В нашем случае снимаем отметку с протокола https.
Дальше переключаемся в режим исходно кода (switch to source view) и приводим содержимое приблизительно к следующему:

<?xml version="1.0" encoding="UTF-8"?> <proxy xmlns="http://ws.apache.org/ns/synapse"        name="external-service"        transports="http"        statistics="disable"        trace="disable"        startOnLoad="true">     <target faultSequence="fault" endpoint="my-endpoint">         <inSequence>             <xslt key="in-xslt"/>             <send>                 <endpoint key="my-endpoint"/>             </send>         </inSequence>         <outSequence>             <xslt key="out-xslt"/>             <send/>         </outSequence>     </target>     <description/> </proxy>                    

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

Готово!

Дополнительно

Чтобы следить за тем, во что превращает наша прокси сообщения, использовал fiddler

Потому что не нашёл как настроить и где смотреть конечные сообщения.
Подробнее про fiddler:

Прокси пробрасывает заголовок Action пришедшие из приложения, а сервис ругается?

Добавляем медиаторы в последовательность прокси сервиса, удаляем заголовок Action:

<inSequence>     <xslt key="in-xslt"/>     <header name="Action" value=""/>     <property name="SOAPAction" value="" scope="transport"/>     <send>         <endpoint key="my-endpoint"/>     </send> </inSequence> 
Прокси возвращает данные в некорректной кодировке?

Указываем кодировку, в которой возвращаем сообщения:

<outSequence>     <xslt key="out-xslt"/>     <property name="messageType"               value="text/xml;charset=windows-1251"               scope="axis2"               type="STRING"/>     <send/> </outSequence> 
Ответ сервиса состоит из нескольких частей?

Например:

<soapenv:Envelope>     <soapenv:Body>         <part1/>         <part2/>     </soapenv:Body> </soapenv:Envelope>  

WSO2 по умолчанию считает что ответ должен состоять из одной части, поэтому передают на преобразование то что находится по пути: s11:Body/child::*[position()=1] | s12:Body/child::*[position()=1], как результат мы можем трансформировать только первую часть, чтобы это исправить меняем вызов преобразователя в сервисе:

<outSequence>     <xslt xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"           key="stav-out-xslt"           source="SOAP-ENV:Body"/>     <send/> </outSequence> 

… и учитываем это в преобразователе:

<xsl:stylesheet>     <xsl:template match=".">         <xsl:apply-templates select="/part1"/>         <xsl:apply-templates select="/part2"/>     </xsl:template>     <!--в нашем случае part1 нужно было проигнорировать-->     <xsl:template match="part1"/>     <xsl:template match="part2">         <forbody>             <!--тут трансформации обёрнутые в произвольный тег, он необходим, чтоб в конечном результате не пропал корневой элемент ответа-->         </forbody>     </xsl:template> </xsl:stylesheet> 

Нерешённые вопросы

Пришлось настраивать две политики безопасности

Есть ощущение что можно обойтись одной, но с единой политикой выходила ошибка, о том, что ответ сервиса не содержит заголовков безопасности. А он действительно их не содержит. Чтоб это обойти пришлось писать пустую политику для ответа.

Не найдено встроенного решения для хранения учётных данных стороннего сервиса

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

Итог

  • Часть с преобразованием не вызвала проблем, кроме того, что настройка очень сильно размазана по UI. Конечно можно настраивать через единый xml в том же UI, но в этом случае настройка будет размазана по xml.
  • Довольно высокий порог вхождения для решения простейшей задачи.
  • Задача решена!

ссылка на оригинал статьи http://habrahabr.ru/post/212267/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *