1. Code
  2. Python
  3. Django

JWT Аутентификация в Django

В этой статье содержится введение в JSON Web Tokens (JWT) и руководство как реализовать JWT-аутентификацию в Django.
Scroll to top

Russian (Pусский) translation by Anna k.Ivanova (you can also view the original English article)

В этой статье содержится введение в JSON Web Tokens (JWT) и руководство как реализовать JWT-аутентификацию в Django.

Что такое JWT?

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

Как работают веб-маркеры JSON

Когда пользователь успешно выполняет вход в систему с использованием своих учетных данных, создается пользовательский токен JSON и сохраняется в локальном хранилище. Всякий раз, когда пользователь хочет получить доступ к защищенному URL-адресу, токен отправляется в заголовке запроса. Затем сервер проверяет правильность JWT в заголовке авторизации, и если он валидный, пользователю будет разрешен доступ.

Содержимое заголовка обычно выглядит так:

Authorization: Bearer eyJhbGciOiJIUzI1NiIsI

Ниже приведена диаграмма, показывающая этот процесс:

How JSON Web Tokens WorkHow JSON Web Tokens WorkHow JSON Web Tokens Work

Концепция аутентификации и авторизации

Аутентификация - это процесс идентификации зарегистрированного пользователя, а авторизация - это процесс определения того, имеет ли определенный пользователь право доступа к веб-ресурсу.

Пример API

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

Требования

  • Джанго
  • Python

Давайте начнем.

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

1
mkdir myprojects
2
3
cd myprojects
4
5
virtual venv
6

Активируйте виртуальную среду:

1
source venv/bin/activate

Создайте проект Django.

1
django-admin startproject django_auth
2

Установите DRF и django-rest-framework-jwt, используя pip.

1
pip install djangorestframework
2
pip install djangorestframework-jwt
3
pip install django

Давайте продолжим и добавим DRF в список установленных приложений в файле settings.py.

Настройка параметров JWT

Чтобы использовать JWT, нам нужно настроить разрешения django-rest-framework для принятия JSON Web Tokens.

В файле settings.py добавьте следующие конфигурации:

1
REST_FRAMEWORK = {
2
  'DEFAULT_AUTHENTICATION_CLASSES': (
3
    'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
4
  ),
5
}

Создайте новое приложение users, которое будет обрабатывать аутентификацию и управление пользователями.

1
cd django-auth
2
django-admin.py startapp users 

Добавьте приложение users в список установленных приложений в файле settings.py.

Настройка базы данных

Мы собираемся использовать базу данных PostgreSQL, потому что она более стабильна и надежна.

Создайте базу данных auth и назначьте пользователя.

Перейдите на учетную запись Postgres на вашем компьютере, набрав:

1
sudo su postgres

Зайдите в оболочку Postgres и создайте базу данных:

1
psql
2
postgres=# CREATE DATABASE auth;

Создайте роль:

1
postgres=# CREATE ROLE django_auth WITH LOGIN PASSWORD 'asdfgh'; 

Предоставьте доступ к базе данных для пользователя:

1
postgres=# GRANT ALL PRIVILEGES ON DATABASE auth TO django_auth;

Установите пакет psycopg2, который позволит нам использовать настроенную нами базу данных:

1
pip install psycopg2

Отредактируйте текущую базу данных SQLite и используйте базу данных Postgres.

1
DATABASES = {
2
    'default': {
3
        'ENGINE': 'django.db.backends.postgresql_psycopg2',
4
        'NAME': 'auth',
5
        'USER': 'django_auth',
6
        'PASSWORD': 'asdfgh',
7
        'HOST': 'localhost',
8
        'PORT': '',
9
    }
10
}

Создание моделей

Django поставляется со встроенной системой аутентификации, которая очень сложна, но иногда нам нужно внести коррективы, и, следовательно, нам нужно создать собственную систему аутентификации пользователей. Наша модель пользователя будет наследоваться от класса AbstractBaseUser, предоставляемого django.contrib.auth.models.

В users/models.py мы начинаем с создания модели User для хранения деталей пользователя.

1
# users/models.py

2
from __future__ import unicode_literals
3
from django.db import models
4
from django.utils import timezone
5
from django.contrib.auth.models import (
6
    AbstractBaseUser, PermissionsMixin
7
)
8
9
class User(AbstractBaseUser, PermissionsMixin):
10
    """

11
    An abstract base class implementing a fully featured User model with

12
    admin-compliant permissions.

13


14
    """
15
    email = models.EmailField(max_length=40, unique=True)
16
    first_name = models.CharField(max_length=30, blank=True)
17
    last_name = models.CharField(max_length=30, blank=True)
18
    is_active = models.BooleanField(default=True)
19
    is_staff = models.BooleanField(default=False)
20
    date_joined = models.DateTimeField(default=timezone.now)
21
22
    objects = UserManager()
23
24
    USERNAME_FIELD = 'email'
25
    REQUIRED_FIELDS = ['first_name', 'last_name']
26
27
    def save(self, *args, **kwargs):
28
        super(User, self).save(*args, **kwargs)
29
        return self

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

UserManager - это класс, определяющий методы create_user и createuperuser. Этот класс должен предшествовать классу AbstractBaseUser, который мы определили выше. Давайте продолжим и определим это.

1
from django.contrib.auth.models import (
2
    AbstractBaseUser, PermissionsMixin, BaseUserManager
3
)
4
5
class UserManager(BaseUserManager):
6
7
    def _create_user(self, email, password, **extra_fields):
8
        """

9
        Creates and saves a User with the given email,and password.

10
        """
11
        if not email:
12
            raise ValueError('The given email must be set')
13
        try:
14
            with transaction.atomic():
15
                user = self.model(email=email, **extra_fields)
16
                user.set_password(password)
17
                user.save(using=self._db)
18
                return user
19
        except:
20
            raise
21
22
    def create_user(self, email, password=None, **extra_fields):
23
        extra_fields.setdefault('is_staff', False)
24
        extra_fields.setdefault('is_superuser', False)
25
        return self._create_user(email, password, **extra_fields)
26
27
    def create_superuser(self, email, password, **extra_fields):
28
        extra_fields.setdefault('is_staff', True)
29
        extra_fields.setdefault('is_superuser', True)
30
31
        return self._create_user(email, password=password, **extra_fields)

Миграции

Миграции предоставляют способ обновления вашей схемы базы данных каждый раз, когда ваши модели меняются, не теряя при этом данные.

Создайте первоначальную миграцию для модели наших пользователей и выполните синхронизацию базы данных.

1
python manage.py make migrations users

2


3
python manage.py migrate

Создание суперпользователя

Создайте суперпользователя, выполнив следующую команду:

1
python manage.py createsuperuser

Создание новых пользователей

Давайте создадим ендпоинт, чтобы разрешить регистрацию новых пользователей. Мы начнем с сериализации полей модели пользователя. Сериализаторы предоставляют способ изменения данных в форме, которую легче понять, например, JSON или XML. Десериализация делает обратное, преобразуя данные в форму, которая может быть сохранена в базе данных.

Создайте users/serializers.py и добавьте следующий код.

1
# users/serializers.py

2
from rest_framework import serializers
3
from.models import User
4
5
6
class UserSerializer(serializers.ModelSerializer):
7
8
    date_joined = serializers.ReadOnlyField()
9
10
    class Meta(object):
11
        model = User
12
        fields = ('id', 'email', 'first_name', 'last_name',
13
                  'date_joined', 'password')
14
        extra_kwargs = {'password': {'write_only': True}}

CreateUserAPIView

Затем мы хотим создать представление, чтобы у клиента был URL-адрес для создания новых пользователей.

В user.views.py добавьте следующее:

1
# users/views.py

2
class CreateUserAPIView(APIView):
3
    # Allow any user (authenticated or not) to access this url 

4
    permission_classes = (AllowAny,)
5
6
    def post(self, request):
7
        user = request.data
8
        serializer = UserSerializer(data=user)
9
        serializer.is_valid(raise_exception=True)
10
        serializer.save()
11
        return Response(serializer.data, status=status.HTTP_201_CREATED)

Мы устанавливаем allow_classes в (AllowAny,), чтобы разрешить любому пользователю (прошедшему проверку подлинности или нет) доступ к этому URL-адресу.

Настройка URL-адресов

Создайте файл users/urls.py и добавьте URL-адрес в соответствие с созданным нами представлением. Также добавьте следующий код.

1
# users/urls.py

2
3
from django.conf.urls import url, patterns
4
from .views import CreateUserAPIView
5
6
urlpatterns = [
7
    url(r'^create/$', CreateUserAPIView.as_view()),
8
]

Нам также нужно импортировать URL-адреса из приложения-пользователя в основной файл django_auth/urls.py. Так что делаем это и двигаемся дальше. Мы используем здесь функцию include , поэтому не забудьте ее импортировать.

1
# django_auth/urls.py

2
from django.conf.urls import url, include
3
from django.contrib import admin
4
5
urlpatterns = [
6
    url(r'^admin/', admin.site.urls),
7
    url(r'^user/', include('users.urls', namespace='users')),
8
9
]

Теперь, когда мы закончили создание ендпоинта, давайте  протестируем его. Для тестов мы будем использовать Postman. Если вы не знакомы с Postman, это инструмент, который представляет дружественный графический интерфейс для построения запросов и чтения ответов.

Configuring URLsConfiguring URLsConfiguring URLs

Как вы можете видеть выше, ендпоинт работает так, как ожидалось.

Аутентификация пользователей

Мы будем использовать модуль Django-REST Framework JWT Python, который мы установили в начале этого руководства. Он добавляет поддержку JWT-аутентификации для приложений Django Rest Framework.

Но сначала давайте определим некоторые параметры конфигурации для наших токенов и как они сгенерированы в файле settings.py.

1
# settings.py

2
import datetime
3
JWT_AUTH = {
4
5
    'JWT_VERIFY': True,
6
    'JWT_VERIFY_EXPIRATION': True,
7
    'JWT_EXPIRATION_DELTA': datetime.timedelta(seconds=3000),
8
    'JWT_AUTH_HEADER_PREFIX': 'Bearer',
9
10
}
  • JWT_VERIFY: он вызовет jwt.DecodeError, если секрет неправильный.
  • JWT_VERIFY_EXPIRATION: Устанавливает истечение срока действия в True, то есть токены истекают через некоторое время. Время по умолчанию - пять минут.
  • JWT_AUTH_HEADER_PREFIX: Префикс значения заголовка авторизации, который требуется отправить вместе с токеном. Мы установили его как Bearer, а по умолчанию - JWT.

В users/views.py добавьте следующий код.

1
@api_view(['POST'])
2
@permission_classes([AllowAny, ])
3
def authenticate_user(request):
4
5
    try:
6
        email = request.data['email']
7
        password = request.data['password']
8
9
        user = User.objects.get(email=email, password=password)
10
        if user:
11
            try:
12
                payload = jwt_payload_handler(user)
13
                token = jwt.encode(payload, settings.SECRET_KEY)
14
                user_details = {}
15
                user_details['name'] = "%s %s" % (
16
                    user.first_name, user.last_name)
17
                user_details['token'] = token
18
                user_logged_in.send(sender=user.__class__,
19
                                    request=request, user=user)
20
                return Response(user_details, status=status.HTTP_200_OK)
21
22
            except Exception as e:
23
                raise e
24
        else:
25
            res = {
26
                'error': 'can not authenticate with the given credentials or the account has been deactivated'}
27
            return Response(res, status=status.HTTP_403_FORBIDDEN)
28
    except KeyError:
29
        res = {'error': 'please provide a email and a password'}
30
        return Response(res)

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

Классы разрешений устанавливаются в allowAny, поскольку любой пользователь может получить доступ к этому ендпоинту.

Мы также сохраняем последнее время входа пользователя с этим кодом.

1
user_logged_in.send(sender=user.__class__,
2
                                    request=request, user=user)

Каждый раз, когда пользователь хочет сделать запрос API, он должен отправить токен в заголовках Auth, чтобы аутентифицировать запрос.

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

A sample responseA sample responseA sample response

Получение и обновление пользователей

Пока пользователи могут зарегистрироваться и пройти проверку подлинности. Тем не менее, так же нужно предоставить способ получения и обновления этой информации. Давайте реализуем это.

В файле user.views.py добавьте следующий код.

1
class UserRetrieveUpdateAPIView(RetrieveUpdateAPIView):
2
3
    # Allow only authenticated users to access this url

4
    permission_classes = (IsAuthenticated,)
5
    serializer_class = UserSerializer
6
7
    def get(self, request, *args, **kwargs):
8
        # serializer to handle turning our `User` object into something that

9
        # can be JSONified and sent to the client.

10
        serializer = self.serializer_class(request.user)
11
12
        return Response(serializer.data, status=status.HTTP_200_OK)
13
14
    def put(self, request, *args, **kwargs):
15
        serializer_data = request.data.get('user', {})
16
17
        serializer = UserSerializer(
18
            request.user, data=serializer_data, partial=True
19
        )
20
        serializer.is_valid(raise_exception=True)
21
        serializer.save()
22
23
        return Response(serializer.data, status=status.HTTP_200_OK)

Сначала мы определяем классы разрешений и устанавливаем IsAuthenticated, так как это защищенный URL-адрес, и к нему могут обращаться только прошедшие проверку подлинности пользователи.

Затем мы определяем метод get для получения сведений о пользователе. После получения сведений о пользователе аутентифицированный пользователь затем по желанию обновит свои данные.

Обновите URL-адреса, чтобы определить ендпоинт следующим образом.

1
users/urls.py
2
from .views import CreateUserAPIView, UserRetrieveUpdateAPIView
3
4
urlpatterns = [
5
    
6
    url(r'^update/$', UserRetrieveUpdateAPIView.as_view()),
7
]

Чтобы запрос был успешным, заголовки должны содержать  JWT токен, как показано ниже.

Headers with the JWT TokenHeaders with the JWT TokenHeaders with the JWT Token

Если вы попытаетесь запросить ресурс без заголовка проверки подлинности, вы получите следующую ошибку.

An example errorAn example errorAn example error

Если пользователь не делает запросов дольше времени, указанного в JWT_EXPIRATION_DELTA, то токен истекает, и ему придется запросить другой токен. Это также показано ниже.

JWT_EXPIRATION_DELTA ExampleJWT_EXPIRATION_DELTA ExampleJWT_EXPIRATION_DELTA Example

Заключение

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