從執行上下文角度重新理解.NET(Core)的多執行緒編程[3]:安全上下文
- 2020 年 11 月 27 日
- 筆記
- [02] 編程技巧, SecurityContext
在前兩篇文章(《基於調用鏈的」參數」傳遞》和《同步上下文》)中,我們先後介紹了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(XU\foobar)創建了對應的模擬上下文(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帳號不僅在當前執行緒中有效,還會自動傳遞到非同步執行緒中。
三、抑制模擬帳號的跨執行緒傳播
通過上面的實例我們可以看出在默認情況下安全上下文攜帶的模擬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帳號(XU\foobar)並沒有傳遞到非同步執行緒中。
四、利用Impersonation機制讀取文件
訪問當前帳號無權訪問的資源是Impersonation機制的主要應用場景,接下來我們就來演示一下基於文件訪問的Impersonation應用場景。我們創建了一個文本文件d:\test.txt,並對其ACL進行如下的設置:只有Xu\foobar帳號才具有訪問許可權。
我們修改了上面的程式碼,將驗證當前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:\test.txt"); return true; } catch { return false; } } } }
如下所示程式執行後的輸出結果,可以看出在文件只有在針對XU\foobar的模擬上下文中才能被讀取。如果執行模擬WindowsIdentity的跨執行緒傳播,非同步執行緒也具有文件讀取的許可權(如圖),否則在非同步執行緒中也無法讀取該文件(感興趣的朋友可以自行測試一下)。
從執行上下文角度重新理解.NET(Core)的多執行緒編程[1]:基於調用鏈的」參數」傳遞
從執行上下文角度重新理解.NET(Core)的多執行緒編程[2]:同步上下文
從執行上下文角度重新理解.NET(Core)的多執行緒編程[3]:安全上下文