paź
29
2014

Impersonacja w kodzie w .NET

Ostatnio dość długo szukałem sposobu aby zaimplementować model uprawnień dla aplikacji .NET umożliwiający jej dostęp do wielu lokalizacji sieciowych które mogą potrzebować różnych poświadczeń. Tradycyjne podejście (impersonacja) działa, ale można podać tylko jeden konkretny profil pod jakim aplikacja będzie działać. Ja potrzebowałem mieć możliwość dynamicznego przełączania użytkowników w zależności do tego czego aplikacja w danym momencie potrzebowała. 

W końcu odnalazłem kod który spełaniał moje wymagania: http://support.microsoft.com/kb/306158

Niestety, nie działał z takim poświadczaniem w którym autentykacja jest nie domenowa tylko na konkrentego użytkownika komputera. Okazało się, że trzeba było dokonać w nim kilku poprawek i teraz działa również w takim przypadku. Przedstawiony kod opakowałem w wygodną klasę statyczną która jest poniżej:

public static class Impersonation
    {
        public const int LOGON32_LOGON_INTERACTIVE = 2;
        public const int LOGON32_PROVIDER_DEFAULT = 0;
        public const int LOGON32_LOGON_NEW_CREDENTIALS = 9;

        [DllImport( "advapi32.dll" )]
        public static extern int LogonUserA( String lpszUserName,
            String lpszDomain,
            String lpszPassword,
            int dwLogonType,
            int dwLogonProvider,
            ref IntPtr phToken );
        [DllImport( "advapi32.dll", CharSet = CharSet.Auto, SetLastError = true )]
        public static extern int DuplicateToken( IntPtr hToken,
            int impersonationLevel,
            ref IntPtr hNewToken );

        [DllImport( "advapi32.dll", CharSet = CharSet.Auto, SetLastError = true )]
        public static extern bool RevertToSelf();

        [DllImport( "kernel32.dll", CharSet = CharSet.Auto )]
        public static extern bool CloseHandle( IntPtr handle );

        public static WindowsImpersonationContext Impersonate( string username, string domain, string password, bool isDomainAccount )
        {
            WindowsIdentity tempWindowsIdentity;
            IntPtr token = IntPtr.Zero;
            IntPtr tokenDuplicate = IntPtr.Zero;

            if( RevertToSelf() )
            {
                if( LogonUserA( username, domain, password, isDomainAccount ? LOGON32_LOGON_INTERACTIVE : LOGON32_LOGON_NEW_CREDENTIALS,
                    LOGON32_PROVIDER_DEFAULT, ref token ) != 0 )
                {
                    if( DuplicateToken( token, 2, ref tokenDuplicate ) != 0 )
                    {
                        tempWindowsIdentity = new WindowsIdentity( tokenDuplicate );
                        var impersonationContext = tempWindowsIdentity.Impersonate();
                        if( impersonationContext != null )
                        {
                            CloseHandle( token );
                            CloseHandle( tokenDuplicate );
                            return impersonationContext;
                        }
                    }
                }
            }
            if( token != IntPtr.Zero )
                CloseHandle( token );
            if( tokenDuplicate != IntPtr.Zero )
                CloseHandle( tokenDuplicate );
            throw new SecurityException( "Could not impersonate user: " + username );
        }
    }

Użycie klasy jest banalnie proste, wywołujemy metodę Impersonate (przekazując nazwę użytkonika, domenę / nazwę komputera i hasło) otrzymując w zamian objekt kontekstu. Jeśli impersonacja się nie powiedzie (np. brak uprawnień) kontekstu nie dostaniemy, zostanie rzucony wyjątek SecurityException. Po zakończeniu kodu który miał być impersonowany kończymy ową impersonację wywołując na kontekście Undo(). Przykład użycia

WindowsImpersonationContext impersonationContext = null;

            try
            {
                impersonationContext = Impersonation.Impersonate( entry.UserName, entry.Domain, entry.Password );
                // nasz kod do wykonania

            }
            finally
            {
                impersonationContext.Undo();
            }