Poniższy artykuł pochodzi z serii Przygotowań do egzaminu 70-536.
Framework .NET pozwala korzystać z systemowego systemu zabezpieczeń opartego na rolach (ang. RBS, Role-based security), Active Directory, lub własnych mechanizmów. Dzięki temu możemy kontrolować do jakich zasobów, czy funkcji użytkownik ma dostęp. Programowo możemy zarządzać autoryzacją (ang. authorization) i uwierzytelnianiem użytkowników (ang. authentication):
- Uwierzytelnianie – weryfikuje tożsamość użytkownika, określa to kim jest użytkownik; zwykle zachodzi na podstawie nazwy użytkownika i hasła, mogą do tego służyć metody biometryczne (siatkówka oka), czy karty magnetyczne,
- Autoryzacja – stwierdza, czy użytkownik ma uprawnienia, aby odwołać się do konkretnych zasobów, odpowiedzialna jest za określanie poziomu uprawnień.
Uwierzytelnianie zachodzi zawsze przed autoryzacją.
Zdobywanie informacji o użytkowniku
Dane użytkownika
Klasa WindowsIdentity reprezentuje konto użytkownika, daje dostęp do nazwy użytkownika, typu uwierzytelniania, czy tokenu uwierzytelniania. Aby z niej korzystać należy dodać właściwą dyrektywę:
1: using System.Security.Principal;
Przykład kodu korzystającego z klasy:
1: WindowsIdentity identity = WindowsIdentity.GetCurrent();
2:
3: Console.WriteLine("Name: " + identity.Name);
4: Console.WriteLine("Token: " + identity.Token.ToString());
5: Console.WriteLine("Authentication Type: " + identity.AuthenticationType);
6:
7: if (identity.IsAnonymous)
8: Console.WriteLine("Is an anonymous user");
9: if (identity.IsAuthenticated)
10: Console.WriteLine("Is an authenticated user");
11: if (identity.IsGuest)
12: Console.WriteLine("Is a guest");
13: if (identity.IsSystem)
14: Console.WriteLine("Is part of the system");
Metoda GetCurrent zwraca obiekt WindowsIdentity dla aktualnego użytkownika. Jeśli chcielibyśmy otrzymać obiekt dla anonimowego, nieautoryzowanego użytkownika, należałoby wywołać metodę GetAnonymous.
W celu sprawdzenia kodu przykład po skompilowaniu najlepiej raz uruchomić jako zwykły użytkownik, raz jako administrator.
Grupy użytkownika
Obiekt WindowsPrincipal i jego metoda IsInRole pozwalają określić nam do jakich grup należy użytkownik. Dla grup wbudowanych możemy skorzystać z typu wyliczeniowego WindowsBuiltInRole i jego zdefiniowanych pól:
Rozszerzmy nasz poprzedni przykład o sprawdzanie trzech przykładowych grup. Najpierw odpowiednia dyrektywa using:
1: using System.Security.Permissions;
Oraz właściwy kod:
1: if (identity.IsAnonymous)
2: Console.WriteLine("Is an anonymous user");
3: if (identity.IsAuthenticated)
4: Console.WriteLine("Is an authenticated user");
5: if (identity.IsGuest)
6: Console.WriteLine("Is a guest");
7: if (identity.IsSystem)
8: Console.WriteLine("Is part of the system");
Sprawdzić niestandardowe grupy możemy podając metodzie IsInRole string:
1: if (principal.IsInRole(@"kml-Komputer\kml")){
2: Console.WriteLine("Hello kml!");
3: }
Lub korzystając ze zmiennych systemowych:
1: if (principal.IsInRole(System.Environment.MachineName + @"\kml")){
2: Console.WriteLine("Hello kml!");
3: }
Jak można zauważyć string przybiera formę “DOMENA\Nazwa grupy”.
Wymuszanie kontroli uprawnień
Wymuszenia, aby użytkownik był uwierzytelniony, lub należał do konkretnej grupy możemy wykonać w sposób deklaratywny i imperatywny. Obie metody wymagają załadowania System.Security.Principal:
1: using System.Security.Principal;
oraz zmianę domyślnej polityki:
1: System.AppDomain.CurrentDomain.SetPrincipalPolicy(PrincipalPolicy.WindowsPrincipal);
Kontrola imperatywna
Tworząc obiekt klasy System.Security.Permissions.PrincipalPermission możemy określić, czy użytkownik musi być uwierzytelniony, sprawdzić konkretną nazwę użytkownika, czy rolę. Nic więcej! Możemy pominąć któreś z wymagań wstawiając null.
1: string r = @"BUILTIN\Administratorzy";
2: // Catch any security denied exceptions so that they can be logged
3: try
4: {
5: // Create and demand the PrincipalPermission object
6: PrincipalPermission p = new PrincipalPermission(null, r, true);
7: p.Demand();
8:
9: Console.WriteLine("Access allowed.");
10: // TODO: Main application
11: }
12: catch (System.Security.SecurityException ex)
13: {
14: Console.WriteLine("Access denied: " + ex.Message);
15: // TODO: Log error
16: }
Metoda Demand sprawdza, czy użytkownik spełnia określone wymagania. Jeśli nie – zostanie wygenerowany wyjątek.
Ta metoda kontroli może zostać wykorzystana do ograniczenia fragmentu kodu, np. części metody.
Wynik programu, który powstał do tej pory. Uruchomiony jako zwykły użytkownik:

Uruchomiony jako administrator:

Kontrola deklaratywna
Kontrola deklaratywna ogranicza dostęp do całej metody. Realizowana jest za pomocą atrybutu (PrincipalPermissionAttribute) opisującego metodę:
1: [PrincipalPermission(SecurityAction.Demand, Role = @"BUILTIN\Administratorzy")]
2: //[PrincipalPermission(SecurityAction.Demand, Name = @"CONTOSO\User1", Role = @"CONTOSO\Managers")]
3: [PrincipalPermission(SecurityAction.Demand, Authenticated = true)]
4: static void AdministratorsOnlyMethod()
5: {
6: Console.WriteLine("Admin is mighty!");
7: }
Wywołanie w kodzie:
1: try {
2: AdministratorsOnlyMethod();
3: } catch (System.Security.SecurityException ex) {
4: Console.WriteLine("Your account lacks permission to that function.");
5: }
Metody generyczne
Jeżeli chcemy stworzyć własny, prosty mechanizm uwierzytelniania, który oparty będzie na nazwie użytkownika, oraz typie uwierzytelnienia, możemy skorzystać z klas GenericIdentity i GenericPrincipal. Przykład wykorzystania:
1: static void Main(string[] args)
2: {
3: GenericIdentity myUser1 = new GenericIdentity("JHealy");
4: String[] myUser1Roles = new String[] { "IT", "Users", "Administrators" };
5: GenericPrincipal myPrincipal1 =
6: new GenericPrincipal(myUser1, myUser1Roles);
7: GenericIdentity myUser2 = new GenericIdentity("TAdams");
8: String[] myUser2Roles = new String[] { "Users" };
9: GenericPrincipal myPrincipal2 =
10: new GenericPrincipal(myUser2, myUser2Roles);
11: try
12: {
13: Thread.CurrentPrincipal = myPrincipal1;
14: TestSecurity();
15: Thread.CurrentPrincipal = myPrincipal2;
16: TestSecurity();
17: }
18: catch (Exception ex)
19: {
20: Console.WriteLine(ex.GetType().ToString() + " caused by " + Thread.CurrentPrincipal.Identity.Name);
21: }
22: }
23:
24: [PrincipalPermission(SecurityAction.Demand, Role = "IT")]
25: private static void TestSecurity()
26: {
27: Console.WriteLine(Thread.CurrentPrincipal.Identity.Name + " is in IT.");
28: }
Podsumowanie
Autoryzacja i uwierzytelnianie z wykorzystaniem mechanizmów systemu operacyjnego daje nam dużo możliwości. W artykule przedstawiłem podstawowe metody pracy z tymi mechanizmami. Bardziej dociekliwi czytelnicy powinni zapoznać się z interfejsami IIdentity oraz IPrincipal. Implementując te interfejsy jesteśmy w stanie rozszerzyć domyślną funkcjonalność systemu.
Kolejny artykuł w serii to 70-536: Using Access Control Lists