вторник, 11 февраля 2014 г.

[Из песочницы] 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://ift.tt/Hm2joK"
xmlns:wsu="http://ift.tt/Hm2joJ"
wsu:Id="UTOverTransport">
<wsp:ExactlyOne>
<wsp:All>
<sp:TransportBinding xmlns:sp="http://ift.tt/1fUDI95">
<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://ift.tt/1fUDI95">
<wsp:Policy>
<sp:UsernameToken sp:IncludeToken="http://ift.tt/1emaEEM"/>
</wsp:Policy>
</sp:SignedSupportingTokens>
<ramp:RampartConfig xmlns:ramp="http://ift.tt/1emaEod">
<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://ift.tt/Hm2joK"
xmlns:wsu="http://ift.tt/Hm2joJ"
wsu:Id="UTOverTransport">
<wsp:ExactlyOne>
<wsp:All>
<sp:TransportBinding xmlns:sp="http://ift.tt/1fUDI95">
<wsp:Policy/>
</sp:TransportBinding>
<sp:SignedSupportingTokens xmlns:sp="http://ift.tt/1fUDI95">
<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://ift.tt/tCZ8VR" 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://ift.tt/IrJ6pr"
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://ift.tt/sVJIaE"
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.

  • Довольно высокий порог вхождения для решения простейшей задачи.

  • Задача решена!


This entry passed through the Full-Text RSS service — if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.


Комментариев нет:

Отправить комментарий