如何在C#程序中模拟域帐户进行登录操作 (转载)
.NET Core
.NET Core也支持用PInvoke来调用操作系统底层的Win32函数
首先要在项目中下载Nuget包:System.Security.Principal.Windows
代码加注释:
using System; using System.IO; using System.Runtime.InteropServices; using System.Security.Principal; namespace NetCorePrincipal { public class WindowsLogin : IDisposable { protected const int LOGON32_PROVIDER_DEFAULT = 0; protected const int LOGON32_LOGON_INTERACTIVE = 2; public WindowsIdentity Identity = null; protected IntPtr m_accessToken; [DllImport("advapi32.dll", SetLastError = true)] private static extern bool LogonUser(string lpszUsername, string lpszDomain, string lpszPassword, int dwLogonType, int dwLogonProvider, ref IntPtr phToken); [DllImport("kernel32.dll", CharSet = CharSet.Auto)] private extern static bool CloseHandle(IntPtr handle); // AccessToken ==> this.Identity.AccessToken //public Microsoft.Win32.SafeHandles.SafeAccessTokenHandle AT //{ // get // { // var at = new Microsoft.Win32.SafeHandles.SafeAccessTokenHandle(this.m_accessToken); // return at; // } //} public WindowsLogin(string username, string domain, string password) { Login(username, domain, password); } public void Login(string username, string domain, string password) { if (this.Identity != null) { this.Identity.Dispose(); this.Identity = null; } try { this.m_accessToken = new IntPtr(0); Logout(); this.m_accessToken = IntPtr.Zero; bool logonSuccessfull = LogonUser( username, domain, password, LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, ref this.m_accessToken); if (!logonSuccessfull) { int error = Marshal.GetLastWin32Error(); throw new System.ComponentModel.Win32Exception(error); } Identity = new WindowsIdentity(this.m_accessToken); } catch { throw; } } public void Logout() { if (this.m_accessToken != IntPtr.Zero) CloseHandle(m_accessToken); this.m_accessToken = IntPtr.Zero; if (this.Identity != null) { this.Identity.Dispose(); this.Identity = null; } } // End Sub Logout public void Dispose() { Logout(); } // End Sub Dispose } // End Class WindowsLogin class Program { static void Main(string[] args) { Console.WriteLine($"Current user is : {WindowsIdentity.GetCurrent().Name}"); using (WindowsLogin wi = new WindowsLogin("uid", "serverdomain", "pwd")) { WindowsIdentity.RunImpersonated(wi.Identity.AccessToken, () => { Console.WriteLine($"Current user is : {WindowsIdentity.GetCurrent().Name}"); //假定要操作的文件路径是10.0.250.11上的d:\txt.txt文件可以这样操作 FileInfo file = new FileInfo(@"\\10.0.250.11\d$\txt.txt"); //想做什么操作就可以做了 }); } Console.WriteLine($"Current user is : {WindowsIdentity.GetCurrent().Name}"); Console.WriteLine("Press any key to quit..."); Console.ReadKey(); } } }
.NET Framework
代码加注释:
using System; using System.IO; using System.Runtime.InteropServices; using System.Security.Principal; namespace NetFrameworkPrincipal { public class WindowsLogin : IDisposable { protected const int LOGON32_PROVIDER_DEFAULT = 0; protected const int LOGON32_LOGON_INTERACTIVE = 2; public WindowsIdentity Identity = null; protected IntPtr m_accessToken; [DllImport("advapi32.dll", SetLastError = true)] private static extern bool LogonUser(string lpszUsername, string lpszDomain, string lpszPassword, int dwLogonType, int dwLogonProvider, ref IntPtr phToken); [DllImport("kernel32.dll", CharSet = CharSet.Auto)] private extern static bool CloseHandle(IntPtr handle); // AccessToken ==> this.Identity.AccessToken //public Microsoft.Win32.SafeHandles.SafeAccessTokenHandle AT //{ // get // { // var at = new Microsoft.Win32.SafeHandles.SafeAccessTokenHandle(this.m_accessToken); // return at; // } //} public WindowsLogin(string username, string domain, string password) { Login(username, domain, password); } public void Login(string username, string domain, string password) { if (this.Identity != null) { this.Identity.Dispose(); this.Identity = null; } try { this.m_accessToken = new IntPtr(0); Logout(); this.m_accessToken = IntPtr.Zero; bool logonSuccessfull = LogonUser( username, domain, password, LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, ref this.m_accessToken); if (!logonSuccessfull) { int error = Marshal.GetLastWin32Error(); throw new System.ComponentModel.Win32Exception(error); } Identity = new WindowsIdentity(this.m_accessToken); } catch { throw; } } public void Logout() { if (this.m_accessToken != IntPtr.Zero) CloseHandle(m_accessToken); this.m_accessToken = IntPtr.Zero; if (this.Identity != null) { this.Identity.Dispose(); this.Identity = null; } } // End Sub Logout public void Dispose() { Logout(); } // End Sub Dispose } // End Class WindowsLogin class Program { static void Main(string[] args) { Console.WriteLine($"Current user is : {WindowsIdentity.GetCurrent().Name}"); using (WindowsLogin wi = new WindowsLogin("uid", "serverdomain", "pwd")) { //WindowsIdentity.RunImpersonated(wi.Identity.AccessToken, () => //{ // //假定要操作的文件路径是10.0.250.11上的d:\txt.txt文件可以这样操作 // FileInfo file = new FileInfo(@"\\10.0.250.11\d$\txt.txt"); // //想做什么操作就可以做了 //}); using (wi.Identity.Impersonate()) { Console.WriteLine($"Current user is : {WindowsIdentity.GetCurrent().Name}"); //假定要操作的文件路径是10.0.250.11上的d:\txt.txt文件可以这样操作 FileInfo file = new FileInfo(@"\\10.0.250.11\d$\txt.txt"); //想做什么操作就可以做了 } } Console.WriteLine($"Current user is : {WindowsIdentity.GetCurrent().Name}"); Console.WriteLine("Press any key to quit..."); Console.ReadKey(); } } }
模拟域帐户之后,就有了模拟用户的权限,这里千万要注意安全!
模拟的域帐户是和线程绑定的
需要注意的一点是,模拟的域账户是和C#中的线程绑定的。
例如下面.NET Core控制台项目中,我们在Program类的Main方法中,模拟域账户UserB后,再启动一个新的线程去查看当前用户,还是显示的是UserB,代码如下所示:
using System; using System.Security.Principal; using System.Threading; using System.Threading.Tasks; namespace NetCorePrincipal { class Program { static void Main(string[] args) { Console.WriteLine($"Current user is : {WindowsIdentity.GetCurrent().Name}");//输出当前用户为UserA //模拟域帐户UserB using (WindowsLogin wi = new WindowsLogin("UserB", "Domain", "1qaz!QAZ")) { WindowsIdentity.RunImpersonated(wi.Identity.AccessToken, () => { Console.WriteLine("Impersonation started."); Console.WriteLine($"Current user is : {WindowsIdentity.GetCurrent().Name}");//输出当前用户为UserB //启动另一个线程查看当前用户 Task.Run(() => { Console.WriteLine($"Current user in another thread is : {WindowsIdentity.GetCurrent().Name}");//输出当前用户为UserB }).Wait();//等待启动的线程执行完毕 Console.WriteLine("Impersonation finished."); }); } Console.WriteLine($"Current user is : {WindowsIdentity.GetCurrent().Name}");//输出当前用户为UserA Console.WriteLine("Press any key to quit..."); Console.ReadKey(); } } }
输出结果如下所示:
Current user is : Domain\UserA Impersonation started. Current user is : Domain\UserB Current user in another thread is : Domain\UserB Impersonation finished. Current user is : Domain\UserA Press any key to quit...
但是接下来我们更改Program类的Main方法代码,在模拟域账户UserB前先启动一个新的线程,然后模拟域账户UserB后,在新启动的线程中查看当前用户,会显示当前用户是UserA,代码如下所示:
using System; using System.Security.Principal; using System.Threading; using System.Threading.Tasks; namespace NetCorePrincipal { class Program { static void Main(string[] args) { Console.WriteLine($"Current user is : {WindowsIdentity.GetCurrent().Name}");//输出当前用户为UserA //启动另一个线程查看当前用户 var newTask = Task.Run(() => { Thread.Sleep(3000); Console.WriteLine($"Current user in another thread is : {WindowsIdentity.GetCurrent().Name}");//输出当前用户为UserA }); //模拟域帐户UserB using (WindowsLogin wi = new WindowsLogin("UserB", "Domain", "1qaz!QAZ")) { WindowsIdentity.RunImpersonated(wi.Identity.AccessToken, () => { Console.WriteLine("Impersonation started."); Console.WriteLine($"Current user is : {WindowsIdentity.GetCurrent().Name}");//输出当前用户为UserB newTask.Wait();//等待启动的线程执行完毕 Console.WriteLine("Impersonation finished."); }); } Console.WriteLine($"Current user is : {WindowsIdentity.GetCurrent().Name}");//输出当前用户为UserA Console.WriteLine("Press any key to quit..."); Console.ReadKey(); } } }
输出结果如下所示:
Current user is : Domain\UserA Impersonation started. Current user is : Domain\UserB Current user in another thread is : Domain\UserA Impersonation finished. Current user is : Domain\UserA Press any key to quit...
由此可见模拟的域帐户实际上是和线程绑定的,新启动的线程会继承启动它的线程的域帐户。
- 如果线程A的当前域帐户是UserA,然后线程A启动了线程B,那么启动的线程B当前域帐户也会是UserA。
- 如果线程A的当前域帐户是UserA,然后线程A使用本文所述方法模拟域帐户UserB,再启动线程B,那么启动的线程B当前域帐户会是UserB。