[PYTHON] Implémenter la fonctionnalité de connexion JWT dans le framework Django REST

Articles jusqu'à la dernière fois

  1. Django REST framework + Vue.js "Faisons un site EC de style SEIYU" 6 fois (prévu)
  2. Créer un site EC de type SEIYU (1) Demander une analyse et une initialisation de projet
  3. Créons un site EC de style SEIYU (2) Renouvelez l'écran de gestion en utilisant Xadmin
  4. Créons un site EC de type SEIYU (3) Créons une API de vitesse explosive avec le framework Django REST
  5. Créer un site EC de style SEIYU (4) Utiliser TypeScript d'initialisation de projet Vue.js

Même si vous ne regardez pas le contenu jusqu'à présent, vous pouvez comprendre le contenu de cette fois, donc j'espère que vous pourrez rester en contact avec moi jusqu'à la fin. : détendu:

Qu'est-ce que JWT

Il s'agit d'une abréviation de JSON Web Token. Il est possible de conserver des informations arbitraires (revendications) dans le token. Par exemple, le serveur génère un token contenant les informations "Logged as administrator" pour le client. être capable de. Le client peut utiliser le jeton pour prouver qu'il est connecté en tant qu'administrateur. -[Wikipédia] (https://ja.wikipedia.org/wiki/JSON_Web_Token)

Mis en œuvre à la main

Vous pouvez facilement implémenter la connexion JWT en utilisant la bibliothèque django-rest-framework-jwt. Il est plus facile de le personnaliser de différentes manières si vous l'écrivez directement, alors écrivez-le directement.

Créer un nouveau projet de framework Django REST

pip install Django
pip install djangorestframework
pip install markdown
pip install django-filter
django-admin startproject jwttest
cd jwttest
python manage.py runserver

Si vous démarrez le serveur et voyez la fusée normalement, tout va bien.

Réglage initial

Créez une nouvelle application.

python manage.py startapp api

Ajoutez-le à ʻINSTALLED_APPS avec rest_framework`.

jwttest/settings.py


INSTALLED_APPS = [
    ...
    'rest_framework',
    'api'
]

Créez un modèle d'utilisateur pour la connexion.

jwtest/api/models.py


from django.db import models


class UserInfo(models.Model):
    username = models.CharField(max_length=50, unique=True, db_index=True)
    password = models.CharField(max_length=100, db_index=True)
    info = models.CharField(max_length=200)

Exécutez la migration de base de données.

python manage.py makemigrations
python manage.py migrate

Mettre en œuvre la fonction de connexion

Installez la bibliothèque pour la génération de jetons JWT.

pip install pyjwt

Ajouter une classe pour la connexion, Ajoutez le dossier ʻutls sous le répertoire ʻapi, et ajoutez un nouveau fichier ʻauth.py` à l'intérieur.

api/utils/auth.py


import time
import jwt
from jwttest.settings import SECRET_KEY
from rest_framework.authentication import BaseAuthentication
from rest_framework import exceptions

from api.models import UserInfo

class NormalAuthentication(BaseAuthentication):
    def authenticate(self, request):
        username = request._request.POST.get("username")
        password = request._request.POST.get("password")
        user_obj = UserInfo.objects.filter(username=username).first()
        if not user_obj:
            raise exceptions.AuthenticationFailed('Échec d'authentification')
        elif user_obj.password != password:
            raise exceptions.AuthenticationFailed('Je n'ai pas de mot de passe')
        token = generate_jwt(user_obj)
        return (token, None)

    def authenticate_header(self, request):
        pass

#Générez un jeton avec la bibliothèque jwt que vous venez d'installer
#Le contenu du jeton comprend les informations utilisateur et le délai d'expiration
#Il est fixe que la clé de temporisation est exp
#document: https://pyjwt.readthedocs.io/en/latest/usage.html?highlight=exp
def generate_jwt(user):
    timestamp = int(time.time()) + 60*60*24*7
    return jwt.encode(
        {"userid": user.pk, "username": user.username, "info": user.info, "exp": timestamp},
        SECRET_KEY).decode("utf-8")

Ajoutez également une vue pour la connexion. Si la connexion réussit, JWT est renvoyé. Ajoutez le NormalAuthentication créé précédemment à ʻauthentication_classes`.

api/views.py


from rest_framework.views import APIView
from rest_framework.response import Response

from .utils.auth import NormalAuthentication


class Login(APIView):

    authentication_classes = [NormalAuthentication,]

    def post(self, request, *args, **kwargs):
        return Response({"token": request.user})

Ajouter l'URL.

jwttest/urls.py


...
from api.views import Login

urlpatterns = [
    ...
    path('login/', Login.as_view()),
]

Ajoutez une information utilisateur![B2DC6C55-949F-4FB9-ADCB-ED3C0F894B1E_4_5005_c.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320164/9608aed2 -dfb2-7b90-bce8-5bf6bc953926.jpeg)

Démarrez le serveur et essayez de vous connecter. 82BFEFD1-D012-4F3A-80D8-5CC828590481.jpeg Analysons le JWT retourné avec https: // jwt.io /. BE755CEB-725D-4833-A954-0300EF1A4E0B.jpeg Le JWT contient les informations que vous avez spécifiées. Créez une vue que vous ne pouvez voir que si vous êtes connecté et utilisez ce jeton pour y accéder. Tout d'abord, ajoutez la classe d'authentification pour JWT à ʻapi / utils / auth.py`.

api/utils/auth.py


...
class JWTAuthentication(BaseAuthentication):
    keyword = 'JWT'
    model = None

    def authenticate(self, request):
        auth = get_authorization_header(request).split()

        if not auth or auth[0].lower() != self.keyword.lower().encode():
            return None

        if len(auth) == 1:
            msg = "Autorisation désactivée"
            raise exceptions.AuthenticationFailed(msg)
        elif len(auth) > 2:
            msg = "Autorisation Pas d'espace non valide"
            raise exceptions.AuthenticationFailed(msg)

        try:
            jwt_token = auth[1]
            jwt_info = jwt.decode(jwt_token, SECRET_KEY)
            userid = jwt_info.get("userid")
            try:
                user = UserInfo.objects.get(pk=userid)
                user.is_authenticated = True
                return (user, jwt_token)
            except:
                msg = "L'utilisateur n'existe pas"
                raise exceptions.AuthenticationFailed(msg)
        except jwt.ExpiredSignatureError:
            msg = "le jeton a expiré"
            raise exceptions.AuthenticationFailed(msg)

    def authenticate_header(self, request):
        pass
...

Ajout de vues à ʻapi / views.py` qui ne sont pas accessibles sans connexion.

api/views.py


...
from rest_framework.permissions import IsAuthenticated
...
class Something(APIView):
    authentication_classes = [JWTAuthentication, ]
    #Rendez-le accessible uniquement aux utilisateurs connectés.
    permission_classes = [IsAuthenticated, ]

    def get(self, request, *args, **kwargs):
        return Response({"data": "C'est le contenu"})
...

J'essaierai d'y accéder après avoir ajouté l'url.

python:jwttest:urls.py


    path('data/', Something.as_view())

Tout d'abord, accédez sans Token. Il a renvoyé «les informations d'authentification n'ont pas été fournies». FFA8B8F1-6250-40FF-9FA9-6DDEB186B3EB_4_5005_c.jpeg

Après avoir ajouté le jeton, j'ai pu y accéder. 528923C5-B32E-44B4-A33F-2ED3F4564042_4_5005_c.jpeg

C'est tout, mais j'aimerais ajouter une analyse du code source lié à la connexion du framework Django REST. Veuillez lire si vous êtes intéressé. : détendu:

Analyse du code source de connexion DRF

Lien avec la classe d'authentification à utiliser

Je vais analyser en fonction du code que j'ai écrit plus tôt, CBV (Vues basées sur les classes) Si utilisé, dispatch sera exécuté. Avec cela comme entrée, nous examinerons le code source. Pour voir les choses, ajoutez self.dispatch () à la classe de connexion que j'ai écrite plus tôt.

api/view.py


...
class Login(APIView):
    authentication_classes = [NormalAuthentication, ]
    def post(self, request, *args, **kwargs):
        #Ajoutez et suivez le code source
        #Lors de l'utilisation de PyCharm
        #commande sur mac+Cliquez sur
        #Alt en victoire+Devrait être un clic
        self.dispatch() 
        return Response({"token": request.user})
...

La destination est def dispatch (self, request, * args, ** kwargs): à la ligne 481 de rest_framework / views.py. Regardons le contenu de la fonction ʻinitialize_request` à la ligne 488.

rest_framework/views.py


    def dispatch(self, request, *args, **kwargs):
        ...
        #Regardons le contenu de cette fonction
        request = self.initialize_request(request, *args, **kwargs)
        self.request = request

La définition du contenu est à la ligne 381 de rest_framework / views.py.

rest_framework/views.py


    def initialize_request(self, request, *args, **kwargs):
        """
        Returns the initial request object.
        """
        parser_context = self.get_parser_context(request)

        return Request(
            request,
            parsers=self.get_parsers(),
            #ici
            authenticators=self.get_authenticators(),
            negotiator=self.get_content_negotiator(),
            parser_context=parser_context
        )

Et si vous regardez le contenu de self.get_authenticators () à la ligne 390, J'ai le code suivant, qui est tiré de self.authentication_classes quelle est la classe d'authentification utilisant le CBV correspondant.

rest_framework/views.py


    def get_authenticators(self):
        """
        Instantiates and returns the list of authenticators that this view can use.
        """
        return [auth() for auth in self.authentication_classes]

Si vous suivez la définition de self.authentication_classes, La ligne 109 de rest_framework / views.py a la définition suivante:

rest_framework/views.py


class APIView(View):
   ...
   authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES

Par conséquent, il existe deux endroits où vous pouvez définir directement la classe d'authentification à utiliser.

  1. À l'intérieur de la classe CBV héritée de ʻAPIView`

api/views.py


...
class Login(APIView):
    #ici
    authentication_classes = [NormalAuthentication, ]

    def post(self, request, *args, **kwargs):
        return Response({"token": request.user})
...
  1. Dans les paramètres de REST_FRAMEWORK dans settings.py
REST_FRAMEWORK = {
  'DEFAULT_AUTHENTICATION_CLASSES': ['api.utils.auth.NormalAuthentication']
}

Une fois que vous avez compris la relation entre CBV et la classe d'authentification, examinons les fonctions de la classe d'authentification. Nous suivrons également de dispatch, self.initial (request, * args, ** kwargs) ʻa la ligne 493 de rest_framework / views.py`.

rest_framework/views.py


    def dispatch(self, request, *args, **kwargs):
        ...

        try:
            self.initial(request, *args, **kwargs)
        ...

J'en suivrai la définition. Ligne 395 de rest_framework / views.py. Il y a perform_authentication, nous irons plus loin.

python:rest_framework.py/views.py


...
    def initial(self, request, *args, **kwargs):
        ...
        self.perform_authentication(request)
        ...

Au-delà de cela, il y a «request.user».

rest_framework/views.py


...
    def perform_authentication(self, request):
        """
        Perform authentication on the incoming request.

        Note that if you override this and simply 'pass', then authentication
        will instead be performed lazily, the first time either
        `request.user` or `request.auth` is accessed.
        """
        request.user
...

Sa définition est à la ligne 213 de rest_framework / request.py.

rest_framework/request.py


...
    @property
    def user(self):
        """
        Returns the user associated with the current request, as authenticated
        by the authentication classes provided to the request.
        """
        if not hasattr(self, '_user'):
            with wrap_attributeerrors():
                self._authenticate()
        return self._user
...

Jetons un œil à la définition de self._authenticate (). Ligne 366 de rest_framework / request.py. Le contenu consiste à extraire la classe de la liste des classes d'authentification CBV et à exécuter la méthode ʻauthenticate` de cette classe. Le résultat de l'exécution est un «tuple» qui contient deux éléments, le premier des tuples est «self.user» et le second est «self.auth».

rest_framework/request.py


    def _authenticate(self):
        """
        Attempt to authenticate the request using each authentication instance
        in turn.
        """
        for authenticator in self.authenticators:
            try:
                user_auth_tuple = authenticator.authenticate(self)
            except exceptions.APIException:
                self._not_authenticated()
                raise

            if user_auth_tuple is not None:
                self._authenticator = authenticator
                self.user, self.auth = user_auth_tuple
                return

        self._not_authenticated()

Dans cet esprit, jetons un coup d'œil à la classe d'authentification pour JWT définie précédemment.

api/utils/auth.py


class JWTAuthentication(BaseAuthentication):
    keyword = 'JWT'
    model = None

    def authenticate(self, request):
        auth = get_authorization_header(request).split()

        if not auth or auth[0].lower() != self.keyword.lower().encode():
            return None

        if len(auth) == 1:
            msg = "Autorisation désactivée"
            raise exceptions.AuthenticationFailed(msg)
        elif len(auth) > 2:
            msg = "Autorisation Pas d'espace non valide"
            raise exceptions.AuthenticationFailed(msg)

        try:
            jwt_token = auth[1]
            jwt_info = jwt.decode(jwt_token, SECRET_KEY)
            userid = jwt_info.get("userid")
            try:
                user = UserInfo.objects.get(pk=userid)
                user.is_authenticated = True
                return (user, jwt_token)
            except:
                msg = "L'utilisateur n'existe pas"
                raise exceptions.AuthenticationFailed(msg)
        except jwt.ExpiredSignatureError:
            msg = "le jeton est expiré"
            raise exceptions.AuthenticationFailed(msg)

    def authenticate_header(self, request):
        pass

La classe contient une méthode ʻauthenticate`. Si l'authentification réussit, un tuple contenant des informations utilisateur est renvoyé. Ceci conclut l'analyse des sources liées à la connexion.

Merci de rester avec nous jusqu'à la fin. : lift_hand_tone1:

Recommended Posts

Implémenter la fonctionnalité de connexion JWT dans le framework Django REST
Implémentation de la fonction d'authentification JWT dans Django REST Framework à l'aide de djoser
Implémenter la fonction de suivi dans Django
Connectez-vous avec Django Rest Framework
Implémenter des URL hiérarchiques avec des routeurs imbriqués drf dans le framework Django REST
Suppression logique dans Django, DRF (Django REST Framework)
Comment implémenter la fonctionnalité de type helper Rails dans Django
Astuces Django Rest Framework
Implémentation de la fonction d'authentification dans Django REST Framework à l'aide de djoser
List, méthode pour les ressources imbriquées dans le framework Django REST
Implémentez l'API à une vitesse explosive en utilisant Django REST Framework
Bloc d'achoppement du framework Django REST
Framework Django REST avec Vue.js
Implémentation de la fonction de connexion dans Django
Implémentez rapidement l'API REST en Python
[Django] Utiliser MessagePack avec le framework Django REST
Implémentation de la fonction d'authentification du modèle utilisateur personnalisé dans Django REST Framework à l'aide de djoser
Créer une API RESTful avec Django Rest Framework
Comprendre la commodité de Django Rest Framework
Un outil administratif qui peut être créé immédiatement avec le framework ng-admin + Django REST
CRUD GET avec Nuxt & Django REST Framework ②
Notes diverses sur le framework Django REST
CRUD POST avec Nuxt & Django REST Framework
CRUD GET avec Nuxt & Django REST Framework ①
Django REST Framework + Considération de conception d'architecture propre
Comment gérer les caractères déformés dans json de Django REST Framework
Implémenter un modèle utilisateur personnalisé dans Django
Comment vous permettre d'essayer les fonctionnalités du framework django rest dans un seul fichier
CRUD PUT, DELETE avec Nuxt & Django REST Framework
Afficher un message d'erreur lorsque la connexion échoue dans Django
Passer les informations de connexion à afficher dans Django
Framework Django REST Un peu utile à savoir.
Comment créer une API Rest dans Django
Modèle dans Django
Formulaire à Django
Notes d'apprentissage pour la fonction migrations dans le framework Django (2)
Créer une application Todo avec Django REST Framework + Angular
Plus de nouvelles méthodes d'authentification des utilisateurs avec Django REST Framework
Essayez de créer une application Todo avec le framework Django REST
Créer une API autour de l'authentification des utilisateurs avec Django REST Framework
[Django Rest Framework] Personnalisez la fonction de filtre à l'aide de Django-Filter
Notes d'apprentissage pour la fonction migrations dans le framework Django (1)
Création d'une API qui renvoie des résultats d'inférence négatifs-positifs à l'aide de BERT dans le framework Django REST