UD10: SERVEIS WEB -- AUTENTIFICACIÓ I SEGURETAT¶

INTRODUCCIÓ¶
En aquesta unitat veurem els diferents mètodes de què disposa DRF per protegir l'accés als endpoints, depenent de si l'usuari està autenticat o no.
Com a introducció general a l'autenticació basada en tokens (que és la que utilitzarem en serveis REST), pots consultar aquest enllaç.
En els següents apartats veurem una introducció general a les diferents possibilitats d'autenticació que ofereix DRF, verificant les més apropiades per al nostre cas, i implementarem l'autenticació i protecció de les vistes per al cas del portfolio, utilitzant el paquet Djoser.
AVALUACIÓ¶
El present document, juntament amb el seu corresponent butlletí d'activitats (publicat addicionalment), cobreix els següents criteris d'avaluació:
| RESULTATS D'APRENENTATGE | CRITERIS D'AVALUACIÓ |
|---|---|
| RA4. Desenvolupa aplicacions Web embegudes en llenguatges de marques analitzant i incorporant funcionalitats segons especificacions. | d) S'han identificat i caracteritzat els mecanismes disponibles per a l'autenticació d'usuaris. e) S'han escrit aplicacions que integren mecanismes d'autenticació d'usuaris. |
| RA7. Desenvolupa serveis web reutilitzables i accessibles mitjançant protocols web, verificant el seu funcionament. | g) S'ha consumit el servei web. |
GESTIÓ D'ESTAT¶
Reprenem la discussió del punt 3 de la UD4. En aquesta unitat desglossem l'ús de les cookies, basant-nos en el Model-Vista-Controlador. La situació que se'ns presenta en l'ús de serveis REST, és que un mateix servei web pot ser utilitzat per una aplicació web, una aplicació nativa, una aplicació d'escriptori, o fins i tot un altre sistema backend. En aquest cas, és impossible emmagatzemar cap tipus d'estat per al navegador que realitza les peticions (el que sí es podia fer en MVC mitjançant sessions). Per tant, s'ha de redefinir la mecànica amb la qual hem de treballar.
Per a tenir una idea de les diferències entre manteniment de l'estat amb MVC i Serveis REST, podem analitzar la comparació d'aquest enllaç, que vam revisar a classe.
DRF - AUTENTIFICACIÓ¶
Tota la documentació sobre autentificació i seguretat en DRF la trobaràs en aquest enllaç.
DRF ofereix diversos tipus d'esquemes d'autentificació:
- Basic: basada en usuari/contrasenya, recomanada només per a entorns de proves.
- Token: autentificació basada en token, apta per a casos en què la capa de client està totalment separada de la capa servidor (aplicacions SPA, mòbils, escriptori ...)
- Session: aquest esquema utilitza el backend d'autentificació per sessió per defecte de Django. És apte per a aplicacions desenvolupades amb el model vista-controlador, amb la utilització d'AJAX o Vue, per dotar de reactivitat a l'aplicació.
- Remota: s' delega l'autentificació en el servidor web.
- Custom: adaptació de l'autentificació base, mitjançant l'extensió de la classe BaseAuthenticacion.
Vistos aquests esquemes d'autentificació, DRF permet utilitzar diversos d'ells, mitjançant la utilització de l'array DEFAULT_AUTHENTICATION_CLASSES, dins del diccionari REST_FRAMEWORK, segons el següent exemple:

O també es pot especificar en la pròpia vista (ha d'heretar d'APIView), mitjançant la llista authentication_classes.
En cas que una request no passe les validacions de seguretat, es poden retornar dues possibles respostes:
- Error HTTP 401 Unauthorized.
- Error HTTP 403 Permission Denied.
DRF - PERMISOS¶
La documentació sobre permisos en DRF la trobaràs en aquest enllaç.
Els permisos en DRF determinen si a una determinada request se li concedeix accés als recursos sol·licitats, i s'apliquen abans de la lògica d'una determinada vista.
Aquests permisos es poden aplicar tant al nivell de vista, com a nivell d'objecte. Això permet que determinats usuaris tinguen accés a uns objectes pertanyents a un model, i no a altres objectes d'aquest mateix model. El mètode a sobreescriure per poder fer això es diu get_object().
En moltes ocasions, ens convindrà sobreescriure els mètodes get_queryset, perform_create, perform_update, perform_delete..., en lloc d'utilitzar permisos a nivell d'objecte (el qual introduiria un retard addicional en la lògica de processament).
Encara que podem determinar la política de permisos a nivell global mitjançant la llista DEFAULT_PERMISSION_CLASSES del diccionari REST_FRAMEWORK, és més recomanable implementar-ho a nivell de cadascuna de les vistes de la nostra aplicació. Un exemple de configuració global:

En aquest enllaç trobem la llista de permisos estàndard de DRF.
D'especial interès són els permisos personalitzats de DRF, mitjançant els quals podem implementar la lògica de permisos adequada segons els requeriments de la nostra aplicació. Imagina que hem creat un model de perfils d'usuaris (administrador, professor, alumnat...), a cadascun dels quals li volem concedir diferents grups de privilegis a la nostra aplicació.
Per a això, ens basarem en la classe BasePermission i sobreescriurem els mètodes (un, o ambdós) has_permission i/o has_object_permission, com es detalla en aquest enllaç.
PAQUETS DJANGO¶
En aquest enllaç trobem totes les extensions de Django que ens permeten implementar els mecanismes d'autenticació i seguretat.
Els més rellevants, destacats en la documentació de DRF són:
- django-rest-knox
- Django OAuth Toolkit
- Django Rest framework OAuth
- JSON Web Token Authentication
- Djoser
En el següent apartat analitzarem amb més profunditat les avantatges que ens ofereix utilitzar el paquet Djoser, i l'utilitzarem per implementar diversos aspectes d'autenticació i seguretat en el projecte de portfolio.
DJOSER I SIMPLE JWT¶
En aquest apartat veurem diferents aspectes de Djoser, que ens proporciona diverses opcions interessants:
- D'una banda, utilitza autenticació (per token o per social) mitjançant altres paquets.
- D'altra banda, ens proporciona de sèrie un conjunt de vistes per gestionar tota l'operativa habitual d'un usuari (alta, login, logout, canvi de contrasenya, etc.), fins i tot amb un model d'usuari modificat (que és el nostre cas).
En els següents subapartats farem la configuració d'aquest paquet i utilitzarem algunes de les seues vistes.
Instal·lació¶
Segueix la guia d'instal·lació d'aquest enllaç. Instal·la el paquet djangorestframework_simplejwt, NO social-auth-app-django, ja que utilitzarem autenticació per token JWT.
També configura tot el necessari per al backend d'autenticació de JWT, segons aquest enllaç. Deixarem el diccionari SIMPLE_JWT de la següent manera (en settings.py):

NOTA: prefixem les URLs de Djoser amb "api", de la següent manera:

Farem algunes de les proves que es detallen en la documentació de Djoser, amb curl (també es poden dur a terme amb l'API navegable de DRF).
Primer obtindrem un token a partir del nostre usuari/contrasenya, mitjançant la següent instrucció (canvia l'usuari i contrasenya segons la configuració del teu usuari):
curl -X POST http://localhost/api/auth/jwt/create/ --data 'username=admin&password=admin'

NOTA: Com estem utilitzant JWT, hem d'utilitzar l'endpoint localhost/api/auth/jwt/create, segons la llista d'endpoints de JWT.
Aquesta instrucció ens ha retornat dues coses:
- El token d'accés, que haurem d'enviar a tots els endpoints amb vistes protegides.
- El token de refresc, a utilitzar quan caduque el token d'autenticació.
Quina és la durada del token d'accés? La podem modificar? La resposta és: sí, podem modificar la durada del token d'accés, amb la directiva ACCESS_TOKEN_LIFETIME, segons la documentació del paquet Simple JWT. El seu valor per defecte és de 5 minuts, com es detalla en aquesta documentació.
Un cop obtinguts aquests dos tokens, podem verificar si el token d'accés continua sent vàlid, mitjançant la següent instrucció:
curl -X POST http://localhost/api/auth/jwt/verify/ --data 'token=[token]'

Com que el token encara no ha caducat, no s'obté cap resposta. Si esperem uns minuts i tornem a intentar-ho, veurem que s'indica que el token ja no és vàlid:

Podem provar-ho igualment amb l'API navegable de DRF:

Per a refrescar un token d'accés un cop caducat, utilitzem el token de refresc, mitjançant l'endpoint http://localhost/api/auth/jwt/refresh, com es mostra a continuació:

També ho podem fer en l'API navegable:

Ara, amb aquest token d'accés podem recuperar les dades de l'usuari al qual pertany el token, mitjançant la instrucció:
curl -LX GET http://localhost/api/auth/users/me/ -H 'Authorization: JWT [access_token]'

NOTA: El text "JWT" que precedeix al token d'accés en la instrucció anterior ve del paràmetre de configuració AUTH_HEADER_TYPES, del diccionari SIMPLE_JWT (en settings.py).
En el frontend, hauríem d'emmagatzemar aquests tokens en cookies del navegador. En cada petició HTTP en què necessitem autorització, cal realitzar els següents passos:
- Verificar que el token d'accés continua sent vàlid.
- Refrescar el token d'accés si està caducat, mitjançant el token de refresc.
- Realitzar la petició HTTP amb un token d'accés vàlid.
Paràmetres de configuració¶
D'una banda tenim la configuració de Simple JWT, que ja hem mencionat en l'apartat anterior. Revisem les principals opcions de configuració.
D'altra banda, la documentació de Djoser ens permet veure com canviar els valors predeterminats, en la seua documentació. Els revisem també durant la classe.
Autenticació amb JWT¶
Anem, en aquest punt, a modificar les vistes del nostre projecte de portfolio perquè només els usuaris autenticats puguen accedir a les operacions CRUD dels nostres models.
Les vistes que necessitem securitzar són les següents:
- CategoriaCRUDViewSet
- CategoriaCreateRetrieveUpdateViewSet
- ProjecteCRUDViewSet
- CapitalizeCategoriaView
- capitalize_categoria_view
Les quatre primeres són vistes basades en classe, amb la qual cosa només necessitem afegir l'atribut següent a la vista:
permission_classes = [IsAuthenticated]
from rest_framework.permissions import IsAuthenticated
from rest_framework.decorators import api_view, permission_classes
# Codi
@api_view(['GET'])
@permission_classes([IsAuthenticated])
def capitalize_categoria_view(request):
# Codi
Si intentem accedir a la llista de categories amb l'API navegable veurem que no hi ha problema en recuperar els valors (fixa't en el valor de "Php"):

Però si intentem accedir a qualsevol operació CRUD de les categories, obtindrem un error de permisos:

Podríem pensar en introduir les nostres credencials mitjançant l'opció "Log in" de l'API navegable, però no s'acceptaran les nostres credencials perquè l'opció de configuració DEFAULT_AUTHENTICATION_CLASSES només recull JWTAuthentication, com a mètode d'autenticació.
Per tant, a partir d'ara, haurem d'utilitzar algun tipus de client (curl, per exemple) per poder provar els serveis protegits, enviant el token d'accés cada vegada que vulguem realitzar alguna operació protegida.
Per exemple, anem a actualitzar una de les categories mitjançant curl, enviant el token d'accés, mitjançant una instrucció similar a la següent:
curl -LX PUT http://localhost/api/categoria_crud/1/ -H 'Authorization: JWT [TOKEN ACCÉS]' -d "nombre=PHP"

Si consultem de nou la llista de categories, veurem que PHP es mostra en majúscules:

Endpoints¶
Encara que aquests dos paquets ens ofereixen molts endpoints per implementar diferents funcionalitats, per a una aplicació web típica basada en serveis REST (una SPA, per exemple), utilitzaríem com a mínim els següents endpoints de Djoser i Simple JWT:
Djoser:
Simple JWT: