Аутентификация на основе JSON Web Token в Django и AngularJS: часть первая

в 14:43, , рубрики: AngularJS, django

Наиболее распространенным методом аутентификации является аутентификация с использованием cookie файлов. Более современный метод аутентификации основан на использовании JSON Web Token (дословно: вэб маркер в формате JSON) и он быстро набирает популярность. В этой статье мы сосредоточимся на нем.

Что такое JSON Web Token?

На самом деле маркеры довольно просты. Представьте себе маркер как ключ в виде карточки, с помощью которого вы каждый день заходите в офис. Когда вас взяли на работу в компанию, вы подтвердили свою личность (логин), предоставив набор учетных данных, таких как водительские права или номер социального страхования. В обмен на эти данных вам выдали ключ (маркер), который разрешает вам находиться в здании. Если бы вы решили уйти с работы или вас уволили (вышли из системы), компания может просто отключить ключ. Конечно, этот ключ может остаться у вас в кармане, но он не даст вам ничего хорошего.

JSON Web Token – это простой маркер в специальном формате. Пока формат не стандартизован, но многие уже используют одну из его реализаций (JWT).

JWT (произносится как jot) состоит из трех частей:

  • Заголовок
  • Полезная нагрузка
  • Подпись

Давайте рассмотрим каждую из них, прежде чем углубимся в реализацию.

Заголовок

По умолчанию, заголовок содержит только тип маркера и алгоритм, используемый для шифрования.
Тип маркера хранится в ключе «typ». Ключ «typ» игнорируется в JWT (для этой статьи это не важно, но вы можете прочитать, чтобы узнать почему). Если ключ «typ» присутствует, его значение должно быть JWT, чтобы указать, что этот объект является JSON Web Token.
Второй ключ «alg» определяет алгоритм, используемый для шифрования маркера. По умолчанию он должен быть установлен в HS256. Есть целый ряд альтернативных алгоритмов, которые используются в различных реализациях. Полный список с разбивкой по библиотекам можно посмотреть на JWT.io.
Заголовок кодируется в base64.

Полезная нагрузка

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

{
    "email": "example@jamesbrewer.io"
}
 

Ключи в полезной нагрузке могут быть произвольными. Тем не менее, есть несколько зарезервированных, которые нужно знать:

  • iat (Issued At). Этот ключ представляет собой время, когда маркер был выдан и может быть использован для определения возраста JWT. iat ключ должен быть отметкой времени в unix формате.
  • exp (Expiration Time). Этот ключ указывает, когда истекает срок действия маркера. Стандарт

JWT требует, чтобы во всех его реализациях маркеры с истекшим сроком действия отклонялись. В некоторых реализациях могут быть добавлены дополнительные ключи, для учета рассинхронизации времени. exp ключ должен быть отметкой времени в unix формате.
Важно понимать, что полезная нагрузка не передается в зашифрованном виде (хотя, маркеры могут быть вложенными и тогда возможно передавать зашифрованные данные). Поэтому в ней нельзя хранить любую секретную информацию. Например, пароли, номера социального страхования и т.д.
Как и заголовок, полезная нагрузка кодируется в base64.

Подпись

Когда у нас есть заголовок и полезная нагрузка, можно вычислить подпись.
Берутся закодированные в base64: заголовок и полезная нагрузка, они объединяются в строку через точку. Затем эта строка и секретный ключ поступает на вход алгоритма шифрования, указанного в заголовке (ключ «alg»). Ключом может быть любая строка. Более длинные строки будут наиболее предпочтительнее, поскольку потребуется больше времени на подбор.

JWT

Теперь, когда у нас есть заголовок, полезная нагрузка и подпись, мы можем построить JWT. Окончательный JWT выглядит следующим образом:

<encoded header>.<encoded payload>.<signature>

Примечание: никогда не сохраняйте маркер в вашей БД. Потому что действительный маркер, эквивалентен паролю, хранить маркер – это все равно, что хранить пароль в открытом виде. Поэтому всегда используйте хеширование перед сохранением.

Реализация проверки подлинности с помощью JSON Web Token

Давайте пойдем дальше и создадим наш проект.
Перейдите в каталог проекта и выполните команды:

$ django-admin.py startproject django_angular_token_auth
$ cd django_angular_token_auth/
$ mkdir static templates

Вам нужно убедиться, что настройки STATICFILES_DIRS и TEMPLATE_DIRS присутствуют в django_angular_token_auth/settings.py и они должны выглядеть следующим образом:

STATICFILES_DIRS = (
    os.path.join(BASE_DIR, 'static'),
)
TEMPLATE_DIRS = (
    os.path.join(BASE_DIR, 'templates'),
)

Django REST Framework

Теперь нужно установить Django REST Framework. Django REST Framework большой проект с открытым исходным кодом, для тех, кто хочет создавать одностраничные приложения и API для них.

Мы не будем детально рассматривать, как работает Django REST Framework, поэтому если вы не знакомы с ним, посмотрите документацию.

Для установки Django REST Framework, выполните следующую команду:

$ pip install djangorestframework

Django REST Framework должен быть добавлен к вашим установленным приложениям (INSTALLED_APPS в django_angular_token_auth/settings.py:)

INSTALLED_APPS = (
    ...,
    'rest_framework',
)

Так же необходимо добавить следующие параметры в django_angular_token_auth/settings.py:

REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES': (
        'rest_framework.permissions.IsAuthenticated',
    ), 
    'DEFAULT_AUTHENTICATION_CLASSES': {
        'rest_framework.authentication.SessionAuthentication',
        'rest_framework.authentication.BasicAuthentication',
    ),
}

Мы не будем использовать SessionAuthentication или BasicAuthentication, но они имеются в Django REST Framework и предлагают работоспособный API из коробки.

django-rest-framework-jwt

Последнее, что нужно сделать, это установить django-rest-framework-jwt. Этот пакет обеспечивает поддержку JWT для Django REST Framework и в качестве JSON Web Token использует реализацию PyJWT. Что бы установить django-rest-framework-jwt выполните следующую команду:

$ pip install djangorestframework-jwt

Нужно будет добавить следующие параметры в django_angular_token_auth/settings.py:

import datetime
JWT_AUTH = {
    'JWT_EXPIRATION_DELTA': datetime.timedelta(days=14)
}

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

Вы так же должны добавить обновленные настройки REST_FRAMEWORK:

REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES': (
        'rest_framework.permissions.IsAuthenticated,
    ),
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework.authentication.SessionAuthentication',
        'rest_framework.authentication.BasicAuthentication',
        'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
}

Обратите внимание, что мы добавили:
'rest_framework_jwt.authentication.JSONWebTokenAuthentication', в 'DEFAULT_AUTHENTICATION_CLASSES'.

Создание Django приложения

Теперь давайте создадим приложение Django для хранения в нем представлений и сериализаторов:

$ python manage.py startapp authentication

Добавим 'authentication' в наш INSTALLED_APPS (в django_angular_token_auth/settings.py like so):

INSTALLED_APPS = (
	...,
	'rest_framework',
	'authentication',
)

Как привило после создания приложения нужно выполнить миграции, для создания моделей в БД. Но мы не будем создавать собственные модели, поэтому об этом не нужно беспокоиться.

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

Для проверки работоспособности API нужно отправлять какие-нибудь данные на AJAX запросы. Самый простой способ это сделать — отправить обратно данные пользователя в нашей системе.

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

$ python manage.py createsuperuser

После создания пользователя, нужно, чтобы Django отправлял его данные в Angular приложение. Очевидно, что мы не можем отправлять Django объекты нашему приложению на JavaScript, поэтому нужен сериализатор для преобразования объектов в JSON и обратно. Его легко можно создать средствами Django REST Framework.
Создайте файл с именем serializers.py и добавьте в него следующий код:

from django.contrib.auth.models import User
from rest_framework import serializers

class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = (id, username, email)

Этот код указывает Django REST Framework, что мы хотим сериализовать модель User и мы хотим включить только следующие поля: id, username и email.

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

С сериализаторами разобрались, давайте теперь перейдем к представлениям.
Для наших целей нужно только одно представление, которое будет возвращать список объектов пользователя.
Django REST Framework предлагает целый ряд представлений, которые выполняют различные функции, например представление списка объектов одного типа. Такую функциональность предлагает класс ListAPIView.
Добавьте следующее в authentication/views.py:

from django.contrib.auth.models import User
from rest_framework import generics
from authentication.serializers import UserSerializer

class UserListAPIView(generics.ListAPIView):
    queryset = User.objects.all()
    serializer_class = UserSerializer

Шаблоны

Создадим простой шаблон, который будет представлять наше приложение.
Создайте файл с именем templates/index.html и добавьте следующий код в него:

<!DOCTYPE html>
<html>
    <head>
        <title>django-angular-token-auth</title>
    </head>

    <body>
        Hello, World!
    </body>
</html>

В дальнейшем мы добавим некоторый angular код в данный файл.

URL-адреса

Теперь нужно настроить URL-адреса для нашего проекта.
Откройте django_angular_token_auth/urls.py и приведите его к следующему виду:

from django.conf.urls import include, patterns, url
from django.views.generic import TemplateView
from authentication.views import UserListAPIView

urlpatterns = patterns('',
    url(r'api/v1/auth/login/', 'rest_framework_jwt.views.obtain_jwt_token'),
    url(r'api/v1/users/', UserListAPIView.as_view()),
    url(r'^.*$', TemplateView.as_view(template_name='index.html')),
)

Давайте рассмотрим, что здесь происходит.

Первое что можно заметить, во втором аргументе для первого URL-шаблона передается строка. Эта строка указывает на представление django-rest-framework-jwt, которое генерирует маркер. Мы не будем рассматривать, как оно работает, но вы можете посмотреть код django-rest-framework-jwt в Github репозитории: django-rest-framework-jwt/rest_framework_jwt/views.py.
Так же мы раньше не использовали представление TemplateView. Оно подобно классу ListAPIView в Django REST Framework предлагает простой способ для обработки шаблонов.

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

Для правильной обработки адресов важно, чтобы этот шаблон был последним. Потому что, Django сопоставляет адрес с шаблонами, в том порядке, в котором они определены и при первом же совпадении проверка прекращается. Так как используемое регулярное выражение совпадает с любым адресом, тем самым мы даем шанс всем остальным адресам быть обработанными. Все что не соответствует Django должно быть обработано Angular’ом. Так же пространство имен API /api/v1/, гарантирует, что шаблоны Django и Angular не будут конфликтовать.

Во второй части

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

Автор: d06pbiy_kot

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js