Ошибка в клиенте WCF, использующем веб-сервис Axis 2 с WS-Security UsernameToken PasswordDigest схема аутентификации

У меня есть клиент WCF, подключающийся к веб-службе Axis2 на основе Java (вне моего контроля). Он собирается применить WS-Security к нему, и мне нужно исправить клиент.NET. Тем не менее, я пытаюсь обеспечить правильную аутентификацию. Я знаю, что WSE 3.0 может сделать это проще, но я бы предпочел не возвращаться к устаревшей технологии.

Подобные проблемы (нерешенные) включают это, это и это.

Сообщение 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>

Мой клиент выглядит так: PS Обратите внимание на обязательный параметр 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 с использованием пользовательских токенов, но как применить дайджест и одноразовый номер по мере необходимости?

Любая помощь приветствуется.

ОБНОВИТЬ

У меня был некоторый ограниченный успех. Я использовал библиотеку Microsoft.Web.Services3 для создания токена пользователя с правильным дайджестом. Затем я создал свое собственное поведение и в методе 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>

Похоже, что на узле пароля отсутствует атрибутType:

Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest"

Однако я не уверен, что параметры трассировки и ведения журнала безопасности полностью удаляют атрибуты и содержимое этих узлов. Я попытался использовать параметр logKnownPii в журнале диагностики, но информация о безопасности остается скрытой. Есть идеи на этот счет?

3 ответа

Решение

Я могу подтвердить, что ОБНОВЛЕНИЕ от моего вопроса фактически работает:

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);

Сообщение об ошибке не было связано. Заголовок безопасности работает с очень простым базовым HTTBBinding:

<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/

Этот вопрос хорошо написан - большое спасибо. Ссылаясь на комментарий @Junto "Как использовать это", выясняется, что параметр SecurityHeader метода службы может использоваться для добавления заголовка. Я включил пример ниже. Я полагаю, что происходит то, что инструмент SvcUtil.exe раздражает при попытке прочитать WS* DTD. Это не очевидно при использовании мастера "Добавить ссылку на службу". Но это очень очевидно, когда вы запускаете 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>

Недавно у меня была похожая проблема, и я перестал искать решение, не относящееся к 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();
Другие вопросы по тегам