Evgenii Legotckoi
14 лютого 2022 р. 15:24

Django Rest Framework - Урок 001. Додавання аутентифікації за токеном

На даний момент я активно працюю над програмою, яка буде працювати з REST API сайту на Django. І одним з перших кроків було налаштування аутентифікації користувача за токеном, але для того, щоб це запрацювало, потрібно спочатку отримати токен авторизації.

Розгляньмо, як це можна зробити.


Установка Django Rest Framework

Оскільки це найперша стаття із застосування DRF, то опишу докладніший процес встановлення та налаштування DRF. У Подальшому я вже не наводитиму цих інструкцій.

Встановлюємо DRF та інші необхідні пакети

  1. pip install djangorestframework
  2. pip install markdown # Markdown support for the browsable API.
  3. pip install django-filter

Далі додаємо програму 'rest_frameword' в INSTALLED_APPS у файлі конфігурації сайту settings.py

  1. INSTALLED_APPS = [
  2. ...
  3. 'rest_framework',
  4. ]

Далі офіційна документація пропонує підключити в urls.py файлі login та logout View із rest_framework, щоб можна було зручно користуватися API у браузері.

Якщо чесно, то я не використовую це API, оскільки мені зручніше користуватися документацією, що згенерована через swagger, про що я теж скажу далі, але я все одно підключаю дані View, щоб потім не вирішувати випадкові проблеми.

  1. urlpatterns = [
  2. ...
  3. path('api-auth/', include('rest_framework.urls')),
  4. ]

Далі налаштовуємо класи автентифікації та прав доступу за замовчуванням (також у settingsp.py)

  1. REST_FRAMEWORK = {
  2. 'DEFAULT_AUTHENTICATION_CLASSES': [
  3. 'rest_framework.authentication.BasicAuthentication',
  4. 'rest_framework.authentication.SessionAuthentication',
  5. 'rest_framework.authentication.TokenAuthentication',
  6. ],
  7. 'DEFAULT_PERMISSION_CLASSES': [
  8. 'rest_framework.permissions.IsAuthenticated',
  9. ],
  10. }

Тепер все готове, щоб написати точку доступу для отримання токена. Але попередньо я хочу підключити ще й генерування swagger документації. Просто так зручніше працювати, мені, наприклад, дуже подобається документація redoc, в якій досить красиво і зручно все відображається.

Установка Swagger

А зараз одразу підключимо swagger на сайті. Я розумію, що це не відноситься до теми статті, але в коді я роблю спеціальні маніпуляції, щоб підключити спеціальне відображення документації і вважаю, що надалі це може стати Вам у нагоді, оскільки:

  • Ви будете надалі використовувати swagger
  • Вам доведеться іноді кастомізувати виведення інформації в описі API, а саме для токена у мене була найспецифічніша зміна для swagger.

Установим drf-yasg

Для цього керуючись документацією виконаємо наступну команду

  1. pip install -U drf-yasg

Після цього модифікуємо файл settings.py

  1. INSTALLED_APPS = [
  2. ...
  3. 'django.contrib.staticfiles', # required for serving swagger ui's css/js files
  4. 'drf_yasg',
  5. ...
  6. ]

А тепер зареєструємо swagger в urls сайту

  1. from django.urls import re_path
  2. from drf_yasg import openapi
  3. from drf_yasg.views import get_schema_view
  4. from rest_framework import permissions
  5.  
  6. schema_view = get_schema_view(
  7. openapi.Info(
  8. title="EXAMPLE API",
  9. default_version='v1',
  10. description="REST API Documentation",
  11. contact=openapi.Contact(email="example@example.com"),
  12. ),
  13. public=True,
  14. permission_classes=(permissions.IsAdminUser,),
  15. )
  16.  
  17. urlpatterns = [
  18. ...
  19. re_path(r'^swagger(?P<format>\.json|\.yaml)$', schema_view.without_ui(cache_timeout=0), name='schema-json'),
  20. re_path(r'^swagger/$', schema_view.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui'),
  21. re_path(r'^redoc/$', schema_view.with_ui('redoc', cache_timeout=0), name='schema-redoc'),
  22. ]

Якщо ви уважно подивіться на код, побачите, що я зареєстрував swagger тільки для адміністрації сайту. Якщо хочете, щоб документація була відкритою, видаліть цей рядок

  1. permission_classes=(permissions.IsAdminUser,),

Додатково можна додати інформацію у виведення документації про те, які типи автентифікації використовуються для API. Це виконується у файлі settings.py.

  1. SWAGGER_SETTINGS = {
  2. 'SECURITY_DEFINITIONS': {
  3. 'Basic': {
  4. 'type': 'basic'
  5. },
  6. 'Bearer': {
  7. 'type': 'apiKey',
  8. 'name': 'Authorization',
  9. 'in': 'header'
  10. }
  11. }
  12. }

Ось тепер можна приступити до реалізації точки доступу для отримання токена.

Додаток API

Створимо програму api, в якій буде додано кінцеву точку для отримання токена.

  1. python manage.py startapp api

serializers.py

Далі створимо файл із серіалізаторами, в якому буде створено спеціальний серіалізатор, який описуватиме відповідь сайту в swagger.

  1. # -*- coding: utf-8 -*-
  2.  
  3. from rest_framework import serializers
  4.  
  5.  
  6. class Response201AuthTokenSerializer(serializers.Serializer):
  7. token = serializers.CharField(required=True, allow_blank=False)
  8. user_id = serializers.IntegerField(required=True)
  9.  

Цей серіалізатор коректно генеруватиме інформацію про відповідь у документації, і я її використовую для створення JSON відповіді з токеном.

views.py

Після чого модифікує файл views.py, щоб отримати кінцеву точку.

  1. # -*- coding: utf-8 -*-
  2.  
  3. from django.contrib.auth import get_user_model
  4. from django.http import JsonResponse
  5. from django.utils.translation import gettext_lazy as _
  6. from drf_yasg import openapi
  7. from drf_yasg.utils import swagger_auto_schema
  8. from rest_framework import authentication, viewsets
  9. from rest_framework.authtoken.views import ObtainAuthToken
  10.  
  11. from api.serializers import Response201AuthTokenSerializer
  12.  
  13.  
  14. class CustomAuthToken(ObtainAuthToken):
  15. authentication_classes = [authentication.BasicAuthentication]
  16.  
  17. @swagger_auto_schema(responses={
  18. "201": openapi.Response(
  19. description=_("User has got Token"),
  20. schema=Response201AuthTokenSerializer,
  21. )
  22. })
  23. def post(self, request, *args, **kwargs):
  24. serializer = self.serializer_class(data=request.data,
  25. context={'request': request})
  26. serializer.is_valid(raise_exception=True)
  27. user = serializer.validated_data['user']
  28. token, created = Token.objects.get_or_create(user=user)
  29. response201 = Response201AuthTokenSerializer(data={"token": token.key, "user_id": user.pk})
  30. response201.is_valid(raise_exception=True)
  31. return JsonResponse(response201.data)

Як бачите в коді є кастомізована схема swagger відповіді 201, контент створений. З серіалізатора Response201AuthTokenSerializer буде сформовано коректну документацію.
Крім іншого я використовую Response201AuthTokenSerializer, щоб сфомувати відповідь з токеном, а також id користувача. Надалі я використовую ID користувача для маніпуляції з даними в мобільному додатку.
Відмінним нюансом даного view є те, що як аутентифікація використовується лише BasicAuthenttication, тобто користувач отримує доступ до даного view тільки через username та password. Надалі розробка API ведеться від використання токена.

urls.py

А тепер підключити view для отримання токена у файлах urls

  1. # -*- coding: utf-8 -*-
  2.  
  3. from django.urls import path
  4.  
  5. from api import views
  6.  
  7.  
  8. app_name = 'api'
  9. urlpatterns = [
  10. path('api-token-auth/', views.CustomAuthToken.as_view()),
  11. ]
  12.  

У цьому файлі визначаємо ім'я програми, що використовується для формування маршрутів, тобто api. Також підключаємо view токена до маршрутизації.

І в кінці підключаємо urls.py програми api в основному файлі urls.py всього проекту.

  1. urlpatterns = [
  2. ...
  3. path('api/', include('api.urls')),
  4. ...
  5. ]

Тепер, щоб отримати токен, необхідно направити POST запит по URL http://127.0.0.1/api/api-token-auth/**, в тому випадку, щоб тестувати функціонал на локальному сервері. У разі бойового сервера потрібно надсилати запит на адресу з доменом вашого сайту.

Для аутентифікації необхідно використовувати Basic аутентифікацію.

Приклад запиту маркера в Felgo QML

Ось приклад коду для запиту токена у додатку написаному на QML Felgo

  1. function login(username, password, success, error) {
  2. HttpRequest
  3. .post("http://127.0.0.1/api/api-token-auth/", {username: username, password: password})
  4. .timeout(5000)
  5. .set('Content-Type', 'application/json')
  6. .then(function(res) { success(res) })
  7. .catch(function(err) { error(err) });
  8. }

Рекомендовані статті на цю тему

По статті запитували0питання

1

Вам це подобається? Поділіться в соціальних мережах!

BL4CK R4BBIT
  • 14 лютого 2022 р. 17:42

Доброго времени.
А если кастомный юзер.
И активация юзера подтверждением по смс или звонку?

Evgenii Legotckoi
  • 14 лютого 2022 р. 17:54
  • (відредаговано)

Добрый день

Если Вы имеетe ввиду, что модель пользователя наследована от AbstractUser и переопределена для проекта, то да - этот пример рабочий, я сейчас занимаюсь одним проектом, который будет иметь приложение и использую там кастомного пользователя.

А что касается подтверждения по СМС, то я участвовал несколько лет назад в проекте, где было подтверждение по СМС. Код показать не могу, но суть была в том, что там было два REST API View, сначало приложение стучалось на первый url, куда посылало логин и пароль, при получении ответа 200, приложение меняло форму ввода данных и ждало, пока пользователь заполнит код из СМС и подтвердит отправку. В этом случае отправка кода была уже на второй url, который проверял правильность кода, и уже в этом случае отдавал токен.

Естественно первый URL, который обрабатывал логин и пароль, стучался на API стороннего провайдера по отправке СМС, получал отправленный код и сохранял в отдельную таблицу, чтобы потом сравнить код и соответствие пользователя коду.

BL4CK R4BBIT
  • 14 лютого 2022 р. 18:14
  • (відредаговано)

В моем случае кастом от AbstractBaseUser

  1. class User(AbstractBaseUser):
  2. phone = PhoneNumberField(region='RU', unique=True)
  3. username = models.CharField(max_length = 150, blank = True, null = True)
  4. standard = models.CharField(max_length = 3, blank = True, null = True)
  5. score = models.IntegerField(default = 16)
  6. first_login = models.BooleanField(default=False)
  7. active = models.BooleanField(default=False)
  8. staff = models.BooleanField(default=False)
  9. admin = models.BooleanField(default=False)
  10. timestamp = models.DateTimeField(auto_now_add=True)
  11.  
  12. USERNAME_FIELD = 'phone'
  13. REQUIRED_FIELDS = []
  14.  
  15. objects = UserManager()
  16.  
  17. def __str__(self):
  18. return self.phone
  19.  
  20. def get_full_name(self):
  21. return self.phone
  22.  
  23. def get_short_name(self):
  24. return self.phone
  25.  
  26. def has_perm(self, perm, obj=None):
  27. return True
  28.  
  29. def has_module_perms(self, app_label):
  30.  
  31. return True
  32.  
  33. @property
  34. def is_staff(self):
  35. return self.staff
  36.  
  37. @property
  38. def is_admin(self):
  39. return self.admin
  40.  
  41. @property
  42. def is_active(self):
  43. return self.active

Только токен я использую knox(Стандарт мобильной безопастности Samsung), потому и спросил

Дальше понятно:
Регистрация post на страницу урл api/register(можно сразу отправить запрос на звонок, после создания пользователя)
Если удачно то получим 4-x символьный код от провайдера, запищем его. Если неудачно то создаем свой и шлем смс
Cледом post-запрос на верификацию с кодом(при удаче - активируем пользователя, при не удаче 4 попытки на повтор)

Evgenii Legotckoi
  • 14 лютого 2022 р. 18:22

В таком случае без напильника этот код сходу не взлетит, если конкретно knox нужно использовать.
А касательно самого AbstractBaseUser, то должно заработать.

Коментарі

Only authorized users can post comments.
Please, Log in or Sign up
  • Останні коментарі
  • Evgenii Legotckoi
    16 квітня 2025 р. 17:08
    Благодарю за отзыв. И вам желаю всяческих успехов!
  • IscanderChe
    12 квітня 2025 р. 17:12
    Добрый день. Спасибо Вам за этот проект и отдельно за ответы на форуме, которые мне очень помогли в некоммерческих пет-проектах. Профессиональным программистом я так и не стал, но узнал мно…
  • AK
    01 квітня 2025 р. 11:41
    Добрый день. В данный момент работаю над проектом, где необходимо выводить звук из программы в определенное аудиоустройство (колонки, наушники, виртуальный кабель и т.д). Пишу на Qt5.12.12 поско…
  • Evgenii Legotckoi
    09 березня 2025 р. 21:02
    К сожалению, я этого подсказать не могу, поскольку у меня нет необходимости в обходе блокировок и т.д. Поэтому я и не задавался решением этой проблемы. Ну выглядит так, что вам действитель…
  • VP
    09 березня 2025 р. 16:14
    Здравствуйте! Я устанавливал Qt6 из исходников а также Qt Creator по отдельности. Все компоненты, связанные с разработкой для Android, установлены. Кроме одного... Когда пытаюсь скомпилиров…