zoukankan      html  css  js  c++  java
  • 让WCF代替WSE访问需要明文UserName/Password验证的WebService

    (万恶的微软已在KB971831/KB976462添加了一个TransportSecurityBindingElement.AllowInsecureTransport来实现本文的功能了. 日啊~)

        以前我们部门一直通过WSE生成代理类来访问JAVA部门生成的WebService, 因为最近部门里把Visual Studio都升级到2008版了, 大家发现WSE不能再在VS2008里生成其代理类了. 上GOOGLE上调查了一翻, 原来微软为了推广WCF, 不再升级WSE了. 被迫开始研究WCF @_@ 啊~~~~

        同时微软提供了svcutil.exe工具(万恶的命令行程序)帮助开发人员生成访问WebService要用的Client类, 于是网上有好事者做了个svcutil.exe的GUI程序. 我本以为这样生成了Client类, 就能直接访问. 可是一测试问题马上就出来了. 访问带验证的WebService时, WCF本身提供的一些Binding类要求SSL连接或服务器证书. 而我们之前都是用WSE生成的代理类直接访问普通HTTP通道的WebService, 而支持HTTP通道的BasicHttpBinding又不支持UserName/Password验证.

        然而, GOOGLE的过程是痛苦的... 最终得到的N个搜索结果都指向了 Yaron Naveh的ClearUsernameBinding类. 虽然ClearUsernameBinding并没有直接解决我的问题, 但是从其源代码中我明白如何破解WCF内置验证的方法.

        要解决我们这个问题的关键在于创建一个以HTTP为通道并支持UserName/Password验证的Binding类. WCF提供了一个CustomBinding类, 按照MSDN上一篇名为"Creating User-Defined Bindings"的说法, 我们需要给CustomBinding类提供一些BindingElement. 针对当前的问题, 我们需要生成TextMessageEncodingBindingElement, HttpTransportBindingElement和TransportSecurityBindingElement (使用SecurityBindingElement.CreateUserNameOverTransportBindingElement创建).

        1. 首先将TextMessageEncodingBindingElement.MessageVersion设置为Soap11 (默认为Soap12WSAddressing10, 而我们JAVA部门生成的WebService是Soap1.1的).
        2. 然后将TransportSecurityBindingElement.IncludeTimestamp设置为false. 就是因为 ClearUsernameBinding 没有这一步所以它无法解决我们的问题. 其根源可能是JAVA服务端不支持Timestamp吧.
        3. 直接使用 HttpTransportBindingElement 是无法通过WCF本身的验证的, 根据错误信息, 我反射到了 System.ServiceModel.Dispatcher.SecurityValidationBehavior.SoapOverSecureTransportRequirementsRule.ValidateSecurityBinding 这个方法的源代码. 很容易看出我们需要修改 HttpTransportBindingElement.GetProperty 方法的返回的 ISecurityCapabilities 实例的一些值才能通过 ValidateSecurityBinding 方法的验证. 因为ISecurityCapabilities的唯一实现类是internal的, 而且另外2个BindingElement都是sealed的, 所以我们只能自己实现一个 ISecurityCapabilities, 然后再派生一个 HttpTransportBindingElement来重写其 GetProperty 方法来返回我们实现的 ISecurityCapabilities类. 于是我定义了一个 NonSecuredHttpTransportBindingElement 类来继承 HttpTransportBindingElement, 并重写其 GetProperty 方法用来返回我们需要的 ISecurityCapabilities 实体.

    public class NonSecuredHttpTransportBindingElement : HttpTransportBindingElement
    {
        public NonSecuredHttpTransportBindingElement() : base() { }
     
        public NonSecuredHttpTransportBindingElement(NonSecuredHttpTransportBindingElement elementToBeCloned) : base(elementToBeCloned) { }
     
        public override T GetProperty<T>(BindingContext context)
        {
            if(typeof(T) == typeof(ISecurityCapabilities))
            {
                return (T)(ISecurityCapabilities)(new NonSecuredHttpSecurityCapabilities());
            }
     
            return base.GetProperty<T>(context);
        }
     
        public override BindingElement Clone()
        {
            return new NonSecuredHttpTransportBindingElement(this);
        }    
     
        class NonSecuredHttpSecurityCapabilities : ISecurityCapabilities
        {
            #region ISecurityCapabilities Members
     
            public ProtectionLevel SupportedRequestProtectionLevel { get { return ProtectionLevel.EncryptAndSign; } }
     
            public ProtectionLevel SupportedResponseProtectionLevel { get { return ProtectionLevel.EncryptAndSign; } }
     
            public bool SupportsClientAuthentication { get { return false; } }
     
            public bool SupportsClientWindowsIdentity { get { return false; } }
     
            public bool SupportsServerAuthentication { get { return true; } }
     
            #endregion
        }
    }


        现在可以试着访问JAVA部门提供的WebService了. 首先使用svcutil.exe生成一个WCF Client类, 然后写入下列测试代码.

    CustomBinding binding = new CustomBinding();
     
    TransportSecurityBindingElement el1 = SecurityBindingElement.CreateUserNameOverTransportBindingElement();
    el1.IncludeTimestamp = false;
    binding.Elements.Add(el1);
     
    TextMessageEncodingBindingElement el2 = new TextMessageEncodingBindingElement();
    el2.MessageVersion = MessageVersion.Soap11;
    binding.Elements.Add(el2);
     
    NonSecuredHttpTransportBindingElement el3 = new NonSecuredHttpTransportBindingElement();
    binding.Elements.Add(el3);
     
    EndpointAddress ea = new EndpointAddress("http://javadepartment:7777/gateway/services/SID0000010?wsdl");
     
    SearchServiceEJBWebServiceClient client = new SearchServiceEJBWebServiceClient(binding, ea);
    client.ClientCredentials.UserName.UserName = "[UserName]";
    client.ClientCredentials.UserName.Password = "[Password]";
    string result = client.querybuilder("Taylor Swift", "Search All");

        终于拿到了久违的正确结果, 从无尽的Exception中解脱出来了~~!!

    参考文档: Introducing WCF ClearUsernameBinding, Creating User-Defined Bindings

  • 相关阅读:
    c++ 利用new动态的定义二维数组
    golang在linux后台执行的方法
    Linux安装配置go运行环境
    SpringCloud 笔记
    你真的了解 Unicode 和 UTF-8 吗?
    Elasticsearch 系列文章汇总(持续更新...)
    Maven 的依赖范围
    在 centos 上安装 virutalbox
    Java 异常总结
    使用 RabbitMQ 实现异步调用
  • 原文地址:https://www.cnblogs.com/ericfine/p/1632065.html
Copyright © 2011-2022 走看看