在 C# 中以编程方式创建 WCF 客户端的标头(wsse)部分

Posted

技术标签:

【中文标题】在 C# 中以编程方式创建 WCF 客户端的标头(wsse)部分【英文标题】:Creating Headers (wsse) Section of WCF Client Programmatically in C# 【发布时间】:2011-11-07 00:47:21 【问题描述】:

如何以编程方式在 C# 中创建 app.config 的以下服务设置部分:

    <client>
  <endpoint address="https://someServiceUrl"
      binding="basicHttpBinding" bindingConfiguration="Contact"
      contract="ServiceReference.PostingWebService" name="PostingWebServicePort">
    <headers>
      <wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
        <wsse:UsernameToken>
          <wsse:Username>someusername</wsse:Username>
          <wsse:Password Type='http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText'>somepassword</wsse:Password>
        </wsse:UsernameToken>
      </wsse:Security>
    </headers>
  </endpoint>
</client>

我已经设法从 C# 生成绑定部分(上面未包括)和端点部分。我无法创建标题部分。

出现的错误是:(这是因为我从 C# 生成所有内容时没有标题部分)

此服务需要&lt;wsse:Security&gt;,但缺少。

headers 部分很重要,就好像我将它从配置中排除并使用 config 运行代码一样,它也会给出上述错误。

我不想使用 web.config/app.config。我必须从 C# 运行所有东西。 (上面的 app.config 工作正常,但我想通过 C# 做同样的事情)

注意:下面的更新基于下面提供的解决方案,请仔细阅读下面对解决方案的评论,以便更好地理解

更新 1:(首先以编程方式使用 BasicHttpBinding)

BasicHttpBinding binding = new BasicHttpBinding();
        binding.Name = "Contact";
        binding.CloseTimeout = TimeSpan.FromMinutes(1);
        binding.OpenTimeout = TimeSpan.FromMinutes(1);
        binding.ReceiveTimeout = TimeSpan.FromMinutes(10);
        binding.SendTimeout = TimeSpan.FromMinutes(1);
        binding.AllowCookies = false;
        binding.BypassProxyOnLocal = false;
        binding.HostNameComparisonMode = HostNameComparisonMode.StrongWildcard;
        binding.MaxBufferSize = 524288;
        binding.MaxBufferPoolSize = 524288;
        binding.MaxReceivedMessageSize = 524288;
        binding.MessageEncoding = WSMessageEncoding.Text;
        binding.TextEncoding = System.Text.Encoding.UTF8;
        binding.TransferMode = TransferMode.Buffered;
        binding.UseDefaultWebProxy = true;

        binding.ReaderQuotas.MaxDepth = 32;
        binding.ReaderQuotas.MaxStringContentLength = 65536;
        binding.ReaderQuotas.MaxArrayLength = 131072;
        binding.ReaderQuotas.MaxBytesPerRead = 32768;
        binding.ReaderQuotas.MaxNameTableCharCount = 131072;

        binding.Security.Mode = BasicHttpSecurityMode.Transport;
        binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.None;
        binding.Security.Transport.ProxyCredentialType = HttpProxyCredentialType.None;
        binding.Security.Transport.Realm = "";
        binding.Security.Message.ClientCredentialType = BasicHttpMessageCredentialType.UserName;
        binding.Security.Message.AlgorithmSuite = System.ServiceModel.Security.SecurityAlgorithmSuite.Default;

        CustomBinding customBinding = new CustomBinding(binding);
        SecurityBindingElement element = customBinding.Elements.Find<SecurityBindingElement>();
        // Remove security timestamp because it is not used by your original binding
        //element.IncludeTimestamp = false; (element is NULL in my case)

EndpointAddress endpoint = new EndpointAddress("https://myserviceaddress");

        PostingWebServiceClient client = new PostingWebServiceClient(customBinding, endpoint);

        client.ClientCredentials.UserName.UserName = "myusername";
        client.ClientCredentials.UserName.Password = "mypassword";

        client.getActiveChannels(new getActiveChannels());

直接使用自定义绑定:

SecurityBindingElement securityElement = SecurityBindingElement.CreateUserNameOverTransportBindingElement();
        securityElement.IncludeTimestamp = false;
        TextMessageEncodingBindingElement encodingElement = new TextMessageEncodingBindingElement(MessageVersion.Soap11, Encoding.UTF8);
        HttpsTransportBindingElement transportElement = new HttpsTransportBindingElement();

        CustomBinding customBinding = new CustomBinding(securityElement, encodingElement, transportElement);


EndpointAddress endpoint = new EndpointAddress("https://myserviceaddress");

        PostingWebServiceClient client = new PostingWebServiceClient(customBinding, endpoint);

        client.ClientCredentials.UserName.UserName = "myusername";
        client.ClientCredentials.UserName.Password = "mypassword";

        client.getActiveChannels(new getActiveChannels());

【问题讨论】:

第一个例子是错误的。您使用了不正确的安全模式。您必须使用 TransportWithMessage 凭据,而不仅仅是 Transport。在您的第二个示例中,错误显然是在身份验证中。服务找到用户名和密码,但返回无效。 非常感谢您总结答案,这是我找到的添加安全标头的最干净的方法。 【参考方案1】:

在这种情况下您不必直接配置标头,因为您的方案应该由BasicHttpBindingCustomBinding 直接支持。

如果您需要从 C# 配置它,您必须在代码中创建绑定:

// Helper binding to have transport security with user name token
BasicHttpBinding binding = new BasicHttpBinding(BasicHttpSecurityMode.TransportWithMessageCredential);
binding.Security.Message.ClientCredentialType = BasicHttpMessageCredentialType.UserName;
// Rest of your binding configuration comes here

// Custom binding to have access to more configuration details of basic binding
CustomBinding customBinding = new CustomBinding(binding);
SecurityBindingElement element = customBinding.Elements.Find<SecurityBindingElement>();
// Remove security timestamp because it is not used by your original binding
element.IncludeTimestamp = false;

EndpointAddress address = new EndpointAddress("https://...");

ProxyWebServiceClient client = new ProxyWebServiceClient(customBinding, address);
client.ClientCredentials.UserName.UserName = "...";
client.ClientCredentials.UserName.Password = "...";

其他解决方案是直接构建自定义绑定,而不是从基本绑定开始:

SecurityBindingElemetn securityElement = SecurityBindingElement.CreateUserNameOverTransportBindingElement();
securityElement.IncludeTimestamp = false; 
TextMessageEncodingBindingElement encodingElement = new TextMessageEncodingBindingElement(MessageVersion.Soap11, Encoding.UTF8);
HttpsTransportBindingElement tranportElement = new HttpsTransportBindingElement();

// Other configurations of basic binding are divided into properties of 
// encoding and transport elements

CustomBinding customBinding = new CustomBinding(securityElement, encodingElement, transportElement);

EndpointAddress address = new EndpointAddress("https://...");

ProxyWebServiceClient client = new ProxyWebServiceClient(customBinding, address);
client.ClientCredentials.UserName.UserName = "...";
client.ClientCredentials.UserName.Password = "...";

【讨论】:

以上都不起作用。在第一种情况下(我首先创建基本绑定),我在 SecurityBindingElement element = customBinding.Elements.Find(); 处得到 null所以不能设置 element.IncludeTimestamp = false;因为元素为空。评论 element.IncludeTimestamp = false; out 再次给了我同样的错误(即:此服务需要 ,这是缺失的。) 在第二个场景中,我直接创建自定义绑定,错误是(身份验证失败:无效的用户名:myusername)但我确定我的凭据是正确的 我正在更新我的问题中的两个 senario 代码,请看一下 我在您的问题下的评论中添加了第一个场景对您不起作用的原因。第二种情况有效。现在您遇到了传递凭据的问题,您必须与服务提供商讨论为什么它不起作用。 我尝试了 TransportWithMessageCredential,现在的错误是:收到来自对方的不安全或安全错误。有关故障代码和详细信息,请参阅内部 FaultException。内部异常:发生内部 WS-Security 错误。详情见日志【参考方案2】:

查看this *** question. 的已接受答案,它显示了如何以编程方式将客户端凭据添加到代理。它还显示了在客户端端点配置 XML 中添加标头,这是我以前从未见过的。

【讨论】:

以上是关于在 C# 中以编程方式创建 WCF 客户端的标头(wsse)部分的主要内容,如果未能解决你的问题,请参考以下文章

以编程方式使用证书身份验证配置 WCF 服务客户端

以编程方式添加终结点标头安全 WCF

如何以编程方式为 WCF 服务创建自签名证书?

在 WCF 服务 C# 的服务器端获取客户端的 Mac 地址不重复(在 WCF 3.0 中获取客户端 IP 地址)

如何在 Sharepoint 的 c# 非可视 Web 部件中以编程方式创建进度更新控件

在 C# 中以编程方式创建防火墙规则以打开每个应用程序的端口