WCF: добавление одноразового номера в UsernameToken

Я пытаюсь подключиться к веб-службе, написанной на Java, но кое-что не могу понять.

При использовании WCF и customBinding почти все в порядке, кроме одной части сообщения SOAP, поскольку в нем отсутствуют узлы Nonce и Created part. Очевидно, я чего-то упускаю, поэтому, если бы вы могли указать мне правильное направление, я был бы очень признателен.

Вот настраиваемая привязка:

<binding name="CustomHTTPBinding">
    <security includeTimestamp="false" authenticationMode="UserNameOverTransport" defaultAlgorithmSuite="Basic256" requireDerivedKeys="True"
              messageSecurityVersion="WSSecurity10WSTrustFebruary2005WSSecureConversationFebruary2005WSSecurityPolicy11BasicSecurityProfile10">
    </security>
    <textMessageEncoding maxReadPoolSize="211" maxWritePoolSize="2132" messageVersion="Soap11"
                         writeEncoding="utf-8"/>
    <httpsTransport />
</binding>

И вот соответствующая часть сообщения:

<o:Security s:mustUnderstand="1" xmlns:o="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
    <o:UsernameToken u:Id="uuid-c306efd1-e84c-410e-a2ad-1046b368582e-1">
        <o:Username>
            <!-- Removed-->
        </o:Username>
        <o:Password>
            <!-- Removed-->
        </o:Password>
    </o:UsernameToken>
</o:Security>

И вот как это должно выглядеть:

<wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" soapenv:mustUnderstand="1">
 <wsse:UsernameToken xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="UsernameToken-25763165">
    <wsse:Username>..</wsse:Username>
    <wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest">..</wsse:Password>
    <wsse:Nonce>6ApOnLn5Aq9KSH46pzzcZA==</wsse:Nonce>
    <wsu:Created>2009-05-13T18:59:23.309Z</wsu:Created>
 </wsse:UsernameToken>
</wsse:Security>

Итак, вопрос: как я могу ввести элементы Nonce и Created внутри части безопасности?


person Adam Vigh    schedule 22.05.2009    source источник
comment
Вы придумали решение этого? Мне было бы интересно узнать.   -  person Jon    schedule 06.08.2009
comment
В конце концов, мы использовали WSE 2 вместо WCF, чтобы обойти возникшие у нас проблемы. Там мы добавили настраиваемую политику для применения UsernameToken к запросу службы, и я думаю, что это все.   -  person Adam Vigh    schedule 07.08.2009


Ответы (5)


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

Сначала добавил настраиваемую привязку в мою конфигурацию

<system.serviceModel>
    <bindings>
      <customBinding>
        <binding name="myCustomBindingConfig">
          <security includeTimestamp="false" 
                    authenticationMode="UserNameOverTransport" 
                    defaultAlgorithmSuite="Basic256" 
                    requireDerivedKeys="true"
                    messageSecurityVersion="WSSecurity10WSTrustFebruary2005WSSecureConversationFebruary2005WSSecurityPolicy11BasicSecurityProfile10">
          </security>
          <textMessageEncoding messageVersion="Soap11"></textMessageEncoding>
          <httpsTransport maxReceivedMessageSize="2000000000" />
        </binding>
      </customBinding>
    </bindings>
</system.serviceModel>

<client>
    <endpoint address="https://..." [other tags] 
        binding="customBinding" bindingConfiguration="OrangeLeapCustomBindingConfig"/>
</client>

Затем возьмите этот код, который находится здесь: http://social.msdn.microsoft.com/Forums/en-US/wcf/thread/4df3354f-0627-42d9-b5fb-6e880b60f8ee и измените его, чтобы создать одноразовый номер (просто случайный хеш, базовый -64 закодировано)

protected override void WriteTokenCore(System.Xml.XmlWriter writer, System.IdentityModel.Tokens.SecurityToken token)
{
    Random r = new Random();
    string tokennamespace = "o";
    DateTime created = DateTime.Now;
    string createdStr = created.ToString("yyyy-MM-ddTHH:mm:ss.fffZ");
    string nonce = Convert.ToBase64String(Encoding.ASCII.GetBytes(SHA1Encrypt(created + r.Next().ToString())));
    System.IdentityModel.Tokens.UserNameSecurityToken unToken = (System.IdentityModel.Tokens.UserNameSecurityToken)token;
    writer.WriteRaw(String.Format(
    "<{0}:UsernameToken u:Id=\"" + token.Id + "\" xmlns:u=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd\">" +
    "<{0}:Username>" + unToken.UserName + "</{0}:Username>" +
    "<{0}:Password Type=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText\">" +
    unToken.Password + "</{0}:Password>" +
    "<{0}:Nonce EncodingType=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary\">" +
    nonce + "</{0}:Nonce>" +
    "<u:Created>" + createdStr + "</u:Created></{0}:UsernameToken>", tokennamespace));
}

protected String ByteArrayToString(byte[] inputArray)
{
    StringBuilder output = new StringBuilder("");
    for (int i = 0; i < inputArray.Length; i++)
    {
    output.Append(inputArray[i].ToString("X2"));
    }
    return output.ToString();
}
protected String SHA1Encrypt(String phrase)
{
    UTF8Encoding encoder = new UTF8Encoding();
    SHA1CryptoServiceProvider sha1Hasher = new SHA1CryptoServiceProvider();
    byte[] hashedDataBytes = sha1Hasher.ComputeHash(encoder.GetBytes(phrase));
    return ByteArrayToString(hashedDataBytes);
}
person Bron Davies    schedule 01.11.2010
comment
Спасибо за ответ. Я не могу сейчас попробовать, но вроде нормально, так что я приму это. - person Adam Vigh; 16.11.2010
comment
Возможно, это старый ответ, но он, возможно, решил проблему, с которой я столкнулся, разговаривая с веб-службой магазина Java! Спасибо! Единственный недостающий элемент был на связанной странице Microsoft, после добавления настраиваемого поведения необходимо указать имя пользователя и пароль (в поле service.ClientCredentials.UserName) - person John T; 17.11.2011
comment
@JohnT: Думаю, мне не хватает одного недостающего элемента. Я делаю следующее: mhsClient.ChannelFactory.Endpoint.Behaviors.Remove<ClientCredentials>(); mhsClient.ChannelFactory.Endpoint.Behaviors.Add(new CustomCredentials()); mhsClient.ClientCredentials.UserName.UserName = "username"; mhsClient.ClientCredentials.UserName.Password = "password"; и получаю следующую ошибку: "Text cannot be written outside the root element". - person Isaac Kleinman; 24.01.2014
comment
@IsaacKleinman: Я получаю ту же ошибку. Есть ли шанс, что вы исправили это и действительно помните, что вы делали? - person Troels Larsen; 23.08.2016
comment
Служба, к которой я подключался, не жаловалась на отсутствие одноразового номера, поэтому я просто оставил ее. Я подробно описал свою работу здесь. - person Isaac Kleinman; 23.08.2016
comment
@IsaacKleinman: А. К сожалению мне это нужно, но спасибо за статью! Честно говоря, я не ожидал никакого ответа, и уж точно не такого быстрого: D - person Troels Larsen; 24.08.2016
comment
Для всех, у кого возникла такая же проблема, подход (с writer.WriteStartElement) отсюда работал: msdn.microsoft.com/en-us/library/ - person Troels Larsen; 24.08.2016

У меня такая же проблема. Вместо настраиваемого сериализатора токенов я использовал MessageInspector, чтобы добавить правильный UsernameToken в метод BeforeSendRequest. Затем я применил собственное поведение, чтобы применить исправление.

Весь процесс задокументирован (с помощью демонстрационного проекта) в моем сообщении в блоге Поддержка пароля базового профиля WS-I Дайджест в клиентском прокси-сервере WCF. Вы также можете просто прочитать PDF.

Если вы хотите проследить, как я продвигаюсь к решению, вы найдете его в StackOverflow под названием «Ошибка в клиенте WCF, использующем веб-службу Axis 2 со схемой аутентификации WS-Security UsernameToken PasswordDigest":

person Rebecca    schedule 30.03.2011
comment
Спасибо, ты спас мне день. - person Sebacote; 22.09.2016
comment
@Sebacote рад помочь. Было больно приступить к работе, поэтому я подумал, что поделюсь своим опытом. - person Rebecca; 23.09.2016

В этой статье представлен образец с полной интеграцией UserNameToken Профиль с переваренным паролем в конвейер безопасности WCF.

person Ladislav Mrnka    schedule 30.03.2011
comment
И этот ответ заставил меня использовать дайджест-пароль :) - person John T; 17.11.2011

Стоит отметить, что Рик Страл сделал сообщение в блоге (на которое он ссылается на этот вопрос), где он все довольно четко объясняет и предлагает решения как для пароля, так и для PasswordDigest.

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

WCF WSSecurity и WSE Nonce Аутентификация

person Matt Kemp    schedule 16.10.2013
comment
Приведенные выше ответы оставляют несколько шагов для новичков в SOAP, в этой статье дается пошаговый способ решения этой проблемы и объясняется каждый шаг на этом пути. - person h8a; 08.11.2013
comment
Это именно то, что я искал. Принятый ответ здесь не объясняет, где разместить код, но ведет к записи в блоге Рика Страла. Поддерживая этот ответ. - person MeanGreen; 10.04.2015

Мне также пришлось поместить сегмент UserNameHeader в заголовок сообщения SOAP:

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:urn="urn:bar:services" xmlns:efm="urn:bar:services">
   <soapenv:Header>
       <efm:UserNameHeader>
           <UserName>foouser</UserName>
           <Password>foopass</Password>
       </efm:UserNameHeader>
   </soapenv:Header>
   <soapenv:Body>
      <urn:GetUserList/>
   </soapenv:Body>
</soapenv:Envelope>

Это было достигнуто с помощью настраиваемого заголовка сообщения:

public class UserNamePasswordHeader : MessageHeader
{
    private readonly string _serviceUserEmail;
    private readonly string _serviceUserPassword;

    public UserNamePasswordHeader(string serviceUserEmail, string serviceUserPassword)
    {
        this._serviceUserEmail = serviceUserEmail;
        this._serviceUserPassword = serviceUserPassword;
    }

    public override string Name
    {
        get { return "UserNameHeader"; }
    }

    public override string Namespace
    {
        get { return "urn:bar:services"; }
    }

    protected override void OnWriteHeaderContents(XmlDictionaryWriter writer, MessageVersion messageVersion)
    {
        writer.WriteElementString("UserName", _serviceUserEmail);
        writer.WriteElementString("Password", _serviceUserPassword);
    }
}

Можно легко добавить другие теги, такие как Nonce и Created.

Класс используется следующим образом:

var service = new BarServiceClient();
service.ClientCredentials.ClientCertificate.Certificate = MessageSigningCertificate;

using (new OperationContextScope(service.InnerChannel))
{
    OperationContext.Current.OutgoingMessageHeaders.Add(
      new UserNamePasswordHeader(serviceUserEmail, serviceUserPassword));

    try
    {
        var response = service.GetUserList();
        return response;
    }
    finally
    {
        service.Close();
    }
}

Примечание. MessageSigningCertificate - это сертификат X.509, я прочитал его из файла:

private static X509Certificate2 LoadCertificateFromFile(string pfxFilePath, string privateKeyPassword)
{
    // Load the certificate from a file, specifying the password
    var certificate = new X509Certificate2(pfxFilePath, privateKeyPassword);
    return certificate;
}
person Handcraftsman    schedule 20.08.2013