使用C#编程在WCF客户端中创建(wsse)头部部分

11

如何以编程方式使C#的app.config的Service Settings部分如下所示:

    <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#生成所有内容时没有头部部分)

此服务需要缺失的<wsse: Security>

头部部分很重要,因为如果我从配置中排除它并使用配置运行代码,它也会给出以上错误。

我不想使用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”。在第二个例子中,错误显然在于身份验证。服务找到了用户名和密码,但返回它们无效。 - Ladislav Mrnka
非常感谢您总结答案,这是我发现的最干净的添加安全头的方法。 - Jim109
2个回答

6

在这种情况下,您不必直接配置标题,因为您的场景应该由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 = "...";

我在你的问题下面的评论中解释了为什么第一个方案对你不起作用。第二个方案可以工作。现在你遇到了传递凭据的问题,你必须与服务提供商讨论为什么它不起作用。 - Ladislav Mrnka
我尝试使用TransportWithMessageCredential,现在的错误是:从对方接收到了未经安全保护或错误安全保护的故障。查看内部FaultException获取故障代码和详细信息。 内部异常:发生了内部WS-Security错误。 有关详细信息,请参见日志。 - user402186
我的凭据肯定是好的。因为如果我通过我在问题中提到的app.config设置连接到服务,它们仍然有效。 - user402186
服务是如何创建的?它使用什么语言/API? - Ladislav Mrnka
+1 我使用了你的另一个示例在配置中静态设置头,但我不想将其带到每个新配置中。我还希望动态设置用户名密码,这个方法很好用。谢谢! - kwelch
显示剩余8条评论

2

请查看这个StackOverflow问题的被接受答案。它展示了如何在代理中以编程方式添加客户端凭据。它还展示了将标头添加到客户端端点配置XML中,这是我之前未见过的。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接