草庐IT

c# - ClientMessageInspector 添加 BinarySecurityToken 和签名

coder 2024-05-28 原文

我正在尝试在桌面应用程序中使用 C# 使用 Java Web 服务。
My first attempt正在使用 WebServicesClientProtocol,但我无法添加 WSSE Username and Token Security Spec 1.1

所需的必要属性

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

<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

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

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

<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 的构造函数是 protected 。

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

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

我在网上搜索过类似的问题,但我找到的都是关于如何添加用户名和密码的教程,关于添加SignatureBinarySecurityToken 的问题仍然没有答案- How to sign xml with X509 cert, add digest value and signature to xml template

最佳答案

此编码绑定(bind)应产生类似的消息:

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;

您选择的路径需要您自己实现消息签名,这更难。

关于c# - ClientMessageInspector 添加 BinarySecurityToken 和签名,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42149068/

有关c# - ClientMessageInspector 添加 BinarySecurityToken 和签名的更多相关文章

  1. ruby - 我需要将 Bundler 本身添加到 Gemfile 中吗? - 2

    当我使用Bundler时,是否需要在我的Gemfile中将其列为依赖项?毕竟,我的代码中有些地方需要它。例如,当我进行Bundler设置时:require"bundler/setup" 最佳答案 没有。您可以尝试,但首先您必须用鞋带将自己抬离地面。 关于ruby-我需要将Bundler本身添加到Gemfile中吗?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/4758609/

  2. ruby - 将 Bootstrap Less 添加到 Sinatra - 2

    我有一个ModularSinatra应用程序,我正在尝试将Bootstrap添加到应用程序中。get'/bootstrap/application.css'doless:"bootstrap/bootstrap"end我在views/bootstrap中有所有less文件,包括bootstrap.less。我收到这个错误:Less::ParseErrorat/bootstrap/application.css'reset.less'wasn'tfound.Bootstrap.less的第一行是://CSSReset@import"reset.less";我尝试了所有不同的路径格式,但它

  3. ruby - 续集在添加关联时访问many_to_many连接表 - 2

    我正在使用Sequel构建一个愿望list系统。我有一个wishlists和itemstable和一个items_wishlists连接表(该名称是续集选择的名称)。items_wishlists表还有一个用于facebookid的额外列(因此我可以存储opengraph操作),这是一个NOTNULL列。我还有Wishlist和Item具有续集many_to_many关联的模型已建立。Wishlist类也有:selectmany_to_many关联的选项设置为select:[:items.*,:items_wishlists__facebook_action_id].有没有一种方法可以

  4. c# - 如何在 ruby​​ 中调用 C# dll? - 2

    如何在ruby​​中调用C#dll? 最佳答案 我能想到几种可能性:为您的DLL编写(或找人编写)一个COM包装器,如果它还没有,则使用Ruby的WIN32OLE库来调用它;看看RubyCLR,其中一位作者是JohnLam,他继续在Microsoft从事IronRuby方面的工作。(估计不会再维护了,可能不支持.Net2.0以上的版本);正如其他地方已经提到的,看看使用IronRuby,如果这是您的技术选择。有一个主题是here.请注意,最后一篇文章实际上来自JohnLam(看起来像是2009年3月),他似乎很自在地断言RubyCL

  5. C# 到 Ruby sha1 base64 编码 - 2

    我正在尝试在Ruby中复制Convert.ToBase64String()行为。这是我的C#代码:varsha1=newSHA1CryptoServiceProvider();varpasswordBytes=Encoding.UTF8.GetBytes("password");varpasswordHash=sha1.ComputeHash(passwordBytes);returnConvert.ToBase64String(passwordHash);//returns"W6ph5Mm5Pz8GgiULbPgzG37mj9g="当我在Ruby中尝试同样的事情时,我得到了相同sha

  6. ruby - 可以通过多少种方法将方法添加到 ruby​​ 对象? - 2

    当谈到运行时自省(introspection)和动态代码生成时,我认为ruby​​没有任何竞争对手,可能除了一些lisp方言。前几天,我正在做一些代码练习来探索ruby​​的动态功能,我开始想知道如何向现有对象添加方法。以下是我能想到的3种方法:obj=Object.new#addamethoddirectlydefobj.new_method...end#addamethodindirectlywiththesingletonclassclass这只是冰山一角,因为我还没有探索instance_eval、module_eval和define_method的各种组合。是否有在线/离线资

  7. ruby - 如何在 Ruby 中向现有方法定义添加语句 - 2

    我注意到类定义,如果我打开classMyClass,并在不覆盖的情况下添加一些东西我仍然得到了之前定义的原始方法。添加的新语句扩充了现有语句。但是对于方法定义,我仍然想要与类定义相同的行为,但是当我打开defmy_method时似乎,def中的现有语句和end被覆盖了,我需要重写一遍。那么有什么方法可以使方法定义的行为与定义相同,类似于super,但不一定是子类? 最佳答案 我想您正在寻找alias_method:classAalias_method:old_func,:funcdeffuncold_func#similartoca

  8. ruby-on-rails - 添加回形针新样式不影响旧上传的图像 - 2

    我有带有Logo图像的公司模型has_attached_file:logo我用他们的Logo创建了许多公司。现在,我需要添加新样式has_attached_file:logo,:styles=>{:small=>"30x15>",:medium=>"155x85>"}我是否应该重新上传所有旧数据以重新生成新样式?我不这么认为……或者有什么rake任务可以重新生成样式吗? 最佳答案 参见Thumbnail-Generation.如果rake任务不适合你,你应该能够在控制台中使用一个片段来调用重新处理!关于相关公司

  9. ruby - 我如何添加二进制数据来遏制 POST - 2

    我正在尝试使用Curbgem执行以下POST以解析云curl-XPOST\-H"X-Parse-Application-Id:PARSE_APP_ID"\-H"X-Parse-REST-API-Key:PARSE_API_KEY"\-H"Content-Type:image/jpeg"\--data-binary'@myPicture.jpg'\https://api.parse.com/1/files/pic.jpg用这个:curl=Curl::Easy.new("https://api.parse.com/1/files/lion.jpg")curl.multipart_form_

  10. 基于C#实现简易绘图工具【100010177】 - 2

    C#实现简易绘图工具一.引言实验目的:通过制作窗体应用程序(C#画图软件),熟悉基本的窗体设计过程以及控件设计,事件处理等,熟悉使用C#的winform窗体进行绘图的基本步骤,对于面向对象编程有更加深刻的体会.Tutorial任务设计一个具有基本功能的画图软件**·包括简单的新建文件,保存,重新绘图等功能**·实现一些基本图形的绘制,包括铅笔和基本形状等,学习橡皮工具的创建**·设计一个合理舒适的UI界面**注明:你可能需要先了解一些关于winform窗体应用程序绘图的基本知识,以及关于GDI+类和结构的知识二.实验环境Windows系统下的visualstudio2017C#窗体应用程序三.

随机推荐