.net c#使用登录用户长时间运行Windows服务

Long Running Windows Service using LogOn User
2020-11-24
  •  译文(汉语)
  •  原文(英语)

我有长期运行的Windows服务.在其部署中,我们经常使用自定义域用户登录凭据来授予服务附加权限.当服务正在运行时,服务正在运行的用户的凭据可能会更改或过期.我试图编写一个功能来通知用户服务凭据已过期,并且必须采取手动干预来更新登录凭据.

我的问题是从过期上下文中运行的服务中检测上下文已过期的最佳方法是什么?

谢谢

速聊1:
您目前如何检查有关context他的用户是否有效..?您为什么不能在该部分中添加一些其他代码来检查域用户的密码/登录名是否有效?PrincipalContext along with AD
速聊2:
在某些情况下,我们使用自定义登录来访问网络驱动器.如果上下文已过期,则到网络位置的连接将失败.我希望有一种更通用的方法来测试服务上下文是否过期.我无权访问用户名和密码,因为这是Windows处理的.
速聊3:
我认为这取决于...取决于服务的设置(如果它查看您有权访问的域用户名\密码,但我不熟悉您目前的结构..您是否熟悉`PrincipalContext ..或可以您显示一个代码段,而没有暴露您的实际域,您的服务当前如何检查..?
速聊4:
我仍然找不到解决此问题的方法.从这个... msdn.microsoft.com/en-us/library/cc875826.aspx 密码被分配后..(小节管理服务帐户密码更改)"时,SCM不验证存储在数据库和密码密码在Active Directory中分配给用户帐户的帐户将继续匹配." 看来,一旦进程运行,AD就可以正常工作,并且无法知道上下文已过期.令人沮丧的是,如果重新启动系统,该服务将无法启动,并且无法向用户发出警告.
解决过程1

我将采用另一种方法,将服务留给受限用户使用,例如Network Service.然后从服务中模拟当前登录的用户,这很棘手,因为可能没有一个或多个用户登录.此答案提供了示例代码来复制令牌.

最后,使用该重复的令牌访问网络共享,如此.

编辑: 以下代码作为本地系统帐户运行时有效:

class AccessShareAsLoggedOnUser
{
    public static bool CopyAsLocalUser(String sourceFile, string targetFile)
    {
        uint dwSessionId, winlogonPid = 0;
        IntPtr hUserToken = IntPtr.Zero, hUserTokenDup = IntPtr.Zero, hPToken = IntPtr.Zero, hProcess = IntPtr.Zero;

        Debug.Print("CreateProcessInConsoleSession");
        // Log the client on to the local computer.
        dwSessionId = WTSGetActiveConsoleSessionId();

        // Find the winlogon process
        var procEntry = new PROCESSENTRY32();

        uint hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
        if (hSnap == INVALID_HANDLE_VALUE)
        {
            return false;
        }

        procEntry.dwSize = (uint)Marshal.SizeOf(procEntry); //sizeof(PROCESSENTRY32);

        if (Process32First(hSnap, ref procEntry) == 0)
        {
            return false;
        }

        String strCmp = "explorer.exe";
        do
        {
            if (strCmp.IndexOf(procEntry.szExeFile) == 0)
            {
                // We found a winlogon process...make sure it's running in the console session
                uint winlogonSessId = 0;
                if (ProcessIdToSessionId(procEntry.th32ProcessID, ref winlogonSessId) &&
                    winlogonSessId == dwSessionId)
                {
                    winlogonPid = procEntry.th32ProcessID;
                    break;
                }
            }
        }
        while (Process32Next(hSnap, ref procEntry) != 0);

        //Get the user token used by DuplicateTokenEx
        WTSQueryUserToken(dwSessionId, ref hUserToken);

        hProcess = OpenProcess(MAXIMUM_ALLOWED, false, winlogonPid);

        if (
            !OpenProcessToken(hProcess,
                TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY | TOKEN_DUPLICATE | TOKEN_ASSIGN_PRIMARY
                | TOKEN_ADJUST_SESSIONID | TOKEN_READ | TOKEN_WRITE, ref hPToken))
        {
            Debug.Print(String.Format("CreateProcessInConsoleSession OpenProcessToken error: {0}",
                Marshal.GetLastWin32Error()));
        }

        var sa = new SECURITY_ATTRIBUTES();
        sa.Length = Marshal.SizeOf(sa);

        if (!DuplicateTokenEx(hPToken, MAXIMUM_ALLOWED, ref sa,
                (int)SECURITY_IMPERSONATION_LEVEL.SecurityIdentification, (int)TOKEN_TYPE.TokenPrimary,
                ref hUserTokenDup))
        {
            Debug.Print(
                String.Format(
                    "CreateProcessInConsoleSession DuplicateTokenEx error: {0} Token does not have the privilege.",
                    Marshal.GetLastWin32Error()));
            CloseHandle(hProcess);
            CloseHandle(hUserToken);
            CloseHandle(hPToken);
            return false;
        }

        try
        {
            using (WindowsImpersonationContext impersonationContext =
                new WindowsIdentity(hUserTokenDup).Impersonate())
            {
            // Put your network Code here.
                File.WriteAllText(targetFile // Somewhere with right permissions like @"C:\Users\xxx\output.txt"
                      , File.ReadAllText(sourceFile)); // like @"\\server\share\existingfile.txt"

                impersonationContext.Undo();
            }
            return true;
        }
        finally
        {
            if (hUserTokenDup != IntPtr.Zero)
            {
                if (!CloseHandle(hUserTokenDup))
                {
                    // Uncomment if you need to know this case.
                    ////throw new Win32Exception();
                }
            }

            //Close handles task
            CloseHandle(hProcess);
            CloseHandle(hUserToken);
            CloseHandle(hUserTokenDup);
            CloseHandle(hPToken);
        }
    }

    [DllImport("kernel32.dll")]
    private static extern int Process32First(uint hSnapshot, ref PROCESSENTRY32 lppe);

    [DllImport("kernel32.dll")]
    private static extern int Process32Next(uint hSnapshot, ref PROCESSENTRY32 lppe);

    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern uint CreateToolhelp32Snapshot(uint dwFlags, uint th32ProcessID);

    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern bool CloseHandle(IntPtr hSnapshot);

    [DllImport("kernel32.dll")]
    private static extern uint WTSGetActiveConsoleSessionId();

    [DllImport("Wtsapi32.dll")]
    private static extern uint WTSQueryUserToken(uint SessionId, ref IntPtr phToken);

    [DllImport("kernel32.dll")]
    private static extern bool ProcessIdToSessionId(uint dwProcessId, ref uint pSessionId);

    [DllImport("kernel32.dll")]
    private static extern IntPtr OpenProcess(uint dwDesiredAccess, bool bInheritHandle, uint dwProcessId);

    [DllImport("advapi32", SetLastError = true)]
    [SuppressUnmanagedCodeSecurity]
    private static extern bool OpenProcessToken(IntPtr ProcessHandle, // handle to process
        int DesiredAccess, // desired access to process
        ref IntPtr TokenHandle);

    [DllImport("advapi32.dll", EntryPoint = "DuplicateTokenEx")]
    public static extern bool DuplicateTokenEx(IntPtr ExistingTokenHandle, uint dwDesiredAccess,
        ref SECURITY_ATTRIBUTES lpThreadAttributes, int TokenType,
        int ImpersonationLevel, ref IntPtr DuplicateTokenHandle);


    #region Nested type: PROCESSENTRY32

    [StructLayout(LayoutKind.Sequential)]
    private struct PROCESSENTRY32
    {
        public uint dwSize;
        public readonly uint cntUsage;
        public readonly uint th32ProcessID;
        public readonly IntPtr th32DefaultHeapID;
        public readonly uint th32ModuleID;
        public readonly uint cntThreads;
        public readonly uint th32ParentProcessID;
        public readonly int pcPriClassBase;
        public readonly uint dwFlags;

        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
        public readonly string szExeFile;
    }

    #endregion


    #region Nested type: SECURITY_ATTRIBUTES

    [StructLayout(LayoutKind.Sequential)]
    public struct SECURITY_ATTRIBUTES
    {
        public int Length;
        public IntPtr lpSecurityDescriptor;
        public bool bInheritHandle;
    }

    #endregion

    #region Nested type: SECURITY_IMPERSONATION_LEVEL

    private enum SECURITY_IMPERSONATION_LEVEL
    {
        SecurityAnonymous = 0,
        SecurityIdentification = 1,
        SecurityImpersonation = 2,
        SecurityDelegation = 3,
    }

    #endregion


    #region Nested type: TOKEN_TYPE

    private enum TOKEN_TYPE
    {
        TokenPrimary = 1,
        TokenImpersonation = 2
    }

    #endregion

    public const int READ_CONTROL = 0x00020000;

    public const int STANDARD_RIGHTS_REQUIRED = 0x000F0000;

    public const int STANDARD_RIGHTS_READ = READ_CONTROL;
    public const int STANDARD_RIGHTS_WRITE = READ_CONTROL;
    public const int STANDARD_RIGHTS_EXECUTE = READ_CONTROL;

    public const int STANDARD_RIGHTS_ALL = 0x001F0000;

    public const int SPECIFIC_RIGHTS_ALL = 0x0000FFFF;

    public const int TOKEN_ASSIGN_PRIMARY = 0x0001;
    public const int TOKEN_DUPLICATE = 0x0002;
    public const int TOKEN_IMPERSONATE = 0x0004;
    public const int TOKEN_QUERY = 0x0008;
    public const int TOKEN_QUERY_SOURCE = 0x0010;
    public const int TOKEN_ADJUST_PRIVILEGES = 0x0020;
    public const int TOKEN_ADJUST_GROUPS = 0x0040;
    public const int TOKEN_ADJUST_DEFAULT = 0x0080;
    public const int TOKEN_ADJUST_SESSIONID = 0x0100;

    public const int TOKEN_READ = STANDARD_RIGHTS_READ | TOKEN_QUERY;

    public const int TOKEN_WRITE = STANDARD_RIGHTS_WRITE |
                                   TOKEN_ADJUST_PRIVILEGES |
                                   TOKEN_ADJUST_GROUPS |
                                   TOKEN_ADJUST_DEFAULT;

    public const uint MAXIMUM_ALLOWED = 0x2000000;

    private const uint TH32CS_SNAPPROCESS = 0x00000002;

    public static int INVALID_HANDLE_VALUE = -1;

    // handle to open access token
}
速聊1:
我真的不喜欢这样,因为它需要服务访问密码才能进行提升.此密码需要存储在网络服务可以访问的位置,因此我觉得这引入了安全漏洞.如果您通过kerberos之类的工具进行海拔提升,则需要将该控件委派给网络服务,这又会引起一些安全问题
速聊2:
不,不是.第一个链接在不知道密码的情况下复制了已登录用户的令牌.第二个链接使用该令牌访问网络共享.无需密码.
速聊3:
该链接具有一种更干净的方式来获取已登录用户的令牌.它使用2个API调用,您可以轻松地将它们从c ++转换为c#.stackoverflow.com/a/19796825/97471
速聊4:
不幸的是,这种方法对我不起作用.我的Windows服务通常在服务器上运行,并且没有用户登录.这就是为什么我们使用Windows提供的Windows服务-运行方式功能.

I have a long running Windows Service. In its deployment we often use custom domain user logon credentials to give addition rights to the service. As the service is running the credentials of the user that the service is running as may change or expire. I trying to write a feature to notify users that the service credentials have expired and manual intervention must be taken update the logon credentials.

My question is what is the best way to detect from a service running under an expired context that its context has expired?

Thanks

Talk1:
how are you currently checking in regards to the context if he user is valid or not..? why can't you add some additional code to that portion that checks if the domain user's password / login are valid.. are you using PrincipalContext along with AD
Talk2:
In some cases we use the custom logon to get access to network drives. If the context has expired then connection to the network locations will fail. I was hoping there was a more generic way to test that the services context has expired. I don't have access to the username and pass since this is handled by windows.
Talk3:
I think that depends ...depending how your service is setup if it looks at the domain username\password you do have access but I am not familiar with how you're currently structured.. are you familiar with `PrincipalContext.. or can you show a code snippet without exposing your actual domain how your service currently checks..?
Talk4:
I still can not find a way forward for this issue. From this ... msdn.microsoft.com/en-us/library/cc875826.aspx .. (Section Managing Service Account Password Changes) "After passwords are assigned, the SCM does not verify the passwords stored in that database and the password assigned to a user account in Active Directory will continue to match." It seems that once a process is running AD will just work and there is no way to know the context has expired. This is frustrating because if the system is restarted the Service will fail to start and no warning can be given to the user.
Solutions1

I would take a different approach and leave the service with a restricted user, such as Network Service. Then from the service, impersonate the currently logged on user, which is tricky as none or more than one user may be logged in. This answer provides sample code to duplicate the token.

Finally use that duplicated token to access the network share, like here.

EDIT: The following code works when run as Local System account:

class AccessShareAsLoggedOnUser
{
    public static bool CopyAsLocalUser(String sourceFile, string targetFile)
    {
        uint dwSessionId, winlogonPid = 0;
        IntPtr hUserToken = IntPtr.Zero, hUserTokenDup = IntPtr.Zero, hPToken = IntPtr.Zero, hProcess = IntPtr.Zero;

        Debug.Print("CreateProcessInConsoleSession");
        // Log the client on to the local computer.
        dwSessionId = WTSGetActiveConsoleSessionId();

        // Find the winlogon process
        var procEntry = new PROCESSENTRY32();

        uint hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
        if (hSnap == INVALID_HANDLE_VALUE)
        {
            return false;
        }

        procEntry.dwSize = (uint)Marshal.SizeOf(procEntry); //sizeof(PROCESSENTRY32);

        if (Process32First(hSnap, ref procEntry) == 0)
        {
            return false;
        }

        String strCmp = "explorer.exe";
        do
        {
            if (strCmp.IndexOf(procEntry.szExeFile) == 0)
            {
                // We found a winlogon process...make sure it's running in the console session
                uint winlogonSessId = 0;
                if (ProcessIdToSessionId(procEntry.th32ProcessID, ref winlogonSessId) &&
                    winlogonSessId == dwSessionId)
                {
                    winlogonPid = procEntry.th32ProcessID;
                    break;
                }
            }
        }
        while (Process32Next(hSnap, ref procEntry) != 0);

        //Get the user token used by DuplicateTokenEx
        WTSQueryUserToken(dwSessionId, ref hUserToken);

        hProcess = OpenProcess(MAXIMUM_ALLOWED, false, winlogonPid);

        if (
            !OpenProcessToken(hProcess,
                TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY | TOKEN_DUPLICATE | TOKEN_ASSIGN_PRIMARY
                | TOKEN_ADJUST_SESSIONID | TOKEN_READ | TOKEN_WRITE, ref hPToken))
        {
            Debug.Print(String.Format("CreateProcessInConsoleSession OpenProcessToken error: {0}",
                Marshal.GetLastWin32Error()));
        }

        var sa = new SECURITY_ATTRIBUTES();
        sa.Length = Marshal.SizeOf(sa);

        if (!DuplicateTokenEx(hPToken, MAXIMUM_ALLOWED, ref sa,
                (int)SECURITY_IMPERSONATION_LEVEL.SecurityIdentification, (int)TOKEN_TYPE.TokenPrimary,
                ref hUserTokenDup))
        {
            Debug.Print(
                String.Format(
                    "CreateProcessInConsoleSession DuplicateTokenEx error: {0} Token does not have the privilege.",
                    Marshal.GetLastWin32Error()));
            CloseHandle(hProcess);
            CloseHandle(hUserToken);
            CloseHandle(hPToken);
            return false;
        }

        try
        {
            using (WindowsImpersonationContext impersonationContext =
                new WindowsIdentity(hUserTokenDup).Impersonate())
            {
            // Put your network Code here.
                File.WriteAllText(targetFile // Somewhere with right permissions like @"C:\Users\xxx\output.txt"
                      , File.ReadAllText(sourceFile)); // like @"\\server\share\existingfile.txt"

                impersonationContext.Undo();
            }
            return true;
        }
        finally
        {
            if (hUserTokenDup != IntPtr.Zero)
            {
                if (!CloseHandle(hUserTokenDup))
                {
                    // Uncomment if you need to know this case.
                    ////throw new Win32Exception();
                }
            }

            //Close handles task
            CloseHandle(hProcess);
            CloseHandle(hUserToken);
            CloseHandle(hUserTokenDup);
            CloseHandle(hPToken);
        }
    }

    [DllImport("kernel32.dll")]
    private static extern int Process32First(uint hSnapshot, ref PROCESSENTRY32 lppe);

    [DllImport("kernel32.dll")]
    private static extern int Process32Next(uint hSnapshot, ref PROCESSENTRY32 lppe);

    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern uint CreateToolhelp32Snapshot(uint dwFlags, uint th32ProcessID);

    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern bool CloseHandle(IntPtr hSnapshot);

    [DllImport("kernel32.dll")]
    private static extern uint WTSGetActiveConsoleSessionId();

    [DllImport("Wtsapi32.dll")]
    private static extern uint WTSQueryUserToken(uint SessionId, ref IntPtr phToken);

    [DllImport("kernel32.dll")]
    private static extern bool ProcessIdToSessionId(uint dwProcessId, ref uint pSessionId);

    [DllImport("kernel32.dll")]
    private static extern IntPtr OpenProcess(uint dwDesiredAccess, bool bInheritHandle, uint dwProcessId);

    [DllImport("advapi32", SetLastError = true)]
    [SuppressUnmanagedCodeSecurity]
    private static extern bool OpenProcessToken(IntPtr ProcessHandle, // handle to process
        int DesiredAccess, // desired access to process
        ref IntPtr TokenHandle);

    [DllImport("advapi32.dll", EntryPoint = "DuplicateTokenEx")]
    public static extern bool DuplicateTokenEx(IntPtr ExistingTokenHandle, uint dwDesiredAccess,
        ref SECURITY_ATTRIBUTES lpThreadAttributes, int TokenType,
        int ImpersonationLevel, ref IntPtr DuplicateTokenHandle);


    #region Nested type: PROCESSENTRY32

    [StructLayout(LayoutKind.Sequential)]
    private struct PROCESSENTRY32
    {
        public uint dwSize;
        public readonly uint cntUsage;
        public readonly uint th32ProcessID;
        public readonly IntPtr th32DefaultHeapID;
        public readonly uint th32ModuleID;
        public readonly uint cntThreads;
        public readonly uint th32ParentProcessID;
        public readonly int pcPriClassBase;
        public readonly uint dwFlags;

        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
        public readonly string szExeFile;
    }

    #endregion


    #region Nested type: SECURITY_ATTRIBUTES

    [StructLayout(LayoutKind.Sequential)]
    public struct SECURITY_ATTRIBUTES
    {
        public int Length;
        public IntPtr lpSecurityDescriptor;
        public bool bInheritHandle;
    }

    #endregion

    #region Nested type: SECURITY_IMPERSONATION_LEVEL

    private enum SECURITY_IMPERSONATION_LEVEL
    {
        SecurityAnonymous = 0,
        SecurityIdentification = 1,
        SecurityImpersonation = 2,
        SecurityDelegation = 3,
    }

    #endregion


    #region Nested type: TOKEN_TYPE

    private enum TOKEN_TYPE
    {
        TokenPrimary = 1,
        TokenImpersonation = 2
    }

    #endregion

    public const int READ_CONTROL = 0x00020000;

    public const int STANDARD_RIGHTS_REQUIRED = 0x000F0000;

    public const int STANDARD_RIGHTS_READ = READ_CONTROL;
    public const int STANDARD_RIGHTS_WRITE = READ_CONTROL;
    public const int STANDARD_RIGHTS_EXECUTE = READ_CONTROL;

    public const int STANDARD_RIGHTS_ALL = 0x001F0000;

    public const int SPECIFIC_RIGHTS_ALL = 0x0000FFFF;

    public const int TOKEN_ASSIGN_PRIMARY = 0x0001;
    public const int TOKEN_DUPLICATE = 0x0002;
    public const int TOKEN_IMPERSONATE = 0x0004;
    public const int TOKEN_QUERY = 0x0008;
    public const int TOKEN_QUERY_SOURCE = 0x0010;
    public const int TOKEN_ADJUST_PRIVILEGES = 0x0020;
    public const int TOKEN_ADJUST_GROUPS = 0x0040;
    public const int TOKEN_ADJUST_DEFAULT = 0x0080;
    public const int TOKEN_ADJUST_SESSIONID = 0x0100;

    public const int TOKEN_READ = STANDARD_RIGHTS_READ | TOKEN_QUERY;

    public const int TOKEN_WRITE = STANDARD_RIGHTS_WRITE |
                                   TOKEN_ADJUST_PRIVILEGES |
                                   TOKEN_ADJUST_GROUPS |
                                   TOKEN_ADJUST_DEFAULT;

    public const uint MAXIMUM_ALLOWED = 0x2000000;

    private const uint TH32CS_SNAPPROCESS = 0x00000002;

    public static int INVALID_HANDLE_VALUE = -1;

    // handle to open access token
}
Talk1:
I dont really like this as it requires the service to have access to the password in order to do elevation. this password needs to be stored somewhere network service has access, hence i feel this introduces a security vulnerability. If you are doing the elevation via something like kerberos you need to delegate that control to network service which again cause a bit of a security issue
Talk2:
No, it doesnt. The first link duplicates the token of logged on user without knowing the password. The second link uses that token to access network share. No password needed.
Talk3:
This link has a cleaner way to get the logged-on user's token. It uses 2 API calls which you will easily convert from c++ to c#. stackoverflow.com/a/19796825/97471
Talk4:
This approach will not work for me unfortunately. My Windows Service is often running on a Server and no users are logged in. This is why we use the Windows Service - Run As functionality that Windows provides.
转载于:https://stackoverflow.com/questions/28157369/long-running-windows-service-using-logon-user

本人是.net程序员,因为英语不行,使用工具翻译,希望对有需要的人有所帮助
如果本文质量不好,还请谅解,毕竟这些操作还是比较费时的,英语较好的可以看原文

留言回复
我们只提供高质量资源,素材,源码,坚持 下了就能用 原则,让客户花了钱觉得值
上班时间 : 周一至周五9:00-17:30 期待您的加入