[PYTHON] Créer une API autour de l'authentification des utilisateurs avec Django REST Framework

J'ai créé l'API suivante en utilisant Django REST Framework, alors prenez note.

--S'identifier --Utilisateur créé

Même si je l'ai fait, la partie que j'ai faite avec une atmosphère relativement grande est grande, et il y a aussi le problème de savoir si c'est optimal, donc c'est pour référence seulement.

Créer un modèle

Vous pouvez utiliser le modèle utilisateur par défaut pour le modèle, mais si vous souhaitez créer une application réelle, vous devrez la personnaliser, j'ai donc créé le modèle suivant.

user/models.py


from django.db import models
from django.contrib.auth.models import (
    BaseUserManager, AbstractBaseUser, _user_has_perm
)
from django.core import validators
from django.utils.translation import ugettext_lazy as _
from django.utils import timezone


class AccountManager(BaseUserManager):
    def create_user(self, request_data, **kwargs):
        now = timezone.now()
        if not request_data['email']:
            raise ValueError('Users must have an email address.')

        profile = ""
        if request_data.get('profile'):
            profile = request_data['profile']

        user = self.model(
            username=request_data['username'],
            email=self.normalize_email(request_data['email']),
            is_active=True,
            last_login=now,
            date_joined=now,
            profile=profile
        )

        user.set_password(request_data['password'])
        user.save(using=self._db)
        return user

    def create_superuser(self, username, email, password, **extra_fields):
        request_data = {
            'username': username,
            'email': email,
            'password': password
        }
        user = self.create_user(request_data)
        user.is_active = True
        user.is_staff = True
        user.is_admin = True
        user.save(using=self._db)
        return user


class Account(AbstractBaseUser):
    username    = models.CharField(_('username'), max_length=30, unique=True)
    first_name  = models.CharField(_('first name'), max_length=30, blank=True)
    last_name   = models.CharField(_('last name'), max_length=30, blank=True)
    email       = models.EmailField(verbose_name='email address', max_length=255, unique=True)
    profile     = models.CharField(_('profile'), max_length=255, blank=True)
    is_active   = models.BooleanField(default=True)
    is_staff    = models.BooleanField(default=False)
    is_admin    = models.BooleanField(default=False)
    date_joined = models.DateTimeField(_('date joined'), default=timezone.now)

    objects = AccountManager()

    USERNAME_FIELD = 'email'
    REQUIRED_FIELDS = ['username']

    def user_has_perm(user, perm, obj):
        return _user_has_perm(user, perm, obj)

    def has_perm(self, perm, obj=None):
        return _user_has_perm(self, perm, obj=obj)

    def has_module_perms(self, app_label):
        return self.is_admin

    def get_short_name(self):
        return self.first_name

    @property
    def is_superuser(self):
        return self.is_admin

    class Meta:
        db_table = 'api_user'
        swappable = 'AUTH_USER_MODEL'

L'utilisateur admin peut être créé comme d'habitude avec $ python manage.py createuperuser. Les deux points suivants sont différents de la valeur par défaut.

  1. Modifiez le champ qui combine le mot de passe pour vous connecter du nom d'utilisateur à l'e-mail Ceci est appliqué dans la partie suivante.
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['username']
  1. Ajouter un champ pour saisir un profil (profil)
profile     = models.CharField(_('profile'), max_length=255, blank=True)

c'est tout.

API implémentée pour la connexion

Cette fois, nous avons implémenté l'authentification par jeton par JWT. Installez django-rest-framework-jwt avec pip.

$ pip install djangorestframework-jwt

Après l'installation, ajoutez ce qui suit à settings.py.

djangoproject/settings.py


JWT_AUTH = {
    'JWT_VERIFY_EXPIRATION': False,
    'JWT_AUTH_HEADER_PREFIX': 'JWT',
}


REST_FRAMEWORK = { 
    'DEFAULT_PERMISSION_CLASSES': (
        'rest_framework.permissions.IsAuthenticated',
    ),  
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
    ),  
    'NON_FIELD_ERRORS_KEY': 'detail',
    'TEST_REQUEST_DEFAULT_FORMAT': 'json'
}

Si vous définissez `` JWT_VERIFY_EXPIRATION ': False, `, la date d'expiration du jeton sera permanente.
Ensuite, accédez à url.py comme suit

djangoproject/urls.py


from django.conf.urls import url

from rest_framework_jwt.views import obtain_jwt_token

urlpatterns = [
    url(r'^login/', obtain_jwt_token),
]

Avec cela seul, vous pouvez obtenir un jeton en POSTANT sur / en vous connectant à l'aide d'un e-mail et d'un mot de passe.

Mettre en œuvre une API créée par l'utilisateur

Ajouter l'URL

Tout d'abord, nous avons besoin d'un point de terminaison d'API, alors créez urls.py dans l'application et

user/urls.py


from django.conf.urls import include, url
from rest_framework import routers
from .views import AuthRegister

urlpatterns = [
    url(r'^register/$', AuthRegister.as_view()),
]

Chargez-le dans urls.py côté projet.

djangoproject/urls.py


from django.conf.urls import include, url
from django.contrib import admin

from rest_framework_jwt.views import obtain_jwt_token
urlpatterns = [
    url(r'^login/', obtain_jwt_token),
    url(r'^api/', include('authentication.urls')),
]

POST dans / api / register / pour permettre la création d'utilisateurs

Créer une vue

user/views.py


from django.contrib.auth import authenticate
from django.db import transaction
from django.http import HttpResponse, Http404
from rest_framework import authentication, permissions, generics
from rest_framework_jwt.settings import api_settings
from rest_framework.exceptions import AuthenticationFailed
from rest_framework.response import Response
from rest_framework import status, viewsets, filters
from rest_framework.views import APIView
from .serializer import AccountSerializer
from .models import Account, AccountManager

#Vue créée par l'utilisateur(POST)
class AuthRegister(generics.CreateAPIView):
    permission_classes = (permissions.AllowAny,)
    queryset = Account.objects.all()
    serializer_class = AccountSerializer

    @transaction.atomic
    def post(self, request, format=None):
        serializer = AccountSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

En utilisant des génériques, vous pouvez limiter les méthodes HTTP qu'une classe peut accepter. Pour plus d'informations sur les génériques, voir ici. CreateAPIView est utilisé pour les points de terminaison de création uniquement.

De plus, bien sûr, pour la création d'un utilisateur, il est nécessaire de POST sur le point final même s'il n'est pas authentifié, donc permission_classes = (permissions.AllowAny,) est défini.

Créer un sérialiseur

from django.contrib.auth import update_session_auth_hash
from rest_framework import serializers

from .models import Account, AccountManager


class AccountSerializer(serializers.ModelSerializer):
    password = serializers.CharField(write_only=True, required=False)

    class Meta:
        model = Account
        fields = ('id', 'username', 'email', 'profile', 'password')

    def create(self, validated_data):
        return Account.objects.create_user(request_data=validated_data)

En gros, cela ressemble à appeler une méthode créée par l'utilisateur définie dans Model.

Pour le moment, si vous POST email, mot de passe, profil (optionnel) json vers / api / register / dans l'état ci-dessus, un utilisateur sera créé.

Obtenir des informations sur l'utilisateur connecté

Concernant l'acquisition des informations utilisateur, cela dépend de l'application, mais pour le moment, l'utilisateur connecté ne peut acquérir que ses propres informations. Si vous souhaitez obtenir des informations sur d'autres utilisateurs, vous devez créer une autre vue et un autre point de terminaison, je suis donc désolé.

Modification de urls.py

Modifiez urls.py côté application.

user/urls.py


from django.conf.urls import include, url
from rest_framework import routers
from .views import AuthRegister, AuthInfoGetView

urlpatterns = [
    url(r'^register/$', AuthRegister.as_view()),
    url(r'^mypage/$', AuthInfoGetView.as_view()),
]

Obtenez des informations utilisateur lorsque vous lancez un GET vers / api / mypage.

Modifier vue

Ajoutez ce qui suit à views.py plus tôt

user/views.py


#Vue de l'acquisition d'informations utilisateur(GET)
class AuthInfoGetView(generics.RetrieveAPIView):
    permission_classes = (permissions.IsAuthenticated,)
    queryset = Account.objects.all()
    serializer_class = AccountSerializer

    def get(self, request, format=None):
        return Response(data={
            'username': request.user.username,
            'email': request.user.email,
            'profile': request.user.profile,
            },
            status=status.HTTP_200_OK)

RetrieveAPIView est un point de terminaison dédié à la méthode GET. De plus, comme j'essaie d'obtenir mes propres informations, j'essaie de les obtenir uniquement lorsque je suis connecté avec permission_classes = (permissions.IsAuthenticated,).

Dans cet état, ajoutez {'Content-Type': 'application / json', 'Authorization': 'JWT [token obtenu à la connexion]'} à l'en-tête et lancez la méthode GET pour vous connecter. Vous pouvez obtenir le nom d'utilisateur / email / profil de l'utilisateur actuel.

Informations de connexion mises à jour (y compris la mise à jour du mot de passe)

On parle d'utiliser PUT ou PATCH lors de la mise à jour des informations, mais pour le moment j'ai utilisé PUT cette fois, mais maintenant que j'y pense, je pense que PATCH est bon. Cependant, depuis que je l'ai fait, j'en parlerai dans PUT.

Modification de urls.py

Créez un point de terminaison appelé / auth_update et appelez la vue AuthInfoUpdateView.

user/urls.py


from django.conf.urls import include, url
from rest_framework import routers
from .views import AuthRegister, AuthInfoGetView, AuthInfoUpdateView

urlpatterns = [
    url(r'^register/$', AuthRegister.as_view()),
    url(r'^mypage/$', AuthInfoGetView.as_view()),
    url(r'^auth_update/$', AuthInfoUpdateView.as_view()),
]

Modifier vue

Ajoutez ce qui suit à views.py. Il s'agit d'une implémentation d'AuthInfoUpdateView, une vue qui est appelée lorsqu'il y a un PUT dans / auth_update. Étant donné que l'e-mail est utilisé comme clé, l'e-mail de l'utilisateur connecté doit également être inclus dans le JSON lors de l'envoi d'un PUT.

user/views.py


#Vue de la mise à jour des informations utilisateur(PUT)
class AuthInfoUpdateView(generics.UpdateAPIView):
    permission_classes = (permissions.IsAuthenticated,)
    serializer_class = AccountSerializer
    lookup_field = 'email'
    queryset = Account.objects.all()

    def get_object(self):
        try:
            instance = self.queryset.get(email=self.request.user)
            return instance
        except Account.DoesNotExist:
            raise Http404

Comme son nom l'indique, UpdateAPIView accepte PUT / PATCH comme une vue de mise à jour uniquement.

Modification du sérialiseur

Modifiez AccountSerializer dans serializers.py comme suit.

user/serializers.py


class AccountSerializer(serializers.ModelSerializer):
    password = serializers.CharField(write_only=True, required=False)

    class Meta:
        model = Account
        fields = ('id', 'username', 'email', 'profile', 'password')

    def create(self, validated_data):
        return Account.objects.create_user(request_data=validated_data)

    def update(self, instance, validated_data):
        if 'password' in validated_data:
            instance.set_password(validated_data['password'])
        else:
            instance = super().update(instance, validated_data)
        instance.save()
        return instance

Si le mot de passe est mis à jour, il est nécessaire de hacher le mot de passe reçu, utilisez donc la méthode set_password. Comme pour obtenir des informations utilisateur, vous devez METTRE avec {'Content-Type': 'application / json', 'Authorization': 'JWT [token obtenu à la connexion]'} dans l'en-tête.

Suppression de l'utilisateur

Enfin, nous mettrons en œuvre la suppression de l'utilisateur connecté.

Modification de urls.py

Je crée un point de terminaison appelé / delete et j'appelle AuthInfoDeleteView.

user/urls.py


from django.conf.urls import include, url
from rest_framework import routers
from .views import AuthRegister, AuthInfoGetView, AuthInfoUpdateView, AuthInfoDeleteView

urlpatterns = [
    url(r'^register/$', AuthRegister.as_view()),
    url(r'^mypage/$', AuthInfoGetView.as_view()),
    url(r'^auth_update/$', AuthInfoUpdateView.as_view()),
    url(r'^delete/$', AuthInfoDeleteView.as_view()),
]

Modifier vue

Une implémentation d'AuthInfoDeleteView.

user/views.py


#Vue de suppression de l'utilisateur(DELETE)
class AuthInfoDeleteView(generics.DestroyAPIView):
    permission_classes = (permissions.IsAuthenticated,)
    serializer_class = AccountSerializer
    lookup_field = 'email'
    queryset = Account.objects.all()

    def get_object(self):
        try:
            instance = self.queryset.get(email=self.request.user)
            return instance
        except Account.DoesNotExist:
            raise Http404

En définissant generics.DestroyAPIView, la vue accepte uniquement la méthode DELETE. En envoyant DELETE avec {'Content-Type': 'application / json', 'Authorization': 'JWT [token obtenu à la connexion]'} dans l'en-tête ainsi que l'acquisition et la mise à jour des informations utilisateur Vous pouvez supprimer l'utilisateur connecté.

c'est tout.

Problèmes

En fait, le courrier électronique ne peut pas être mis à jour dans l'état actuel. Non, je peux le faire, mais comme le jeton que j'ai côté client est celui connecté avec l'ancien e-mail, le comportement sera étrange si le côté client ne dispose pas du jeton obtenu avec le nouvel e-mail. Vous pouvez émettre un nouveau jeton avec un nouvel e-mail en POSTANT à / login avec un mot de passe et un nouvel e-mail, mais c'est absurde. Vous devez avoir le mot de passe en texte brut côté client. Pour le moment, je voudrais étudier cela plus en détail.

Aussi, je pense qu'il est nécessaire de préparer une API pour réémettre le mot de passe afin d'oublier le mot de passe, mais c'était ennuyeux </ s>, j'ai donc décidé de ne pas le faire cette fois.

En outre, le problème évident est que si le jeton de connexion est obtenu d'une manière ou d'une autre, les informations utilisateur peuvent être facilement altérées ou supprimées. Cependant, je pense que le problème du vol malveillant de jetons est une question de JWT. Cette fois, la date d'expiration du jeton est infinie, mais je me demande s'il est nécessaire de définir une date d'expiration ou d'émettre un jeton d'actualisation lors de l'envoi d'une requête à l'API lors de la publication effective de l'application. Je pense.

De plus, l'API ci-dessus combinée à Angular à l'avant est disponible dans le référentiel suivant. https://github.com/xKxAxKx/auth_django_and_angular2

Recommended Posts

Créer une API autour de l'authentification des utilisateurs avec Django REST Framework
Créer une API RESTful avec Django Rest Framework
Implémentation de la fonction d'authentification dans Django REST Framework à l'aide de djoser
Implémentation de la fonction d'authentification du modèle utilisateur personnalisé dans Django REST Framework à l'aide de djoser
Framework Django REST avec Vue.js
Connectez-vous avec Django Rest Framework
Créer une application Todo avec Django REST Framework + Angular
[Django] Utiliser MessagePack avec le framework Django REST
CRUD POST avec Nuxt & Django REST Framework
CRUD GET avec Nuxt & Django REST Framework ①
Créer une API REST pour faire fonctionner dynamodb avec le Framework Django REST
CRUD PUT, DELETE avec Nuxt & Django REST Framework
Bases du framework Django REST
Astuces Django Rest Framework
Lorsque vous souhaitez filtrer avec le framework Django REST
Implémentez l'API à une vitesse explosive en utilisant Django REST Framework
Implémenter des URL hiérarchiques avec des routeurs imbriqués drf dans le framework Django REST
Créer une API avec Django
Créer une page d'accueil avec django
Bloc d'achoppement du framework Django REST
Créez facilement des systèmes d'authentification, de gestion des utilisateurs et multilingues avec Flask-AppBuilder
Créer une fonction d'authentification à l'aide de django-allauth et CustomUser dans Django
Créer une fonction d'authentification utilisateur dans Airflow
Implémentation de la fonction d'authentification JWT dans Django REST Framework à l'aide de djoser
Implémentation de CRUD à l'aide de l'API REST avec Python + Django Rest framework + igGrid
Créer un téléchargeur de fichiers avec Django
Comment gérer les caractères déformés dans json de Django REST Framework
Suppression logique dans Django, DRF (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
Notes diverses sur le framework Django REST
Créer un écran de mise à jour avec Django Updateview
Création de la première application avec Django startproject
Django REST Framework + Considération de conception d'architecture propre
Je souhaite créer une API qui retourne un modèle avec une relation récursive dans Django REST Framework
Comment générer automatiquement un document API avec le framework Django REST et POST à partir de l'écran de document
Django: enregistrez l'agent utilisateur et gérez-le avec l'administrateur
Créez un tableau de bord pour les appareils réseau avec Django!
Créer un environnement de Nginx + uWSGI + Python (Django) avec docker
Framework Django REST Un peu utile à savoir.
Créez une application Hello World avec un seul fichier avec django
Implémenter la fonctionnalité de connexion JWT dans le framework Django REST
Comment créer une API Rest dans Django
Pratique de développement d'applications Web: Créez une page de création d'équipe avec Django! (Traitement d'authentification)
Parfois, vous souhaitez accéder aux informations de vue depuis Serializer avec DRF (Django REST Framework)