RemotingConfiguration.Configure (String, Boolean) 中隐藏的秘密

来源:互联网 发布:单片机高级证书 编辑:程序博客网 时间:2024/06/05 04:21
 

在进行Remoting开发过程中,一种流行的做法是将诸如对象激活方式,注册通道的类型,端口信息等写在配置文件中,然后使用RemotingConfiguration.Configure (String)方法读取这些信息并完成远程调用的配置。此时你会发现,.NET编译器提示该方法已经过期,推荐使用RemotingConfiguration.Configure (String, Boolean),这是.NET FrameWork2.0版中新增的方法。后者与前者的区别仅在于多了一个参数,完整的表示形式为:

public static void Configure ( string filename, bool ensureSecurity )

那么后面的这个ensureSecurity布尔型参数究竟有什么用呢。Msdn上给出的解释非常简单,如下:

ensureSecurity

如果启用安全性,则为 true;否则为 false

显然不满足安全性要求会抛出一个SecurityException异常来,但是如何启用安全性,处理针对谁的安全性都没有详细的说明。在Google上搜索了一下,发现大家还热衷于使用那个过时的版本,很少有用public static void Configure ( string filename, bool ensureSecurity )这种形式的,至于相关说明就更是少之又少。难道微软提出了这样一个新的方法只是在作秀吗?

不,其实这个方法中隐藏着许多秘密,要想知道更多的信息不妨让我们用反编译工具揭开它神秘的面纱,看看内部究竟做了些什么?下面是用.Net Reflecter工具看到的Configure ( string filename, bool ensureSecurity )内部实现代码:

[SecurityPermission(SecurityAction.Demand, Flags=SecurityPermissionFlag.RemotingConfiguration)]
public static void Configure(string filename, bool ensureSecurity)
{
      RemotingConfigHandler.DoConfiguration(filename, ensureSecurity);
      RemotingServices.InternalSetRemoteActivationConfigured();
}

看来,Configure方法只是最外面的一层包装纸,真正进行远程配置的其实是里面的两个方法。跟踪到DoConfiguration()方法中去,如下:

internal static void DoConfiguration(string filename, bool ensureSecurity)
{
      RemotingConfigHandler.LoadMachineConfigIfNecessary();
      RemotingXmlConfigFileData data1 = RemotingConfigHandler.LoadConfigurationFromXmlFile(filename);
      if (data1 != null)
      {
            RemotingConfigHandler.ConfigureRemoting(data1, ensureSecurity);
      }
}

继续探查RemotingConfigHandlerConfigureRemoting方法作了什么,感觉离真相已经不远了。

private static void ConfigureRemoting(RemotingXmlConfigFileData configData, bool ensureSecurity)
{
      try
      {
            string text1 = configData.ApplicationName;
            if (text1 != null)
            {
                  RemotingConfigHandler.ApplicationName = text1;
            }
            if (configData.CustomErrors != null)
            {
                  RemotingConfigHandler._errorMode = configData.CustomErrors.Mode;
            }
            RemotingConfigHandler.ConfigureChannels(configData, ensureSecurity);
            if (configData.Lifetime != null)
            {
                  if (configData.Lifetime.IsLeaseTimeSet)
                  {
                        LifetimeServices.LeaseTime = configData.Lifetime.LeaseTime;
                  }
                  if (configData.Lifetime.IsRenewOnCallTimeSet)
                  {
                        LifetimeServices.RenewOnCallTime = configData.Lifetime.RenewOnCallTime;
                  }
                  if (configData.Lifetime.IsSponsorshipTimeoutSet)
                  {
                        LifetimeServices.SponsorshipTimeout = configData.Lifetime.SponsorshipTimeout;
                  }
                  if (configData.Lifetime.IsLeaseManagerPollTimeSet)
                  {
                        LifetimeServices.LeaseManagerPollTime = configData.Lifetime.LeaseManagerPollTime;
                  }
            }
            RemotingConfigHandler._bUrlObjRefMode = configData.UrlObjRefMode;
            RemotingConfigHandler.Info.StoreRemoteAppEntries(configData);
            RemotingConfigHandler.Info.StoreActivatedExports(configData);
            RemotingConfigHandler.Info.StoreInteropEntries(configData);
            RemotingConfigHandler.Info.StoreWellKnownExports(configData);
            if (configData.ServerActivatedEntries.Count > 0)
            {
                  ActivationServices.StartListeningForRemoteRequests();
            }
      }
      catch (Exception exception1)
      {
            throw new RemotingException(string.Format(CultureInfo.CurrentCulture, Environment.GetResourceString("Remoting_Config_ConfigurationFailure"), new object[] { exception1 }));
      }
}

在此方法中继续寻找使用那个ensureSecurity参数的地方,显然里面是要使用该参数配置通道,再跟进去

private static void ConfigureChannels(RemotingXmlConfigFileData configData, bool ensureSecurity)
{
      RemotingServices.RegisterWellKnownChannels();
      foreach (RemotingXmlConfigFileData.ChannelEntry entry1 in configData.ChannelEntries)
      {
            if (!entry1.DelayLoad)
            {
                  IChannel channel1 = RemotingConfigHandler.CreateChannelFromConfigEntry(entry1);
                  ChannelServices.RegisterChannel(channel1, ensureSecurity);
            }
            else
            {
                  RemotingConfigHandler._delayLoadChannelConfigQueue.Enqueue(new DelayLoadClientChannelEntry(entry1, ensureSecurity));
            }
      }
}

至此,你是不是也发现了事情的本质,没错,就是这句:

ChannelServices.RegisterChannel(channel1, ensureSecurity);

事实上,如果你经常使用Remoting的话,你可能会恍然大悟。因为通常我们会用ChannelServices.RegisterChannel(channel1),但此时.NET会提示我们该方法也已过期。原来,

RemotingConfiguration.Configure (String, Boolean)其实就是ChannelServices.RegisterChannel(channel1, ensureSecurity)方法的一个最上层包装罢了,它要将用户设置的ensureSecurity参数值一层层的传递到ChannelServices.RegisterChannel方法中去。在Msdn中我们可以找到对该方法的第二个参数的说明:

ensureSecurity

true ensures that security is enabled; otherwise false. Setting the value to false will not nullify the security setting done on the TCP or IPC channel. For details, see Remarks.

Remarks

……

If the ensureSecurity parameter is set to true, the remoting system determines whether the channel implements ISecurableChannel, and if so, enables encryption and digital signatures. An exception is thrown if the channel does not implement ISecurableChannel. Note: 

Setting ensureSecurity to true throws a RemotingException for  TcpServerChannel on win98 (Since secure tcp channel is not supported on wi9x)  , and for Http Server Channel on all platforms (Users need to host service in IIS if they want secure http channel).

这下我们因该明白了ensureSecurity参数其实是用来设置注册通道安全性的。但是,到这事情还没有完,请看以下分别使用HttpChannelTcpChannel的例子:

static void Main(string[] args)

        {

            Console.WriteLine("Host Started.");

            //Channel Registeration

            HttpChannel c = new HttpChannel(3200);

            ChannelServices.RegisterChannel(c,true);

            //Type Registeration-Use server-activated object(SAO)

            //Type is sepecified as (namespace.class)

            Type ServerType = typeof(SimpleServer.MessageManager);

            RemotingConfiguration.RegisterWellKnownServiceType(

                ServerType,//Type of object

                "MyObject",//Arbitrary name

                WellKnownObjectMode.SingleCall);

            Console.Read(); //Keep host running

        }

 

static void Main()

        {

            Console.WriteLine("Host Started.");

            //Channel Registration

            TcpChannel c = new TcpChannel(3200);

            //c.IsSecured = true;

            ChannelServices.RegisterChannel(c, true);

            //Type Registeration

            Type ServerType = typeof(MessageManager);

            RemotingConfiguration.RegisterWellKnownServiceType(

                ServerType,    //Type of object

                "MyObject",    //Arbitrary name

                WellKnownObjectMode.Singleton);

            Console.Read();

           

        }

 

运行第一个例子,马上会抛出一个RemotingException: 利用集成 Windows 身份验证在 IIS 中承载此服务,以保护服务器”,而运行第二个例子,却不会有任何问题,why

这恐怕还要从Remoting的安全性说起,先来看看微软大哥是如何解释的吧:

比较传输通道接收

下表对两个主要的传输通道接收进行了比较。

1. TcpChannel HttpChannel 的比较 HttpChannel

功能

TCP 通道

HTTP 通道

注释

身份验证

HTTP 通道使用 IIS ASP.NET 提供的身份验证功能,但是不支持 Passport 和表单身份验证。

授权

HTTP 通道支持 IIS ASP.NET 提供的授权功能。这些授权包括 NTFS 权限、URL 授权和文件授权。

安全通信

TCP 通道中使用 IPSec。在 HTTP 通道中使用 SSL / IPSec

原来,Remoting没有自己的安全模型,客户端(代理)和服务器(远程对象)之间的身份验证和授权是通过通道和主机进程执行的。可以组合使用以下的主机和通道:

Ø         自定义的可执行文件和TCP通道。这种组合不提供任何内置的安全功能。

Ø         ASP.NETHTTP通道。这种组合通过基本的ASP.NETIIS安全功能提供身份验证和授权。

在上面的第一个例子中,由于我们并没有提供相应的身份验证凭证,所以会抛出那样的异常,显然是IIS进行身份验证时出现错误。而在第二个例子中,由于对TCP通道没有设置安全功能,所以将ensureSecurity设置为TrueFalse都是一样的,即都可以正常的工作。这样就引出了另外一个问题:使用哪种通道更好一些呢?

这要看具体的情况,其实二者各有优缺点。首先HTTP通道使用SOAP格式传输数据,这样就类似于Web服务可以轻松穿透防火墙从而可以在Internet上访问远程对象,且具有较高的安全性。需要注意的是此时不能把服务端的方法暴露出来,如果要这样做可以使用Web Service替代。然而,这种方式也存在很大的弊端就是效率很低,甚至于比Web Service还要慢,在内部网中使用效果不尽人意。

再来看TCP通道,由于它采用原始二进制格式传输数据,因此效率很高,速度很快,在Intranet中使用效果很好。但是安全性略有不足,且穿透防火墙的能力很差。因此,在局域网应用中使用TCP通道是最好的选择。

当然,如果您使用 TCP 通道,可使用 IPSec 保护客户端和服务器之间的通信通道;使用 SSL 来保护 HTTP 通道。

需要补充说明的是,如果将远程对象驻留在控制台应用程序中,则在控制台应用程序进程内包含的应用程序域中激活远程对象。你不能使用 HTTP 通道,而必须使用 TCP 通道。然而大多数情况也不这样用,如果你需要对远程资源进行受信任的调用,请将组件驻留在 Windows 服务中,而不是驻留在控制台应用程序中。

最后还是回到对RemotingConfiguration.Configure (String, Boolean)方法的讨论上,如果你使用Http通道并且要求对用户身份进行验证,请将第二个参数设为True,否则设为False。如果你使用Tcp通道,那么第二个参数的设置通常情况下为False,如果使用True,请在具体的应用环境中进行测试。