zoukankan      html  css  js  c++  java
  • 从执行上下文角度重新理解.NET(Core)的多线程编程[3]:安全上下文

    在前两篇文章(《基于调用链的”参数”传递》和《同步上下文》)中,我们先后介绍了CallContext(IllogicalCallContext和LogicalCallContext)、AsyncLocal<T>和SynchronizationContext,它们都是线程执行上下文的一部分。本篇介绍的安全上下文(SecurityContext)同样是执行上下文的一部分,它携带了的身份和权限相关的信息决定了执行代码拥有的控制权限。

    目录
    一、SecurityContext
    二、让代码在指定Windows账号下执行
    三、抑制模拟账号的跨线程传播
    四、利用Impersonation机制读取文件

    一、SecurityContext

    SecurityContext类型表示的安全上下文主要携带两种类型的安全信息,一种是通过WindowsIdentity对象表示Windows认证身份,体现为SecurityContext类型的WindowsIdentity属性。如果采用Windows认证和授权,这个WindowsIdentity对象决定了当前代码具有的权限。SecurityContext类型的另一个属性返回的CompressedStack携带了调用堆栈上关于CAS(Code Access Security)相关的信息。。

    public sealed class SecurityContext : IDisposable
    {   
        ...
        internal WindowsIdentity WindowsIdentity { get; set; }
        internal CompressedStack CompressedStack { get; set; }
    }

    由于CAS在.NET Core和.NET 5中不再被支持,所以我们不打算对此展开讨论,所以本篇文章讨论的核心就是SecurityContext的WindowsIdentity属性返回的WindowsIdentity对象,这个对象与一种被称为Impersonation的安全机制。

    二、让代码在指定Windows账号下执行

    Windows进程总是在一个指定账号下执行,该账号决定了当前进程访问Windows资源(比如Windows文件系统)的权限。安全起见,我们一般会选择一个权限较低的账号(比如Network Service)。如果某些代码涉及的资源访问需要更高的权限,我们可以针对当前登录用户对应的Windows账号(如果采用Windows认证)或者是任意指定的Windows账号创建一个上下文,在此上下文中的代相当于在指定的Windows账号下执行,自然拥有了对应账号的权限。这种策略相当于模拟/假冒了(Impersonate)了指定账号执行了某种操作,所以我们将这种机制称为Impersonation。

    我们通过一个简单的例子来演示一下Impersonation机制。我们首先编写了如下这个GetWindowsIdentity方法根据指定的账号和密码创建对应的WindowsIdentity对象。如代码片段所示,方法利用指定的用户名和密码调用了Win31函数LogonUser实施了登录操作,并领用返回的token创建代码登录用户的WindowsIdentity对象。

    [DllImport("advapi32.dll")]
    public static extern int LogonUser(string lpszUserName,
        string lpszDomain,
        string lpszPassword,
        int dwLogonType,
        int dwLogonProvider,
        ref IntPtr phToken);
    
    public static WindowsIdentity GetWindowsIdentity(string username, string password)
    {
        IntPtr token = IntPtr.Zero;
        var status = LogonUser(username, Environment.MachineName, password, 2, 0, ref token);
        if (status != 0)
        {
            return new WindowsIdentity(token);
        }
        throw new InvalidProgramException("Invalid user name or password");
    }

    我们编写了如下的代码来演示不同执行上下文中当前的Windows账号是什么,当前Windows账号对应的WindowsIdentity对象通过调用WindowsIdentity类型的静态方法GetCurrent获得。如代码片段所示,我们在程序初始化时打印出当前Windows账号。然后针对账号foobar(XUfoobar)创建了对应的模拟上下文(Impersonation Context),并在此上下文中打印出当前Windows账号。我们在模拟上下文中通过创建一个线程的方式执行了一个异步操作,并在异步线程中在此输出当前Windows账号。在模拟上下文终结之后,我们在此输出当前的Windows账号看看是否恢复到最初的状态。

    class Program
    {
    
        static void Main()
        {
            Console.WriteLine("Before impersonating: {0}", WindowsIdentity.GetCurrent().Name);
            using (GetWindowsIdentity(@"foobar", "password").Impersonate())
            {
                Console.WriteLine("Within Impersonation context: {0}", WindowsIdentity.GetCurrent().Name);
                new Thread(() => Console.WriteLine("Async thread: {0}", WindowsIdentity.GetCurrent().Name)).Start();
            }
            Console.WriteLine("Undo impersonation: {0}", WindowsIdentity.GetCurrent().Name);
            Console.Read();
        }
    }

    程序运行之后,控制台上会输出如下所示的结果。可以看出在默认情况下,模拟的Windows账号不仅在当前线程中有效,还会自动传递到异步线程中。

    image

    三、抑制模拟账号的跨线程传播

    通过上面的实例我们可以看出在默认情况下安全上下文携带的模拟Windows账号支持跨线程传播,但是有的时候这个机制是不必要的,甚至会代码安全隐患,在此情况下我们可以按照如下的当时调用SecurityContext的

    class Program
    {
    
        static void Main()
        {
            Console.WriteLine("Before impersonating: {0}", WindowsIdentity.GetCurrent().Name);
                using (GetWindowsIdentity(@"foobar", "password").Impersonate())
                {
                    SecurityContext.SuppressFlowWindowsIdentity();
                    Console.WriteLine("Within Impersonation context: {0}", WindowsIdentity.GetCurrent().Name);
                    new Thread(() => Console.WriteLine("Async thread: {0}", WindowsIdentity.GetCurrent().Name)).Start();
                }
            Console.WriteLine("Undo impersonation: {0}", WindowsIdentity.GetCurrent().Name);
            Console.Read();
        }
    }

    再次执行修改后的程序会得到如下所示的输出结果,可以看出模拟的Windows账号(XUfoobar)并没有传递到异步线程中。
    image

    四、利用Impersonation机制读取文件

    访问当前账号无权访问的资源是Impersonation机制的主要应用场景,接下来我们就来演示一下基于文件访问的Impersonation应用场景。我们创建了一个文本文件d: est.txt,并对其ACL进行如下的设置:只有Xufoobar账号才具有访问权限。

    image

    我们修改了上面的代码,将验证当前Windows账号的代码替换成验证文件读取权限的代码。

    class Program
    {
    
        static void Main()
        {
            Console.WriteLine("Before impersonating: {0}", CanRead() ? "Allowed" : "Denied");
            using (GetWindowsIdentity("foobar", "password").Impersonate())
            {
                Console.WriteLine("Within Impersonation context: {0}", CanRead() ? "Allowed" : "Denied");
                new Thread(() => Console.WriteLine("Async thread: {0}", CanRead() ? "Allowed" : "Denied")).Start();
            }
            Console.WriteLine("Undo impersonation: {0}", CanRead() ? "Allowed" : "Denied");
            Console.Read();
            bool CanRead()
            {
                var userName = WindowsIdentity.GetCurrent().Name;
                try
                {
                    File.ReadAllText(@"d:	est.txt");
                    return true;
                }
                catch
                {
                    return false;
                }
            }
        }
    }

    如下所示程序执行后的输出结果,可以看出在文件只有在针对XUfoobar的模拟上下文中才能被读取。如果执行模拟WindowsIdentity的跨线程传播,异步线程也具有文件读取的权限(如图),否则在异步线程中也无法读取该文件(感兴趣的朋友可以自行测试一下)。

    image

    从执行上下文角度重新理解.NET(Core)的多线程编程[1]:基于调用链的”参数”传递
    从执行上下文角度重新理解.NET(Core)的多线程编程[2]:同步上下文
    从执行上下文角度重新理解.NET(Core)的多线程编程[3]:安全上下文

  • 相关阅读:
    检测c/c++中内存泄露
    在一个集合S中寻找最大的C使A+B=C且A,B,C均在集合当中
    《为学》
    U盘windows无法格式化的解决办法
    java.lang.AbstractMethodError: oracle.jdbc.driver...解决方法
    sqlplus连接远程Oracle
    oracle字符集导致的乱码问题
    大端与小端存储模式详解
    《劝学》原文
    《报任安书》司马迁
  • 原文地址:https://www.cnblogs.com/artech/p/multiple-threading-via-execution-context-03.html
Copyright © 2011-2022 走看看