Регистрация и авторизация пользователей в Django с помощью djoser и веб-токенов JSON

В первой части введения в Django Rest Framework мы создали проект и сделали обзор того, что представляет собой проект. Проверьте это, если вы еще не сделали.

Ообзор

В этом разделе мы пройдем регистрацию пользователей с помощью Django Rest Framework и будем использовать веб-токены JSON для авторизации. Мы также будем расширять модель User по умолчанию, которая поставляется с django, чтобы мы могли получить больше информации о пользователях нашей системы.

Проект доступен на github.

Что такое веб-токен JSON?

JSON Web Token (JWT) - это интернет-стандарт для создания токенов доступа на основе JSON, которые утверждают некоторое количество претензий. Например, сервер может сгенерировать токен с флагом «авторизованный как администратор» или «авторизованный как этот пользователь» и предоставить его клиенту. Затем клиент может использовать этот токен, чтобы доказать, что он вошел как администратор. Токены подписаны закрытым ключом одной стороны (обычно сервером), чтобы обе стороны могли проверить, является ли токен легитимным. Маркеры спроектированы так, чтобы быть компактными, безопасными для URL и использоваться, особенно в контексте единого входа (SSO) веб-браузера. Заявления JWT обычно могут использоваться для передачи идентификационных данных аутентифицированных пользователей между поставщиком идентификационных данных и поставщиком услуг.

В отличие от аутентификации на основе токенов, JWT не хранятся в базе данных приложения. Для более подробного объяснения того, как работают JWT, посмотрите это потрясающее видео.

 

Создание проекта

Прежде чем продолжить, давайте посмотрим на некоторые из конечных точек, которые мы будем использовать в этом разделе.

Конечная точка Описание
/auth/users/ Зарегистрировать нового пользователя
/auth/users/me/ получить/обновить зарегистрированного пользователя
/auth/jwt/create/ создать JWT, передав действующему пользователю в запросе post эту конечную точку
/auth/jwt/refresh/ получить новый JWT по истечении времени жизни ранее сгенерированного
/api/accounts/all-profiles/ получить все профили пользователей и создать новый
/api/accounts/profile/id/ подробный вид профиля пользователя

Это может показаться трудным, но хорошая новость в том, что djoser сделал для нас большую часть тяжелой работы. Все конечные точки, начинающиеся с auth, генерируются djoser.

Возвращаясь к первой части серии, мы установили несколько пакетов Python. Нам нужно добавить эти пакеты в файл settings.py проекта, чтобы использовать их в нашем проекте django.

INSTALLED_APPS = [
	#our first django app
    'accounts',
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
	
	#third party package for user registration and authentication endpoints 	
    'djoser',
	
     #rest API implementation library for django
    'rest_framework',
	
	#JWT authentication backend library
    'rest_framework_simplejwt',
]

Не забудьте изменить настройки аутентификации для DRF, чтобы отразить использование JWTS.

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework_simplejwt.authentication.JWTAuthentication',
    ),
}

После настройки этой системы важно зарегистрировать маршруты для конечных точек, которые будут использоваться в проекте. Регистрируя пути к главному файлу проекта urls.py, мы можем получить доступ к различным конечным точкам, которые нам понадобятся позже.

 

from django.contrib import admin
from django.urls import path,include

urlpatterns = [
    path('admin/', admin.site.urls),
	
	#path to djoser end points
    path('auth/', include('djoser.urls')),
    path('auth/', include('djoser.urls.jwt')),
	
	#path to our account's app endpoints
    path("api/accounts/",include("accounts.urls"))
]

Модель профиля пользователя

Django поставляется с моделью пользователя по умолчанию с полями, такими как имя пользователя, пароли и адрес электронной почты, однако в некоторых случаях этих полей может быть недостаточно, чтобы мы могли расширить модель User или создать собственную модель пользователя. В этом случае мы будем расширять пользовательскую модель, потому что нам нужен способ дифференциации пользователей. Там будет два типа пользователей. Те, которые могут организовывать мероприятия, и те, которые просто хотят посещать мероприятия.

from django.db import models
from django.contrib.auth.models import User
# Создайте свои модели здесь.

class userProfile(models.Model):
    user=models.OneToOneField(User,on_delete=models.CASCADE,related_name="profile")
    description=models.TextField(blank=True,null=True)
    location=models.CharField(max_length=30,blank=True)
    date_joined=models.DateTimeField(auto_now_add=True)
    updated_on=models.DateTimeField(auto_now=True)
    is_organizer=models.BooleanField(default=False)

    def __str__(self):
        return self.user.username

Мы также создадим сигнал post_save для автоматического создания профиля пользователя для новых пользователей, которые регистрируются на платформе.

Для этого создайте файл signal.py и напишите приведенный ниже код.

from django.contrib.auth.models import User
from django.db.models.signals import post_save
from django.dispatch import receiver

from .models import userProfile


@receiver(post_save, sender=User)
def create_profile(sender, instance, created, **kwargs):
    if created:
        userProfile.objects.create(user=instance)

Для подробного объяснения того, как работают сигналы, это хорошая отправная точка. Не забудьте зарегистрировать сигнал в вашем файле app.py следующим образом:

from django.apps import AppConfig


class AccountsConfig(AppConfig):
    name = 'accounts'

    def ready(self):
        import accounts.signals

Сериализаторы

С базовыми настройками давайте перейдем к реализации API. Если вы новичок в django, сериализаторы позволяют преобразовывать сложные данные, такие как наборы запросов и экземпляры модели, в собственные типы данных Python, которые можно легко преобразовать в форматы, такие как JSON. Это называется сериализацией. Они также позволяют десериализацию после первой проверки данных входящего запроса.

В каталоге приложения мы запустим файл serializers.py и введем следующий код:

from rest_framework import serializers
from .models import userProfile
class userProfileSerializer(serializers.ModelSerializer):
    user=serializers.StringRelatedField(read_only=True)
    class Meta:
        model=userProfile
        fields='__all__'

Далее мы постарались импортировать класс сериализаторов из rest_framework, а также модель, которую мы хотим сериализовать. В данном случае это модель userProfile.

Наш первый сериализатор - userProfileSerializer. Он будет наследоваться от класса ModelSerializer в django. Как вы заметили ранее, модель userProfile была связана с моделью пользователя по умолчанию в django. Мы будем указывать это поле как read_only. Это означает, что поле будет включено в выходные данные API, но не будет включено во время операций создания или обновления на конечной точке. Чтобы заполнить это поле, мы создадим метод для автоматического заполнения поля пользователем запроса.

В rest_framework есть другие типы сериализатора, такие как ListSerializer и HyperlinkedModelSerializer. Для всестороннего руководства по сериализаторам лучше всего начать с документации по остальным фреймворкам.

Представления API

Для доступа к данным в API мы используем конечные точки. Это в основном URL-маршруты. Как работает django, так это то, что каждый URL связан с контроллером, называемым представлением. Контроллеры могут быть на основе классов или функций.

После того, как маршрутизация определила, какой контроллер использовать для запроса, ваш контроллер отвечает за понимание запроса и соответствующий вывод.

Одной из реализаций этого контроллера в инфраструктуре отдыха являются общие представления. Они были разработаны как ярлык для общих моделей использования. Они берут определенные общие идиомы и шаблоны, найденные при разработке представления, и абстрагируют их, чтобы вы могли быстро написать общие представления данных без необходимости повторяться.

Некоторыми из этих представлений являются CreateAPIView, ListAPIView, ListCreateAPIView, RetrieveUpdateDestroyAPIView, и этот список можно продолжить.

Мы реализуем ListCreateAPIView и RetrieveUpdateDestroyAPIView.

from rest_framework.generics import (ListCreateAPIView,RetrieveUpdateDestroyAPIView,)
from rest_framework.permissions import IsAuthenticated
from .models import userProfile
from .permissions import IsOwnerProfileOrReadOnly
from .serializers import userProfileSerializer


class UserProfileListCreateView(ListCreateAPIView):
    queryset=userProfile.objects.all()
    serializer_class=userProfileSerializer
    permission_classes=[IsAuthenticated]

    def perform_create(self, serializer):
        user=self.request.user
        serializer.save(user=user)


class userProfileDetailView(RetrieveUpdateDestroyAPIView):
    queryset=userProfile.objects.all()
    serializer_class=userProfileSerializer
    permission_classes=[IsOwnerProfileOrReadOnly,IsAuthenticated]

Каждое представление API связано с классом сериализатора, который мы создали ранее. Одна вещь, на которую мы обратим внимание, это perform_create метод в классе UserProfileListCreateView. Вот как мы указываем, как мы хотим создать сериализатор. В этом случае мы хотели заполнить поле пользователя read_only запрашивающим пользователем, а затем заполнить сериализатор этим значением.

The views are then linked to a URL endpoint in the app's urls.py file:

from django.urls import include, path
from rest_framework.routers import DefaultRouter

from .views import UserProfileListCreateView, userProfileDetailView

urlpatterns = [
    #gets all user profiles and create a new profile
    path("all-profiles",UserProfileListCreateView.as_view(),name="all-profiles"),
   # retrieves profile details of the currently logged in user
    path("profile/<int:pk>",userProfileDetailView.as_view(),name="profile"),
]

Права доступа

Разрешения определяют, должен ли запросу быть предоставлен или запрещен доступ. Django rest framework поставляется с несколькими. Я не буду вдаваться в подробности, поскольку их документация достаточно обширна. Однако давайте обратим наше внимание на класс разрешений IsOwnerProfileOrReadOnly.

Это реализация пользовательских разрешений. Мы инициализируем файл license.py и наполняем его следующим кодом:

from rest_framework.permissions import BasePermission,SAFE_METHODS

class IsOwnerProfileOrReadOnly(BasePermission):
    def has_object_permission(self, request, view, obj):
        if request.method in SAFE_METHODS:
            return True
        return obj.user==request.user

Переопределив класс BasePermission, мы можем создать собственное разрешение. Этот класс имеет два метода, которые мы можем переопределить: .has_permission() и .has_object_permission().

Оба должны возвращать True, если запрос должен быть удовлетворен, и False, если запрос отклонен. SAFE_METHODS - это GET, OPTIONS и HEAD.

В нашем пользовательском классе разрешений мы проверяем, похож ли запрашивающий пользователь на пользовательское поле объекта. Это гарантирует, что владелец профиля - единственный, кто может изменить свою информацию.

API тесты

Почти готово. Мы напишем несколько тестов, чтобы убедиться, что наши конечные точки работают должным образом.

class userProfileTestCase(APITestCase):
    profile_list_url=reverse('all-profiles')
    def setUp(self):
        # создайте нового пользователя, отправив запрос к конечной точке djoser
        self.user=self.client.post('/auth/users/',data={'username':'mario','password':'i-keep-jumping'})
        # получить веб-токен JSON для вновь созданного пользователя
        response=self.client.post('/auth/jwt/create/',data={'username':'mario','password':'i-keep-jumping'})
        self.token=response.data['access']
        self.api_authentication()

    def api_authentication(self):
        self.client.credentials(HTTP_AUTHORIZATION='Bearer '+self.token)

    # получить список всех профилей пользователей во время аутентификации пользователя запроса
    def test_userprofile_list_authenticated(self):
        response=self.client.get(self.profile_list_url)
        self.assertEqual(response.status_code,status.HTTP_200_OK)

    # получить список всех профилей пользователей, пока запрос пользователя не прошел проверку подлинности
    def test_userprofile_list_unauthenticated(self):
        self.client.force_authenticate(user=None)
        response=self.client.get(self.profile_list_url)
        self.assertEqual(response.status_code,status.HTTP_401_UNAUTHORIZED)

    # проверьте, чтобы получить данные профиля аутентифицированного пользователя
    def test_userprofile_detail_retrieve(self):
        response=self.client.get(reverse('profile',kwargs={'pk':1}))
        # print(response.data)
        self.assertEqual(response.status_code,status.HTTP_200_OK)


    # заполнить профиль пользователя, который был автоматически создан с использованием сигналов
    def test_userprofile_profile(self):
        profile_data={'description':'I am a very famous game character','location':'nintendo world','is_creator':'true',}
        response=self.client.put(reverse('profile',kwargs={'pk':1}),data=profile_data)
        print(response.data)
        self.assertEqual(response.status_code,status.HTTP_200_OK)

Чтобы запустить тесты, запустите команду

python manage.py test

в вашем терминале.

Если вы чувствуете себя немного смущенным, вот структура проекта до этого момента.

eventScheduler  
├── accounts    
│   ├── admin.py
│   ├── apps.py
│   ├── __init__.py
│   ├── migrations     
│   │   └── __init__.py
│   ├── models.py      
│   ├── permissions.py 
│   ├── serializers.py 
│   ├── tests.py       
│   ├── urls.py        
│   └── views.py       
├── eventScheduler     
│   ├── __init__.py
│   ├── __pycache__
│   │   ├── __init__.cpython-35.pyc
│   │   └── settings.cpython-35.pyc
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
└── manage.py

Демо с postman

Регистрация пользователя

Получить токен доступа

Получить/обновить аутентифицированного пользователя.

Мы будем передавать запрос get как пользователь Batman. Для этого у каждого postman запроса должен быть JWT, чтобы идентифицировать пользователя как действительного пользователя. В postman мы можем поместить токен в раздел auth и указать, что вы хотите использовать токен Bearer. Затем вы вставите полученный токен доступа.

Получить все профили пользователей

Это будет через запрос GET.

Обновить аутентифицированного пользователя

Через запрос PUT

Дополнительные ресурсы

  1. Официальная документация django rest framework.
  2. Документация djoser.

Это конец этой долгой статьи. Я надеюсь, что с этой информацией вы также можете создать свой собственный RESTful API с помощью django.

https://dev.to/lewiskori/user-registration-and-authorization-on-a-django-api-with-djoser-and-json-web-tokens-4kc7

Вернуться на верх