如何通过AppDomain用特定的安全上下文加载外部程序集

.NET的程序集实际上不是直接在进程(Process)中运行,而是在一个特殊的上下文环境(AppDomain)中。我们的程序在运行的时候,首先会由CLR动态创建一个或多个默认的AppDomain,然后我们在代码中还可以手工地创建自定义的AppDomain。

有关程序域的说明,有兴趣的朋友可以参考下面的链接

http://msdn.microsoft.com/zh-cn/library/system.appdomain(VS.80).aspx

实际上它的三大好处是

1. 安全性(不同程序域之间默认是无法通讯的)

2. 稳定性(单一程序域的异常不会影响其他的)

3. 便捷性(可以利用程序域的单独卸载,而不是整个程序卸载)

那么什么时候需要用到这个技术呢?最典型的一个场景就是我们需要设计一个插件结构的程序。我们的主程序可以动态加载一个或者多个插件,这些插件当然可能是别人写的,那么我们对于这些插件所可能会做的事情是不可预期的。为了避免这些插件通过我们的主程序的执行而造成用户受到危害——例如插件可能会去更改注册表等等重要的信息——我们就可以把这些插件单独运行,而且让他们运行在相对比较低的安全上下文中。

下面用一个例子说明,我们大致的原理是用一个单独的应用程序域运行所有的插件,在该应用程序域上设置的权限是Internet这个级别。

fangz20910_1

核心代码如下

private AppDomain CreateAppDomain()
{
    AppDomain result = AppDomain.CreateDomain("plugins");
    PolicyLevel policy = PolicyLevel.CreateAppDomainLevel();
    PermissionSet ps = policy.GetNamedPermissionSet("Internet");
//这里我们取得Internet这个权限集的默认权限集合
    ps.AddPermission(new FileIOPermission(FileIOPermissionAccess.AllAccess, Application.StartupPath));
//并且再为其增加文件读取的权限,因为它需要加载另外的程序集,所以需要有主程序根目录下面的权限
    policy.RootCodeGroup.PolicyStatement = new PolicyStatement(ps);
    result.SetAppDomainPolicy(policy);
    return result;
}

下面的代码是菜单响应事件

        void item_Click(object sender, EventArgs e)
        {
            ToolStripMenuItem item = (ToolStripMenuItem)sender;
            string tag = item.Tag.ToString();

            string fileName = tag.Split(';')[0];
            string typeName = tag.Split(';')[1];
           
            BindingFlags bindings = BindingFlags.CreateInstance | 
                BindingFlags.Instance | BindingFlags.Public;

            AssemblyLoader loader = (AssemblyLoader)
                pluginDomain.CreateInstanceFromAndUnwrap("Host.exe", 
                typeof(AssemblyLoader).FullName, true, bindings, 
                null, new object[] { fileName }, null, null, null);
            loader.RunPlugin(typeName);
        }

上面的代码中的AssemblyLoader类相当于是一个桥梁,它帮助我们实例化插件并运行里面的入口方法

using System;
using System.Reflection;
using PluginSample.Core; 

namespace Host
{
    [Serializable]
    public class AssemblyLoader:MarshalByRefObject
    {
        public AssemblyLoader() { } 

        public AssemblyLoader(string dllName)
        {
            if (_assembly == null)
                _assembly = LoadAssembly(dllName);
        } 

        private Assembly _assembly=null;
        public Assembly LoadAssembly(string fileName) {
            return Assembly.LoadFile(fileName);
        } 

        public void RunPlugin(string type) {
            IPlugin plugin = (IPlugin)_assembly.CreateInstance(type);
            plugin.Main();
        } 

    }
}
下面我们来看看运行的效果。首先说说主程序:因为主程序是在本机运行,所以默认使用到的权限集是FullTrust,如下图
image
我们这个程序会自动地加载plugins目录下面的dll,然后创建菜单。(你这里可以看见两个菜单项,分别对应两个插件)
我们接下去点击插件的菜单,这时候会调用上面贴出来的第二段代码,动态实例化插件,并将其放在单独的程序域中执行
因为我们给程序域设置的权限集是Internet,所以你会看到下面的效果
image
注意,我们这里为了测试,故意在这个插件上用一个按钮去修改注册表,我们来看看会出现什么情况
image
很好,这样我们就实现了目的:可以让插件运行,但不允许他去修改注册表。
  
原文地址:https://www.cnblogs.com/chenxizhang/p/1286789.html