ClientMessageInspector 添加 BinarySecurityToken 和 Signature [英] ClientMessageInspector add BinarySecurityToken and Signature

查看:132
本文介绍了ClientMessageInspector 添加 BinarySecurityToken 和 Signature的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试在桌面应用程序中使用 C# 来使用 Java Web 服务.
我的第一次尝试是使用 WebServicesClientProtocol,但我不是能够添加WSSE用户名和令牌安全规范1.1

I'm trying to consume Java Web Service using C# in desktop application.
My first attempt was using WebServicesClientProtocol, but I'm not able to add necessary attribute that is required by WSSE Username and Token Security Spec 1.1

我需要创建具有这种结构的请求:

I need to create request that has this structure:

<soap:Envelope xmlns:dz="http://dom.query.api.com" xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:xsd="http://dz.api.swd.zbp.pl/xsd">
    <soap:Header>
        <wsse:Security 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:UsernameToken wsu:Id="UsernameToken-E94CEB6F4708FB7C23148611494797612">
                <wsse:Username>my_login</wsse:Username>
                <wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest">XqEwZ/CxaBfFvh487TjvN8qD63c=</wsse:Password>
                <wsse:Nonce EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary">JzURe0CxvzRjmEcH/ndldw==</wsse:Nonce>
                <wsu:Created>2017-02-09T09:42:27.976Z</wsu:Created>
            </wsse:UsernameToken>
            <wsse:BinarySecurityToken EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary" ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509PKIPathv1" wsu:Id="X509-E94CEB6F4708FB7C2314861149479517">MIIKnDCCB.........nmIngeg6d6TNI=</wsse:BinarySecurityToken>
            <ds:Signature Id="SIG-E94CEB6F4708FB7C23148611494795311" xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
                <ds:SignedInfo>
                    <ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#">
                        <ec:InclusiveNamespaces PrefixList="dz soap xsd" xmlns:ec="http://www.w3.org/2001/10/xml-exc-c14n#"/>
                    </ds:CanonicalizationMethod>
                    <ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
                    <ds:Reference URI="#id-E94CEB6F4708FB7C23148611494795310">
                        <ds:Transforms>
                            <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#">
                                <ec:InclusiveNamespaces PrefixList="dz xsd" xmlns:ec="http://www.w3.org/2001/10/xml-exc-c14n#"/>
                            </ds:Transform>
                        </ds:Transforms>
                        <ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
                        <ds:DigestValue>mlABQuNUFOmLqsDswxXxQ6XnjpQ=</ds:DigestValue>
                    </ds:Reference>
                </ds:SignedInfo>
                <ds:SignatureValue>lYhBHSQ/L...XL1HEbMQjJ/Q2Rvg==</ds:SignatureValue>
                <ds:KeyInfo Id="KI-E94CEB6F4708FB7C2314861149479518">
                    <wsse:SecurityTokenReference wsse11:TokenType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509PKIPathv1" wsu:Id="STR-E94CEB6F4708FB7C2314861149479519" xmlns:wsse11="http://docs.oasis-open.org/wss/oasis-wss-wssecurity-secext-1.1.xsd">
                        <wsse:Reference URI="#X509-E94CEB6F4708FB7C2314861149479517" ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509PKIPathv1"/>
                    </wsse:SecurityTokenReference>
                </ds:KeyInfo>
            </ds:Signature>
        </wsse:Security>
    </soap:Header>
    <soap:Body wsu:Id="id-E94CEB6F4708FB7C23148611494795310" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
        <dz:query>
            <dz:param>
                <xsd:userQueryId>27467</xsd:userQueryId>
            </dz:param>
        </dz:query>
    </soap:Body>
</soap:Envelope>

我已经设法使用 IEndpointBehavior 和 IClientMessageInspector 创建自定义类,但使用它们我只能添加 UsernameToken

I've managed to create custom classes using IEndpointBehavior and IClientMessageInspector, but with them I'm only able to add UsernameToken

public class InspectorBehavior : IEndpointBehavior
{
    /// <summary>
    /// Gets or sets the custom ClientInspector.
    /// </summary>
    public ClientInspector ClientInspector { get; set; }

    /// <summary>
    /// Constructs a new InspectorBehavior
    /// </summary>
    /// <param name="clientInspector"><see cref="ClientInspector"/></param>
    public InspectorBehavior(ClientInspector clientInspector)
    {
        ClientInspector = clientInspector;
    }

    /// <summary>
    /// Implement to confirm that the endpoint meets some intended criteria.
    /// </summary>
    /// <param name="endpoint"><see cref="ServiceEndpoint"/></param>
    public void Validate(ServiceEndpoint endpoint)
    {
        // not calling the base implementation
    }

    /// <summary>
    /// Implement to pass data at runtime to bindings to support custom behavior.
    /// </summary>
    /// <param name="endpoint"><see cref="ServiceEndpoint"/></param>
    /// <param name="bindingParameters"><see cref="BindingParameterCollection"/></param>
    public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
    {
        // not calling the base implementation
    }

    /// <summary>
    /// Implements a modification or extension of the service across an endpoint.
    /// </summary>
    /// <param name="endponit"><see cref="ServiceEndpoint"/></param>
    /// <param name="endpointDispatcher"><see cref="EndpointDispatcher"/></param>
    public void ApplyDispatchBehavior(ServiceEndpoint endponit, EndpointDispatcher endpointDispatcher)
    {
        // not calling the base implementation
    }

    /// <summary>
    /// Implements the custom modification of the WCF client across an endpoint.
    /// </summary>
    /// <param name="endpoint"><see cref="ServiceEndpoint"/></param>
    /// <param name="clientRuntime"><see cref="ClientRuntime"/></param>
    public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
    {
        if (this.ClientInspector == null)
            throw new InvalidOperationException("Caller must supply ClientInspector.");

        clientRuntime.ClientMessageInspectors.Add(ClientInspector);
    }
}

public class ClientInspector : IClientMessageInspector
{
    /// <summary>
    /// Gets or sets the custom MessageHeader.
    /// </summary>
    public MessageHeader[] Headers
    {
        get;
        set;
    }

    /// <summary>
    /// Constructs a new ClientInspector
    /// </summary>
    /// <param name="headers"><see cref="MessageHeader"/></param>
    public ClientInspector(params MessageHeader[] headers)
    {
        Headers = headers;
    }

    /// <summary>
    /// Enables inspection or modification of a message before a request message is sent to a service.
    /// </summary>
    /// <param name="request"><see cref="Message"/></param>
    /// <param name="channel"><see cref="IClientChannel"/></param>
    /// <returns></returns>
    public object BeforeSendRequest(ref Message request, IClientChannel channel)
    {
        if (Headers != null)
        {
            for (int i = Headers.Length - 1; i >= 0; i--)
                request.Headers.Insert(0, Headers[i]);
        }

        return request;
    }

    /// <summary>
    /// Enables inspection or modification of a message after a reply message is received but 
    /// prior to passing it back to the client.
    /// </summary>
    /// <param name="reply"><see cref="Message"/></param>
    /// <param name="correlationState">object</param>
    public void AfterReceiveReply(ref Message reply, object correlationState)
    {
        // not calling the base implementation
    }
}

public class SecurityHeader : MessageHeader
{
    private readonly APIConfig config;

    /// <summary>
    /// Constructors a new SecurityHeader
    /// </summary>
    /// <param name="config"><see cref="APIConfig"/></param>
    public SecurityHeader(APIConfig config)
    {
        this.config = config;
    }

    /// <summary>
    /// Gets or sets a value that indicates whether the header must be understood, according to SOAP 1.1/1.2 specification.
    /// </summary>
    public override bool MustUnderstand
    {
        get
        {
            return true;
        }
    }

    /// <summary>
    /// Gets the name of the message header.
    /// </summary>
    public override string Name
    {
        get
        {
            return "Security";
        }
    }

    /// <summary>
    /// Gets the namespace of the message header.
    /// </summary>
    public override string Namespace
    {
        get
        {
            return "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd";
        }
    }

    protected override void OnWriteStartHeader(XmlDictionaryWriter writer, MessageVersion messageVersion)
    {
        writer.WriteStartElement("wsse", Name, Namespace);
        writer.WriteXmlnsAttribute("wsse", Namespace);
    }

    /// <summary>
    /// Called when the header content is serialized using the specified XML writer.
    /// </summary>
    /// <param name="writer"><see cref="XmlDictionaryWriter"/></param>
    /// <param name="messageVersion"><see cref="MessageVersion"/></param>
    protected override void OnWriteHeaderContents(XmlDictionaryWriter writer, MessageVersion messageVersion)
    {
        WriteHeader(writer);
    }

    /// <summary>
    /// Overwrites the default SOAP Security Header values generated by WCF with
    /// those required by the UserService which implements WSE 2.0.  This is required
    /// for interoperability between a WCF Client and a WSE 2.0 Service.
    /// </summary>
    /// <param name="writer"><see cref="XmlDictionaryWriter"/></param>
    private void WriteHeader(XmlDictionaryWriter writer)
    {
        // Create the Nonce
        byte[] nonce = GenerateNonce();

        // Create the Created Date
        string created = DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ss.fffZ");

        // Create the WSSE Security Header, starting with the Username Element
        writer.WriteStartElement("wsse", "UsernameToken", Namespace);
        writer.WriteXmlnsAttribute("wsu", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd");
        writer.WriteStartElement("wsse", "Username", null);
        writer.WriteString(config.Username);
        writer.WriteEndElement();

        // Add the Password Element
        writer.WriteStartElement("wsse", "Password", null);
        writer.WriteAttributeString("Type", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest");
        writer.WriteString(GeneratePasswordDigest(nonce, created, config.Password));
        writer.WriteEndElement();

        // Add the Nonce Element
        writer.WriteStartElement("wsse", "Nonce", null);
        writer.WriteAttributeString("EncodingType", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary");
        writer.WriteBase64(nonce, 0, nonce.Length);
        writer.WriteEndElement();

        // Lastly, add the Created Element
        writer.WriteStartElement("wsu", "Created", null);
        writer.WriteString(created);
        writer.WriteEndElement();
        writer.WriteEndElement();
        writer.Flush();
    }

    /// <summary>
    /// Generates a random Nonce for encryption purposes
    /// </summary>
    /// <returns>byte[]</returns>
    private byte[] GenerateNonce()
    {
        RNGCryptoServiceProvider rand = new RNGCryptoServiceProvider();
        byte[] buf = new byte[0x10];
        rand.GetBytes(buf);
        return buf;
    }

    /// <summary>
    /// Generates the PasswordDigest using a SHA1 Hash
    /// </summary>
    /// <param name="nonceBytes">byte[]</param>
    /// <param name="created">string</param>
    /// <param name="password">string</param>
    /// <returns>string</returns>
    private string GeneratePasswordDigest(byte[] nonceBytes, string created, string password)
    {
        // Convert the values to be hashed to bytes
        byte[] createdBytes = Encoding.UTF8.GetBytes(created);
        byte[] passwordBytes = Encoding.UTF8.GetBytes(password);
        byte[] msgBytes = new byte[nonceBytes.Length + createdBytes.Length + passwordBytes.Length];

        // Combine the values into one byte array
        Array.Copy(nonceBytes, msgBytes, nonceBytes.Length);
        Array.Copy(createdBytes, 0, msgBytes, nonceBytes.Length, createdBytes.Length);
        Array.Copy(passwordBytes, 0, msgBytes, (nonceBytes.Length + createdBytes.Length), passwordBytes.Length);

        // Generate the hash
        SHA1CryptoServiceProvider sha1 = new SHA1CryptoServiceProvider();
        byte[] hashBytes = sha1.ComputeHash(msgBytes);
        return Convert.ToBase64String(hashBytes);
    }
}

public class APIConfig
{
    /// <summary>
    /// Gets or Sets the Password property
    /// </summary>
    public string Password
    {
        get;
        set;
    }

    /// <summary>
    /// Gets or Sets the Username property
    /// </summary>
    public string Username
    {
        get;
        set;
    }
}

使用上面的代码,我可以创建这个请求:

With above code I'm able to create this request:

<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
    <s:Header>
        <wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
            <wsse:UsernameToken xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
                <wsse:Username>Demo</wsse:Username>
                <wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest">1TiCoKWfNF3EdEH3qdU4inKklaw=</wsse:Password>
                <wsse:Nonce EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary">mAyz3SywR8sR9IkhDGJRIw==</wsse:Nonce>
                <wsu:Created>2017-02-09T23:29:14.371Z</wsu:Created>
            </wsse:UsernameToken>
        </wsse:Security>
    </s:Header>
    <s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
        <query xmlns="http://dom.query.api.com">
            <param>
                <userQueryId xsi:nil="true" xmlns="http://dom.query.api.com/xsd"/>
            </param>
        </query>
    </s:Body>
</s:Envelope>

如您所见,我的 Security 元素中缺少 BinarySecurityTokenSignature 元素.我试过使用 Microsoft.Web.Services3 但没有成功.
例如 BinarySecurityToken 的构造函数是受保护的.

As You can see I'm missing BinarySecurityToken and Signature elements in my Security element. I've tried using Microsoft.Web.Services3 but without luck.
For example constructor of BinarySecurityToken is protected.

我在我的证书存储中导入了我的客户端证书.我只需要在我的请求正文上签名.

I have my client cert imported inside my cert store. I need to sign only the body of my request.

如何将这两个元素添加到 Header 内的 Security 元素?我知道我必须使用 Microsoft.Web.Services3 但我不知道如何使用.

How can I add those two elements to Security element inside Header? i know I must use Microsoft.Web.Services3 but i don't know how.

我在互联网上搜索了类似的问题,但我发现的只是关于如何添加用户名和密码的教程,有关添加 SignatureBinarySecurityToken 的问题仍未得到解答- 如何使用 X509 证书签署 xml,将摘要值和签名添加到 xml 模板

I've searched over the internet for similar questions, but all I found was tutorials on how to add username and passwords, questions about adding Signature and BinarySecurityToken remains unanswered - How to sign xml with X509 cert, add digest value and signature to xml template

推荐答案

这个编码绑定应该产生类似的消息:

this coded binding should produce a similar message:

var b = new CustomBinding();

            var sec = (AsymmetricSecurityBindingElement)SecurityBindingElement.CreateMutualCertificateBindingElement(MessageSecurityVersion.WSSecurity10WSTrust13WSSecureConversation13WSSecurityPolicy12BasicSecurityProfile10);
            sec.EndpointSupportingTokenParameters.Signed.Add(new UserNameSecurityTokenParameters());
            sec.MessageSecurityVersion =
                MessageSecurityVersion.
                    WSSecurity10WSTrust13WSSecureConversation13WSSecurityPolicy12BasicSecurityProfile10;
            sec.IncludeTimestamp = false;
            sec.MessageProtectionOrder = System.ServiceModel.Security.MessageProtectionOrder.EncryptBeforeSign;

            b.Elements.Add(sec);
            b.Elements.Add(new TextMessageEncodingBindingElement(MessageVersion.Soap11, Encoding.UTF8));
            b.Elements.Add(new HttpsTransportBindingElement());


            var c =
                new ServiceReference1.SimpleServiceSoapClient(b, new EndpointAddress(new Uri("https://www.bankhapoalim.co.il/"), new DnsEndpointIdentity("WSE2QuickStartServer"), new AddressHeaderCollection()));

            c.ClientCredentials.UserName.UserName = "yaron";
            c.ClientCredentials.UserName.Password = "1234";

            c.ClientCredentials.ServiceCertificate.Authentication.CertificateValidationMode =
                System.ServiceModel.Security.X509CertificateValidationMode.None;
            c.ClientCredentials.ServiceCertificate.DefaultCertificate = new X509Certificate2(@"C:\Program Files\Microsoft WSE\v2.0\Samples\Sample Test Certificates\Server Public.cer");

            c.ClientCredentials.ClientCertificate.Certificate = new X509Certificate2(@"C:\Program Files\Microsoft WSE\v2.0\Samples\Sample Test Certificates\Client Private.pfx", "wse2qs");

            c.Endpoint.Contract.ProtectionLevel = System.Net.Security.ProtectionLevel.Sign;

您选择的路径需要您自己实现消息签名,这比较困难.

The path you chose would require you to implement message signing by yourself which is harder.

这篇关于ClientMessageInspector 添加 BinarySecurityToken 和 Signature的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆