This shows you the differences between two versions of the page.
pw:laboratoare:03 [2023/02/23 13:31] ciprian.dobre [Serviciu] |
pw:laboratoare:03 [2023/02/27 14:15] (current) ciprian.dobre [Bonus - Client E-mail] |
||
---|---|---|---|
Line 1: | Line 1: | ||
- | ====== Laboratorul 03: Servicii, controllere si CRUD ====== | + | ====== Laboratorul 03: Servicii, controllere, autentificare si CRUD ====== |
===== Scopul laboratorului ===== | ===== Scopul laboratorului ===== | ||
Line 9: | Line 9: | ||
Cand ruleaza aplicatia se creaza serverul HTTP, ca orice aplicatie peste HTTP trebuie sa avem declarate **endpoint-urile/rutele** unde se expune API-ul. Astfel, primele componete invatate aici sunt controller-ele, acestea sunt clase speciale a caror metode publice sunt apelate la accesul rutelor corespunzatoare metodei. Pentru ca framework-ul sa identifice controller-ele si rutele, se decoreaza clasa si metodele cu atribute, de exemplu [ApiController] specifica fremawork-ului ca acesta clasa trebuie sa fie folosita ca controller iar [Route(“api/[controller]”)] pus pe clasa si [HttpGet(“my-route”)] pe metoda specifica ca atunci cand se aceseaza ruta “/api/<nume_clasa_controller>/my-route” cu un HTTP GET sa se apeleze acea metoda in cauza. | Cand ruleaza aplicatia se creaza serverul HTTP, ca orice aplicatie peste HTTP trebuie sa avem declarate **endpoint-urile/rutele** unde se expune API-ul. Astfel, primele componete invatate aici sunt controller-ele, acestea sunt clase speciale a caror metode publice sunt apelate la accesul rutelor corespunzatoare metodei. Pentru ca framework-ul sa identifice controller-ele si rutele, se decoreaza clasa si metodele cu atribute, de exemplu [ApiController] specifica fremawork-ului ca acesta clasa trebuie sa fie folosita ca controller iar [Route(“api/[controller]”)] pus pe clasa si [HttpGet(“my-route”)] pe metoda specifica ca atunci cand se aceseaza ruta “/api/<nume_clasa_controller>/my-route” cu un HTTP GET sa se apeleze acea metoda in cauza. | ||
- | Decorarea claselor si metodelor cu attribute, sau in Java cu adnotari, pentru ca acestea sau dobandeasca mai multe functionalitati, la runtime sau compiletime, se numeste **AOP** (**Aspect-Oriented-Programming**). | + | Decorarea claselor si metodelor cu attribute, sau in Java cu adnotari, pentru ca acestea sa dobandeasca mai multe functionalitati, la runtime sau compiletime, se numeste **AOP** (**Aspect-Oriented-Programming**). |
In cereri HTTP datele transmise catre server pot fi transferate in mai multe locatii din cerere care pot fi extrase si pasate automat ca parametri pentru metoda din controller corespunzatoare rutei. Aceste locatii se specifica folosind atribute in fata parametrilor in urmatoarele moduri: | In cereri HTTP datele transmise catre server pot fi transferate in mai multe locatii din cerere care pot fi extrase si pasate automat ca parametri pentru metoda din controller corespunzatoare rutei. Aceste locatii se specifica folosind atribute in fata parametrilor in urmatoarele moduri: | ||
Line 24: | Line 24: | ||
===== Serviciu ===== | ===== Serviciu ===== | ||
- | De obicei logica aplicatiei nu se afla in controllere, motivul fiind ca este bine sa fie segregate componentele pe functionalitati, o componenta care are multe responsabilitati o sa fie greu de intretinut si de inteles de catre alti dezvoltatori. Astfel, controller-ele trebuie doar sa trateze cererile de la client si eventual sa formateze corespunzator raspunsul trimis inapoi, logica aplicatiei poate fi segregate apoi in servicii. | + | De obicei logica aplicatiei nu se afla in controllere, motivul fiind ca este bine sa fie segregate componentele pe functionalitati, o componenta care are multe responsabilitati o sa fie greu de intretinut si de inteles de catre alti dezvoltatori. Astfel, controller-ele trebuie doar sa trateze cererile de la client si eventual sa formateze corespunzator raspunsul trimis inapoi, logica aplicatiei poate fi segregata apoi in servicii. |
Asa cum ati vazut in laboratorul trecut si aveti exemple in codul din laborator puteti adauga servicii cu diverse life-time-uri adaugand la un WebApplicationBuilder serviciile pe proprietatea Services folosind ".AddTransient", ".AddScoped" sau ".AddSingleton". De obicei o sa aveti nevoie ca componentele sa fie transiente dar exista aplicabilitati si pentru celelalte life-time-uri. | Asa cum ati vazut in laboratorul trecut si aveti exemple in codul din laborator puteti adauga servicii cu diverse life-time-uri adaugand la un WebApplicationBuilder serviciile pe proprietatea Services folosind ".AddTransient", ".AddScoped" sau ".AddSingleton". De obicei o sa aveti nevoie ca componentele sa fie transiente dar exista aplicabilitati si pentru celelalte life-time-uri. | ||
+ | |||
+ | Serviciile se declara ca implementari ale unor interfete. Nu exista o regula cum ar trebui sa arate interfata sau implementarea, este la latitudinea dezvoltatorului cum trebuie sa fie implementate specificatiile insa trebuie ca existe o coerenta in implementare iar logica din servicii ar trebui segregate pe functionalitatii. Daca este nevoie de un serviciu care sa gestioneze date despre utilizatori trebuie sa existe un serviciu pentru utilizatori si sa nu faca alte actiuni, similar un serviciu pentru notificari trebuie sa se ocupe doar notificari, alte servicii pot cel mult folosi interfata expusa de acel serviciu. | ||
+ | |||
+ | ===== Operatii CRUD ===== | ||
+ | |||
+ | Pentru simplitate vom implementa operatii **CRUD** (**C**reate **R**ead **U**pdate **D**elete) peste controllere. In general pentru fiecare entitate din baza de date, sau cel putin a unui subset care sa fie expus utilizatorului, se pot face operatii de baza pe acestea. Sunt patru mari operatii CRUD: | ||
+ | |||
+ | * Create - sunt operatii de adaugare, de obicei sunt cereri de tip **POST**. | ||
+ | * Read - sunt operatii de citire a unei sau mai multor obiecte, de obicei sunt cereri de tip **GET**. Se poate citi doar cate un obiect sau mai multe, listele de obiecte e bine sa fie extrase paginat pentru a nu incarca atat serverul cat si clientul cu date. | ||
+ | * Update - sunt operatii de modificare pe datele de pe server, de obicei sunt cereri de tip **PUT**. | ||
+ | * Delete - sunt operatii de stergere, fie de stergere completa a datelor sau doar de invalidare, de obicei sunt cereri de tip **DELETE**. | ||
+ | |||
+ | Pentru transferul efectiv de date, nu se folosesc entitatii de baza de date in mod direct ci se folosesc **DTO**-uri (**Data Transfer Objects**) mapand entitatile la acestea din motive de securitate, performanta si pentru a preintampina erori. DTO-urile sunt doar obiecte clasice care nu sunt gestionate de ORM si prin care se pot transfera informatiile din entitati in diversele locuri din aplicatie. | ||
+ | ===== Autentificare si autorizare ===== | ||
+ | |||
+ | Pentru autentificarea utilizatorilor in aplicatie si securitate vom folosi un token **JWT** (**JSON Web Token**). JWT-ul este un JSON semnat in format **Base64**. Generati din swagger-ul proiectul laboratorului JWT-ul pe ruta de login cu user **"admin@default.com"** si parola **"default"**. | ||
+ | |||
+ | Puteti intra pe [[https://jwt.io/|jwt.io]] sa il decodificati si veti vedea ca JWT-ul are 3 parti in format **Base64**: | ||
+ | * Header - aici se descrie ce tip este, aici o sa fie mereu "JWT" si algoritmul folosit. | ||
+ | * Payload - aici se pun informatiile utile din JWT numite **claim**-uri, acestea o sa fie folosite pentru identificarea si autorizarea utilizatorului. | ||
+ | * Semnatura - JWT-ul este semnat cu o cheie stiuta doar de server, din header si playload se genereaza mai intai un hash apoi se cripteaza hash-ul respectiv. Semnatura are ca scop ca JWT-ul sa nu poata fi alterat sau sa fie falsificat de cineva fara cheia de semnare. | ||
+ | |||
+ | Pentru a folosi JWT-ul trebuie pus in cereri HTTP in header-ul **"Authorization"** cu schema de **bearer token**, adica in formatul **"Bearer <jwt_token>"**. Din swagger puteti seta token-ul ca "Bearer <jwt_token>" prin butonul de "Authorize". Ca sa protejati diferitele rute din controllere puteti pune atributul [Authorize] pe ruta sau pe intregul controller. Codul din laborator e deja configurat sa functioneze cu acesta autorizare doar trebuie sa folosit acel atribut unde aveti nevoie. | ||
+ | |||
+ | Trebuie sa stiti ca JWT nu trebuie sa fie litera de lege pentru autorizare, acesta doar trebuie sa fie valid si sa identifice utilizatorul. Din payload-ul JWT-ului se pot extrage claim-urile, cele standard sunt: | ||
+ | |||
+ | * **sub/nameid** (subject/name idenfifier) - de obicei identifica detinatorul JWT-ului pentru care s-a emis, poate fi un ID ca numar sau GUID sau un username. | ||
+ | * **iat** (issued at) - e amprenta de timp de cand a fost emis JWT-ul. | ||
+ | * **exp** (expires at) - e amprenta de timp cand v-a expira JWT-ul, se poate omite dar un JWT care nu expira este inutil si o bresa de securitate. | ||
+ | * **iss** (issuer) - autoritatea care a emis JWT-ul, de obicei este numele de domeniu al furnizorului de identitate. | ||
+ | * **aud** (audience) - audienta tinta pentru care se emite JWT-ul, de obicei este numele de domeniu al aplicatiei client. | ||
+ | |||
+ | Pe langa claim-urile standard se pot adauga orice alte campuri in payload dar sub/nameid va sunt indeajuns ca sa identificati utilizatorul in baza de date cu ce drepturi are acesta. In codul laboratorului aveti codul cu comentarii ca sa va ajute pentru extragerea claim-urilor si implementarea drepturilor de acces a utilizatorilor. | ||
+ | |||
+ | <note warning>Exemplul prezentat in codul pentru laborator este doar unul didactic ca sa intelegeti cum functioneaza JWT-ul si autentificarea de baza. In productie se folosesc, mai ales pentru SPA, scheme mai complexe de autentificare pentru securitate ridicata cum ar fi **[[https://wiki.oasis-open.org/security/FrontPage|SAML2]]** si **[[https://openid.net/connect/faq/|OIDC]]**.</note> | ||
Line 66: | Line 101: | ||
O ultima particularitate care face ca C# sa semene si cu limbaje ca Kotlin este suportul pentru programare asincrona. Daca urmariti metodele din codul pentru laborator veti observa ca majoritatea returneaza un Task si au cuvantul cheie **“async”**, aceste metode sunt preluate de thread-urile aplicatiei si executate **asincron**, adica nu se stie cand anume sunt executate, sunt executate cand sunt planificate de framework pentru a optimiza executia lor, mai multe task-uri se pot executa pe acelasi thread iar intr-o functie **“async”** se poate face **“await”** pe alt task ca sa se suspende executia task-ului current ca sa fie asteptata executia task-ului la care se face await. | O ultima particularitate care face ca C# sa semene si cu limbaje ca Kotlin este suportul pentru programare asincrona. Daca urmariti metodele din codul pentru laborator veti observa ca majoritatea returneaza un Task si au cuvantul cheie **“async”**, aceste metode sunt preluate de thread-urile aplicatiei si executate **asincron**, adica nu se stie cand anume sunt executate, sunt executate cand sunt planificate de framework pentru a optimiza executia lor, mai multe task-uri se pot executa pe acelasi thread iar intr-o functie **“async”** se poate face **“await”** pe alt task ca sa se suspende executia task-ului current ca sa fie asteptata executia task-ului la care se face await. | ||
+ | ===== Bonus - Client E-mail ===== | ||
+ | |||
+ | In cadrul codului pentru laborator aveti si un serviciu de mail care poate fi configurat din **appsettings.json**. Daca vreti sa il testati ca sa-l folosit in cadrul proiectului puteti sa va faceti cont pe [[https://mailtrap.io/|MailTrap]] si sa configurati credetialele in appsettings.json. Corpul e-mail-ului poate fi formatat ca HTML ca sa aiba un aspect mai placut, incercati sa va faceti propriile sabloane de mail customizate daca vreti. | ||
+ | |||
+ | ===== Sarcini pentru laborator ===== | ||
+ | |||
+ | Pentru acest laborator urmariti urmatoarele componente: | ||
+ | * Servicii | ||
+ | * DTO-uri | ||
+ | * Controllere | ||
+ | * Metode de extensie si configurari | ||
+ | |||
+ | Incercati sa folositi toate rutele din swagger facand mai un login si folosind JWT-ul emis. | ||
+ | Modificati codul si adaugati-va propriile servicii, DTO-uri si controllere cu cateva operatii CRUD. De acum puteti implementa API-ul si logica aplicatiei pentru proiect. | ||