Ошибка в клиенте WCF, использующем веб-службу Axis 2 с WS-Security. Имя пользователя: Token PasswordDigest.
У меня есть клиент WCF, который подключается к веб-сервису Axis2 на базе Java (вне моего контроля). Он собирается использовать WS-Security, и мне нужно исправить клиент .NET. Тем не менее, я изо всех сил пытаюсь обеспечить правильную аутентификацию. Я знаю, что WSE 3.0 может сделать это проще, но я бы предпочел не возвращаться к устаревшей технологии.
Аналогичные проблемы (нерешенные), включают этот, этот и this.
Сообщение SOAP должно выглядеть следующим образом:
<wsse:UsernameToken>
<wsse:Username><!-- Removed--></wsse:Username>
<wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest"><!-- Removed--></wsse:Password>
<wsse:Nonce><!-- Removed--></wsse:Nonce>
<wssu:Created>2010-05-28T12:50:33.675+01:00</wssu:Created>
</wsse:UsernameToken>
Однако мой вид выглядит следующим образом:
<s:Header>
<h:Security xmlns:h="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" xmlns="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"></h:Security>
<o:Security s:mustUnderstand="1" xmlns:o="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
<u:Timestamp u:Id="_0">
<u:Created>2010-06-23T10:31:23.441Z</u:Created>
<u:Expires>2010-06-23T10:36:23.441Z</u:Expires>
</u:Timestamp>
<o:UsernameToken u:Id="uuid-d329b3b2-6a1f-4882-aea6-ec6b8a492de7-1">
<o:Username>
<!-- Removed-->
</o:Username>
<o:Password>
<!-- Removed-->
</o:Password>
</o:UsernameToken>
</o:Security>
</s:Header>
Мой клиент выглядит так:
Постскриптум Обратите внимание на параметр required SecurityHeaderType. Что это?
public MyAck SendRequest(MyRequest request)
{
RemoteServicePortTypeClient client = new RemoteServicePortTypeClient();
client.ClientCredentials.UserName.UserName = "JAY";
client.ClientCredentials.UserName.Password = "AND";
// what is the difference between the two different Credential types??
//client.ClientCredentials.HttpDigest.ClientCredential.UserName = "SILENT";
//client.ClientCredentials.HttpDigest.ClientCredential.Password = "BOB";
SecurityHeaderType sht = new SecurityHeaderType();
//sht.Any = ???; // How do I use this???
//sht.AnyAttr = ???; // How do I use this ???
// SecurityHeaderType is a required parameter
return client.RemoteServiceOperation_Provider(sht, request);
}
Текущее привязку выглядит следующим образом:
<basicHttpBinding>
<binding name="CustomBinding">
<security mode="TransportWithMessageCredential">
<transport clientCredentialType="None"></transport>
<message clientCredentialType="UserName" />
</security>
</binding>
</basicHttpBinding>
Я также пробовал пользовательскую привязку и получил аналогичную ошибку:
<customBinding>
<binding name="myCustomBindingConfig">
<security authenticationMode="UserNameOverTransport"
messageSecurityVersion="WSSecurity11WSTrustFebruary2005WSSecureConversationFebruary2005WSSecurityPolicy11"
securityHeaderLayout="Strict"
includeTimestamp="false"></security>
<textMessageEncoding messageVersion="Soap11"></textMessageEncoding>
<httpsTransport />
</binding>
</customBinding>
И конечная точка (адрес явно изменен...):
<endpoint address="https://www.somecompany.com/uat/axis/services/RemoteServiceOperation_Provider"
binding="basicHttpBinding" bindingConfiguration="CustomBinding"
contract="RemoteService.RemoteServicePortType"
name="RemoteService_UAT" />
Возвращаемая пользовательская ошибка выглядит следующим образом:
<ErrorID>0</ErrorID>
<ErrorType>UNEXPECTED</ErrorType>
<ErrorDescription><![CDATA[Array index out of range: 0]]></ErrorDescription>
<TimeStamp>2010-06-23T13:28:54Z</TimeStamp>
Я читал много о пользовательских заголовках, токенах, привязках и моем мозгу, полностью смущен. Может ли кто-нибудь предложить пошаговый процесс отправки сообщения в правильном формате?
Это, по-видимому, является способом продвижения WCF с использованием пользовательских токенов, но как следует применять дайджест и nonce по мере необходимости?
Любая помощь приветствуется.
UPDATE
У меня был ограниченный успех. Я использовал библиотеку Microsoft.Web.Services3 для создания UsernameToken с правильным дайджестом. Затем я создал свое собственное поведение и в методе BeforeSendRequest я сделал следующее, чтобы ввести заголовок:
object IClientMessageInspector.BeforeSendRequest(ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel)
{
UsernameToken ut = new UsernameToken("USERNAME", "PASSWORD", PasswordOption.SendHashed);
XmlElement securityElement = ut.GetXml(new XmlDocument());
MessageHeader myHeader = MessageHeader.CreateHeader("Security", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd", securityElement, false);
request.Headers.Add(myHeader);
return Convert.DBNull;
}
Я добавляю следующее:
CustomBehavior behavior = new CustomBehavior("USERNAME", "PASSWORD");
client.Endpoint.Behaviors.Add(behavior);
Теперь я могу видеть заголовки:
<s:Header>
<Security xmlns="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
<wsse:UsernameToken wsu:Id="SecurityToken-c6aeb72d-4d36-4650-abd3-33cc66caac6d" xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
<wsse:Username>
<!-- Removed-->
</wsse:Username>
<wsse:Password>
<!-- Removed-->
</wsse:Password>
<wsse:Nonce>
<!-- Removed-->
</wsse:Nonce>
<wsu:Created>2010-06-24T16:23:58Z</wsu:Created>
</wsse:UsernameToken>
</Security>
</s:Header>
Но я получаю сообщение об ошибке:
<soapenv:Fault>
<faultcode xmlns="">soapenv:Server</faultcode>
<faultstring xmlns="">WSDoAllReceiver: security processing failed; nested exception is:
org.apache.ws.security.WSSecurityException: General security error (WSSecurityEngine: Callback supplied no password for: USERNAME)</faultstring>
<faultactor xmlns="">urn:Remote_Provider</faultactor>
<detail xmlns="">
<CUSTOMError xmlns="urn:customerror:v01">
<ErrorID>0</ErrorID>
<ErrorType>UNEXPECTED</ErrorType>
<ErrorDescription><![CDATA[WSDoAllReceiver: security processing failed; nested exception is:
org.apache.ws.security.WSSecurityException: General security error (WSSecurityEngine: Callback supplied no password for: USERNAME)]]></ErrorDescription>
<TimeStamp>2010-06-24T17:23:59Z</TimeStamp>
</CUSTOMError>
</detail>
</soapenv:Fault>
На пароле node появляется отсутствующий атрибут Тип:
Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest"
Тем не менее, я не уверен, что параметры отслеживания и ведения журнала безопасности являются пустыми, удаляя атрибуты и содержимое этих узлов. Я попытался использовать параметр logKnownPii в регистрации диагностики, но информация о безопасности остается скрытой. Любые идеи по этому поводу?
Ответы
Ответ 1
Я могу подтвердить, что UPDATE из моего вопроса действительно работает:
object IClientMessageInspector.BeforeSendRequest(ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel)
{
UsernameToken ut = new UsernameToken("USERNAME", "PASSWORD", PasswordOption.SendHashed);
XmlElement securityElement = ut.GetXml(new XmlDocument());
MessageHeader myHeader = MessageHeader.CreateHeader("Security", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd", securityElement, false);
request.Headers.Add(myHeader);
return Convert.DBNull;
}
И клиент:
CustomBehavior behavior = new CustomBehavior("USERNAME", "PASSWORD");
client.Endpoint.Behaviors.Add(behavior);
Сообщение об ошибке не было связано. Заголовок безопасности работает с очень простым basicHttpBinding:
<basicHttpBinding>
<binding name="BasicSOAPBinding">
<security mode="Transport" />
</binding>
</basicHttpBinding>
Пример кода этого действия можно найти в моем блоге: http://benpowell.org/supporting-the-ws-i-basic-profile-password-digest-in-a-wcf-client-proxy/
Ответ 2
Этот вопрос хорошо написан - большое спасибо. В отношении комментария @Junto "Как это использовать" получается, что параметр SecurityHeader в методе службы может использоваться для добавления заголовка. Я привел пример ниже. Я считаю, что происходит то, что инструмент SvcUtil.exe работает при попытке прочитать DTD WS *. Это не очевидно, когда вы используете мастер "Добавить службу". Но это очень очевидно, когда вы запускаете svcutil.exe из командной строки. Поскольку svcutil.exe не может прочитать WS * DTD, объект SecurityHeader недостаточно развит. Но Microsoft дает вам возможность использовать свойство .Any. Вы можете сериализовать класс UsernameToken прямо в свойство .Any, и ваш заголовок будет добавлен в сообщение. Опять же, спасибо за этот отличный вопрос.
Как использовать параметр SecurityHeader для добавления заголовка безопасности UsernameToken:
Необходимые инструменты:
Fiddler2 (или аналогичный) - вы действительно не можете понять ничего из этого, не проверяя заголовки http.
Необходимая ссылка:
Microsoft.Web.Services3.dll -- you can reference this 2.0 framework assembly from your 4.0 assembly
вызов службы WCF:
// Initialization of the service...
_service = new MyService("MyEndpoint", RemoteUri);
// etc.
// Calling the service -- note call to GetSecurityHeader()
_service.ServiceAction(GetSecurityHeader(), "myParam1");
// etc.
/// <summary>
/// Construct the WSE 3.0 Security Header
/// </summary>
private SecurityHeader GetSecurityHeader()
{
SecurityHeader h = new SecurityHeader();
UsernameToken t = new UsernameToken(RemoteLogin, RemotePassword, PasswordOption.SendPlainText);
h.Any = new XmlElement[1];
h.Any[0] = t.GetXml(new XmlDocument());
return h;
}
App.config:
<system.serviceModel>
<bindings>
<basicHttpBinding>
<binding name="MyBinding" closeTimeout="00:01:00" openTimeout="00:01:00"
receiveTimeout="00:10:00" sendTimeout="00:10:00" allowCookies="false"
bypassProxyOnLocal="false" hostNameComparisonMode="StrongWildcard"
maxBufferSize="1048576" maxBufferPoolSize="524288" maxReceivedMessageSize="1048576"
messageEncoding="Text" textEncoding="utf-8" transferMode="Buffered"
useDefaultWebProxy="true">
<readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384"
maxBytesPerRead="4096" maxNameTableCharCount="16384" />
<security mode="Transport">
<transport clientCredentialType="None" proxyCredentialType="None"
realm="" />
<message clientCredentialType="UserName" algorithmSuite="Default" />
</security>
</binding>
</basicHttpBinding>
</bindings>
<client>
<endpoint address="http://myservice.com/service.asmx"
binding="basicHttpBinding" bindingConfiguration="MyBinding" contract="MyContract"
name="MyEndpoint" />
</client>
</system.serviceModel>
Ответ 3
Недавно у меня была аналогичная проблема, и я отказался от поиска решения, отличного от WSE. После нескольких дней вытягивания волос я закончил загрузку WSE 3.0 SDK, создав прокси-класс с использованием WseWsdl3.exe и создав новую политику для UsernameToken. Я был в 15мин. В настоящее время для меня работает следующее.
RemoteService service = new RemoteService(); //generated class
UsernameToken token = new UsernameToken(username, password, PasswordOption.SendPlainText);
Policy policy = new Policy();
policy.Assertions.Add(new UsernameOverTransportAssertion());
service.SetClientCredential(token);
service.SetPolicy(policy);
var result = service.MethodCall();