Tento článek má za úkol uvést, shrnout nebo pro někoho možná jen zopakovat všechna důležitá fakta ohledně impersonace a její používání v ASP.NET a WCF aplikacích.
Začnu tedy logicky tím, co to ta impersonace (česky také ještě “zosobnění” nebo “ztotožnění”, ale zůstanu raději u původního označení) je. Impersonace (Impersonation) je proces běhu kódu pod jinou (ale také autentizovanou) identitou tj. pod jinými právy. V ASP.NET i ve WCF je touto identitou identita klienta, který vstupuje na stránku webové aplikace nebo provádí volání WCF služby.
Než se ale budu věnovat těmto konkrétním technologiím, ukážeme si chování a způsob použití impersonace obecně v libovolné .NET aplikaci.
Základní volání pro provedení impersonace je volání metody Impersonate přímo na objektu typu WindowsIdentity, který představuje nějakou dříve získanou autentizovanou identitu, na kterou se chceme impersonovat. Volání vrátí objekt WindowsImpersonationContext, pomoci kterého (voláním metody Dispose, Undo nebo při použití using bloku) můžeme impersonovaný kontext ukončit a vrátit se ke kontextu původnímu.
Druhé užitečné volání je volání statické metody Impersonate s předáním parametru IntPtr.Zero. To umožňuje dočasně provést vrácení k původnímu kontextu (vzdát se impersonace), bez nutnosti zapamatovat si původní identitu. Obě volání si ukážeme na příkladu konzolové aplikace:
[SuppressUnmanagedCodeSecurityAttribute()]
private static class SafeNativeMethods
{
[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
internal static extern int LogonUser(string lpszUsername, string lpszDomain, string lpszPassword,
Int32 dwLogonType, Int32 dwLogonProvider, ref IntPtr phToken);
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
internal static extern int CloseHandle(IntPtr handle);
}
private static WindowsIdentity LogonUser(string userName, string password, string domainName)
{
const int LOGON32_PROVIDER_DEFAULT = 0;
const int LOGON32_PROVIDER_WINNT40 = 2; //NTLM
const int LOGON32_PROVIDER_WINNT50 = 3; //Negotiate (NTLM, Kerberos or other SSP (Security Support Provider))
const int LOGON32_LOGON_INTERACTIVE = 2; //This parameter causes LogonUser to create a primary token.
const int LOGON32_LOGON_NETWORK = 3;
const int LOGON32_LOGON_BATCH = 4;
const int LOGON32_LOGON_SERVICE = 5;
const int LOGON32_LOGON_UNLOCK = 7;
IntPtr tokenHandle = IntPtr.Zero;
int returnValue = SafeNativeMethods.LogonUser(userName, domainName, password,
LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, ref tokenHandle);
if (returnValue == 0)
{
int ret = Marshal.GetLastWin32Error();
throw new System.ComponentModel.Win32Exception(ret);
}
var identity = new WindowsIdentity(tokenHandle);
if (tokenHandle != IntPtr.Zero)
{
SafeNativeMethods.CloseHandle(tokenHandle);
}
return identity;
}
static void Main()
{
Console.WriteLine("Current identity: {0}", WindowsIdentity.GetCurrent().Name);
var identity = LogonUser("Administrator", "Password", null);
using (identity.Impersonate())
{
Console.WriteLine("\tAfter impersonate: {0}", WindowsIdentity.GetCurrent().Name);
using (System.Security.Principal.WindowsIdentity.Impersonate(IntPtr.Zero))
{
Console.WriteLine("\t\tAfter revert: {0}", WindowsIdentity.GetCurrent().Name);
}
Console.WriteLine("\tAfter impersonate (2): {0}", WindowsIdentity.GetCurrent().Name);
}
Console.WriteLine("Current identity (2): {0}", WindowsIdentity.GetCurrent().Name);
}
Výstup příkladu může být tento:
Current identity: IMP\holatom
After impersonate: IMP\Administrator
After revert: IMP\holatom
After impersonate (2): IMP\Administrator
Current identity (2): IMP\holatom
Implementace pomocné metody LogonUser zaobalující volání stejnojmenné metody z WIN32 API není tolik důležitá, zde nám stačí pouze vědět to, že pomoci ní provádíme ověření Windows uživatele na základě přihlašovacího jména a hesla. Kromě tohoto asi příklad další vysvětlení již nepotřebuje.
Dále je také dobré vědět, že opakované volání Impersonate(IntPtr.Zero) tj. toto volání, kdy nejsme v impersonovaném kontextu ničemu nevadí, jen nic neprovede.
ASP.NET
Pokud není v ASP.NET impersonace zapnutá, je každý požadavek na straně serveru zpracováván v procesu IISka (od verze IIS 6.0 je to proces w3wp) a kód běží pod identitou tohoto procesu (tzv. Process Identity, například NT AUTHORITY\NETWORK SERVICE). Tuto identitu lze nastavit pro application pool v IIS.
Pokud impersonaci zapneme v sekci system.web souboru web.config uvedením:
<identity impersonate="true" />
bude veškerý kód zpracování requestu automaticky prováděn pod identitou klienta přistupujícího k aplikaci. Pokud není klient ověřen a je povolen anonymní přístup k aplikaci, běží kód zpracování requestu pod speciálním účtem odpovídající anonymnímu uživateli (typicky NT AUTHORITY\IUSR).
Výchozí hodnota nastavení impersonate je false, tedy že impersonace je vypnutá.
Více o používaných identitách v ASP.NET včetně rozdílnosti mezi jednotlivými verzemi IIS se také dočtete v tomto článku.
Impersonaci ale můžeme v ASP.NET využívat i když nebude ve web.config zapnutá a to tak, že jí provedeme přímo v kódu a jen pro část kódu kde zrovna potřebujeme. Pro tento účel je identita klienta (tj. ta identita, pod kterou by kód běžel, pokud by byla impersonace zapnutá ve web.config) dostupná jako:
HttpContext.Current.Request.LogonUserIdentity
Impersonaci pak tedy můžeme provést velmi snadno:
using (HttpContext.Current.Request.LogonUserIdentity.Impersonate())
{
//Kód běžící pod identitou klienta
}
Samozřejmě záleží na scénáři, ale já osobně častěji používám tento způsob.
WCF
Výchozí chování ve WCF je opět takové, že kód zpracovávající volanou operaci na serveru běží pod identitou procesu, ve kterém je WCF služba hostována. V případě NT Service to tedy bude identita nastavená v Service Manageru (například NT AUTHORITY\LOCAL SYSTEM), v případě IIS to bude identita nastavená pro application pool.
Pokud chceme impersonaci využít při volání WCF služby, první co je pro to nutné udělat je explicitně jí povolit při vlastním volání služby na straně klienta. Tím bude serveru umožněno dostat se k Windows identitě klienta, který volání WCF služby provádí. Kód volání služby pak bude vypadat takto:
using (var client = new ServiceClient())
{
client.ClientCredentials.Windows.AllowedImpersonationLevel = System.Security.Principal.TokenImpersonationLevel.Impersonation;
client.Operation();
}
Zde nastavujeme AllowedImpersonationLevel na hodnotu TokenImpersonationLevel.Impersonation, která odpovídá tomu, aby mohl server přistupovat pod právy klienta k lokálním zdrojům. Hodnota TokenImpersonationLevel.Delegation umožňuje navíc i přístup ke zdrojům vzdáleným (s využitím tzv. delegace) a naopak hodnota TokenImpersonationLevel.Identification by dovolila pouze zjistit na serveru identitu klienta, ale přistup v jeho zastoupení k jakýmkoliv zdrojům již nikoliv (toto je výchozí hodnota).
Na straně serveru pak jsou celkem tří možnosti. První z nich je příslušnou metodu/metody implementace kontraktu deklarativně označit atributem OperationBehavior a nastavit (vlastností Impersonation) tak, že se má celý její běh vykonávat v impersonovaném kontextu. To provedeme následujícím způsobem:
[OperationBehavior(Impersonation=ImpersonationOption.Required)]
public void Operation()
{
//Celá metoda běží pod identitou klienta volající WCF službu
}
Druhá varianta je pouze se na identitu klienta odkázat v kódu a impersonaci provést jen na část metody kde potřebujeme. V takovém případě to bude vypadat takto:
using (ServiceSecurityContext.Current.WindowsIdentity.Impersonate())
{
//Kód běžící pod identitou klienta volající WCF službu
}
Poslední možností je nastavení, že se mají úplně všechny metody implementace kontraktu volat v impersonovaném kontextu. To se provádí nastavením vlastnosti ImpersonateCallerForAllOperations objektu ServiceAuthorizationBehavior na hodnotu true.