Kada implementiramo API koristeći ASP.NET Core, često postoji potreba za autorizacijom korisnika tog API-a. Vaš sustav može biti organiziran u više zasebnih područja koja omogućavaju pristup različitim resursima i akcijama. Vrlo je vjerojatno da svim korisnicima sustava neće biti dozvoljen pristup svim tim područjima. Unutar jednog područja, dizajn sustava će možda zahtijevati ograničen pristup samim entitetima. Postoje brojni načini kako implementirati takvu autorizaciju, a jedan od njih je deklariranje vlastitih autorizacijskih atributa na metodama/akcijama kontrolera ili na kontrolerima u cjelini.

Model dozvola

Ovisno o dizajnu vaše aplikacije, može biti potrebno razviti model dozvola koji će opisivati što je korisniku dozvoljeno u određenom području sustava. Recimo, na primjer, da API ima dva područja: administratorsko područje i područje kupaca. Administratoru API-a će biti omogućeno:

  • upravljati skladištima
  • upravljati predmetima u skladištu
  • upravljati listom kupaca koji mogu kupovati pojedinom skladištu.

Kupcu će na API-u biti dozvoljeno:

  • vidjeti listu skladišta kojima je dodijeljen
  • vidjeti listu predmeta u skladištu
  • kupiti predmet iz skladišta.

Jednom kada smo organizirali API na ovaj način, možemo stvoriti jednostavne objekte koji predstavljaju sve moguće dozvole. Pohranjivanje podataka u bazi može biti implementirano na razne načine, no ovdje možemo samo pretpostaviti da imamo metodu dohvata objekta UserPermissionsza neki zadani Id korisnika.

1ASP-NET-The-permission-model

Pri svakom pozivu API akcije, koja mora biti zaštićena našom custom autorizacijom, moramo dohvatiti odgovarajući UserPermissions object. Jedna mogućnost je dohvat tog objekta iz baze podataka pri svakom API pozivu, ali to bi moglo prouzročiti probleme u performansama. Druga opcija je stvoriti taj objekt prilikom logina i serijalizirati ga u JWT token. Na taj način, pri svakom API pozivu, klijent će poslati UserPermissions objekt kao dio svog tokena i on će biti deserializiran u back-end autorizacijskom kodu. Upravljanje životnim vijekom tokena, a time i životnim vijekom objekta UserPermissions, je vrlo bitno u ovom slučaju.

Custom autorizacijski atributi

Glavni cilj ovog teksta je pokazati kako izgraditi custom atribute, koje je jednostavno koristiti i koji će nam omogućiti da definiramo zahtijevane dozvole nad API pozivima. Pa, kreirajmo novu klasu: ApiAuthorizationFilter. Ona će biti naslijeđena klasa od ActionFilterAttribute, klase koja se nalazi u Microsoft.AspNetCore.Mvc.Filters namespace-u i koja se koristi za izvršavanje kôda prije i poslije poziva akcija kontrolera. Za našu custom autorizaciju napraviti ćemo override metode OnActionExecuting. Konstruktor će imati parametar koji predstavlja listu AdministatorPermissions enumeracije i predstavlja sve dozvole koje omogućavaju korisniku da izvrši poziv metode na koju je apliciran naš ApiAutorizationFilter.

GetUserPermissions čita token i deserijalizira ga u UserPermissions objekt.

2ASP-NET-Custom-authorization-attributes

3ASP-NET-Custom-authorization-attributes

Apliciranje custom autorizacijskog atributa

Custom atribut je sada spreman za apliciranje na akciju (metodu) kontrolera. On se jednostavno deklarira iznad metode, sa dozvolama koje omogućavaju izvršavanje baš tog dijela koda. I to je sve.

Prije poziva akcije kontrolera, poziva se metoda OnActionExecuting. Parametar kontraktora se može koristiti kako bi pretražili objekt UserPermissions dobiven iz korisničkog tokena. U slučaju da zahtijevana dozvola nije pronađena, akciji je zabranjeno izvršavanje.

Ako cijeli kontroler zahtijeva isti skup dozvola, ApiAuthorizationFilter atribut se može deklarirati iznad same definicije kontrolera. Tada će biti apliciran na sve akcije (metode) i kôd će biti maksimalno uredan.

5ASP-NET-Applying-the-custom-attribute

Na taj način smo pokrili općeniti princip kako koristiti atribute na metodama kako bi definirali custom autorizaciju za akcije kontrolera. Ali mogla bi postojati potreba kreirati dozvole koje su pridijeljene prema zasebnim entitetima koji se nalaze iza neke metode, kao u slučaju kupca koji pokušava dohvatiti listu produkata u skladištu. Skladište se identificira svojstvom WarehouseId i ta brojka mora biti parametar u metodi za dohvaćanje produkata.

Dodati ćemo novi konstruktor za definiranje dozvola WarehousePermissions u ApiAuthorizationFilter klasi:

6ASP-NET-Applying-the-custom-attribute

I možemo deklarirati ApiAuthorizationFilter atribut iznad metode za dohvaćanje produkata (Products):

7ASP-NET-Applying-the-custom-attribute

Ali, kako su WarehousePermissions dozvole vezane uz specifična skladišta (Warehouses), potreban nam je način kako u kodu prepoznati koji parametar GetProducts metode predstavlja WarehouseId koji želimo naći. Atributi nemaju pristup parametrima metode kroz vlastiti konstruktor i stvaraju se u trenu kompiliranja pa stoga njihovi konstruktori mogu prihvaćati samo predefinirane objekte i konstante. Istovremeno, ApiAuthorizationFilter ima pristup objektu ActionExecutingContext i moguće je ispitati parametre pozvane metode kontrolera unutar OnActionExecuting.

Jedan mogući način za pronalazak pojedinog parametra pozvane metode je deklariranjem string-a u konstruktoru ApiAuthorizationFiltera i traženje tog parametra po imenu.

8ASP-NET-Applying-the-custom-attribute9ASP-NET-Applying-the-custom-attribute

Jednom kada smo to napravili, možemo tražiti taj parametar po imenu i koristiti ga kao WarehouseId:

10ASP-NET-Applying-the-custom-attribute

11ASP-NET-Applying-the-custom-attribute

Možemo pozvati tu metodu unutar OnActionExecuting i iskoristiti pronađeni WarehouseId za provjeru sadrži li objekt UserPermissions listu dozvola za to skladište (CheckPermissionsForValue). Ako dohvaćena lista sadrži neku od dozvola koje smo deklarirali prilikom apliciranja atributa na akciji kontrolera, akciji je dozvoljeno da se izvrši i vrati rezultat.

12ASP-NET-Applying-the-custom-attribute

Očito loša strana ovog pristupa je korištenje “magičnih stringova” prilikom apliciranja atributa, što je odlična prilika da nam se potkrade greška u tipkanju koja neće biti primijećena prilikom kompiliranja. Zato bi bilo bolje imati neki više strongly-typed način za pronalazak WarehouseIdmeđu parametrima metode.

Atributi parametara

Kako bi označili parametar akcije kontrolera kao WarehouseId, kreirati ćemo custom atribut parametara WarehouseIdAttribute.

13ASP-NET-Parameter-attributes

On ne zahtijeva nikakvu unutarnju logiku, nego će biti korišten samo kako bi pokazao na željeni parametar u potpisu akcije (metode) kontrolera.

14ASP-NET-Parameter-attributes

Jednom kada smo označili točan parametar u potpisu metode kontrolera, možemo ga pronaći u ApiAuthorizationFilteru. I time smo se riješili “magičnih stringova”.

13ASP-NET-Parameter-attributes

Integracija sa Swagger UI

Korisna stvar pri razvoju API-a u ASP.NET Core je korištenje Swagger UI kako bismo jednostavno testirali naše metode kontrolera, bez konkretne implementacije klijentske strane. Bez obzira čuva li se objekt UserPermissionsu tokenu ili se dohvaća na backendu, korištenje i testiranje API-a je puno jednostavnije ako znamo koje dozvole su potrebne za poziv pojedine metode. U svrhu olakšavanja života frontend developeru, možemo napraviti jednostavne prilagodbe kako bismo prikazali dozvole koje smo deklarirali svojim custom autorizacijskim atributima.

U klasi ApiAuthorizationFilter dodajemo svojstvo PermissionsString koje predstavlja user friendly zapis koje smo aplicirali na neku metodu kontrolera.

Kreirati ćemo i klasu koja implementira IoperationFilter sučelje, pod imenom PermissionsFilter. Ova klasa će biti iskorištena od Swagger sustava kako bi se na UI ispisale potrebne dozvole.

17ASP-NET-Swagger-integration

Metoda Apply pokušava pronaći filter iz ApiDescription koji ima naš custom tip (ApiAuthorizationFilter). Ako je pronađen, user friendly zapis se dodaje u opis za akciju. PermissionsFilter se dodaje u opcije Swaggera pri konfiguraciji servisa.

18ASP-NET-Swagger-integration

Ova jednostavna prilagodba omogućava da Swagger UI prikazuje dozvole za svaku API metodu.

19ASP-NET-Swagger-integration

I što dalje?

Korištenje custom atributa je vrlo praktičan i uredan način za implementaciju autorizacijskog sustava u vašem ASP.NET Core API-u. Takav pristup smanjuje količinu redundantnog koda i omogućava developerima da stvore dozvole sa granulacijom kakva im je potrebna. Jednostavnim prilagodbama pri konfiguraciji Swagger UI-a moguće je i frontend developerima omogućiti prikaz dozvola koje API metoda zahtijeva.

What's your reaction?