【C#】Windowsのログインユーザを偽装する

SQL Serverからデータをとってきたいんだけど、ある特定のユーザでWindows認証によるログインを済ませないと目的のデータが引っ張ってこれない。
SQL認証であれば接続文字列にちょちょっと加えればそれで済むんだけどWindows認証だとそうもいかない。

まぁアプリを別ユーザで起動するとかすればそれで済むっちゃ済むんだけど、折角だし、C#のコードだけでログインユーザを偽装する方法を探してみました。いや、Windows APIを使わないといけないんですが。
WindowsImpersonationContextに詳しい説明が載ってます。

コード

とりあえず適当にMSDNからコピペしたコードを改変してみませう。

using System;
using System.Runtime.InteropServices;
using System.Security.Principal;
using System.Security.Permissions;
using Microsoft.Win32.SafeHandles;
using System.Runtime.ConstrainedExecution;
using System.Security;

namespace ConsoleApplication1
{
    public class ImpersobationUtil
    {
        [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
        public static extern bool LogonUser(String lpszUsername, String lpszDomain, String lpszPassword,
            int dwLogonType, int dwLogonProvider, out SafeTokenHandle phToken);

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

        public static WindowsImpersonationContext GetImpersonationContext(string userName, string domainName, string password)
        {
            const int LOGON32_PROVIDER_DEFAULT = 0;
            const int LOGON32_LOGON_INTERACTIVE = 2;
            
            try
            {
                SafeTokenHandle safeTokenHandle;
                var returnValue = LogonUser(userName, domainName, password, LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, out safeTokenHandle);

                if (false == returnValue) throw new ArgumentException(string.Format("ログインユーザの偽装に失敗しました。 ErrorCode:{0}", Marshal.GetLastWin32Error()));

                using (safeTokenHandle)
                {
                    var newId = new WindowsIdentity(safeTokenHandle.DangerousGetHandle());
                    return newId.Impersonate();
                }
            }
            catch (Exception)
            {
                throw;
            }

        }

    }

    public sealed class SafeTokenHandle : SafeHandleZeroOrMinusOneIsInvalid
    {
        private SafeTokenHandle() : base(true) {}

        [DllImport("kernel32.dll")]
        [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
        [SuppressUnmanagedCodeSecurity]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool CloseHandle(IntPtr handle);

        protected override bool ReleaseHandle()
        {
            return CloseHandle(handle);
        }
    }

}

まとめ

後はユーザを偽装したいところで

using(ImpersobationUtil.GetImpersonationContext(userName, domainName, password))

としてやればOK。