Статьи

Аутентификация JWT в Джанго

Из этого туториала вы узнаете, как использовать JSON Web Tokens (JWT) и как реализовать аутентификацию JWT в Django.

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

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

Типичный заголовок контента будет выглядеть так:

Authorization: Bearer eyJhbGciOiJIUzI1NiIsI

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

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

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

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

  • Джанго
  • питон

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

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

1
2
3
4
5
mkdir myprojects
 
cd myprojects
 
virtual venv

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

1
source venv/bin/activate

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

1
django-admin startproject django_auth

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

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

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

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

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

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

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

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

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

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

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

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

1
sudo su postgres

Откройте приглашение Postgres и создайте базу данных:

1
2
psql
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.

01
02
03
04
05
06
07
08
09
10
DATABASES = {
    ‘default’: {
        ‘ENGINE’: ‘django.db.backends.postgresql_psycopg2’,
        ‘NAME’: ‘auth’,
        ‘USER’: ‘django_auth’,
        ‘PASSWORD’: ‘asdfgh’,
        ‘HOST’: ‘localhost’,
        ‘PORT’: »,
    }
}

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

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# users/models.py
from __future__ import unicode_literals
from django.db import models
from django.utils import timezone
from django.contrib.auth.models import (
    AbstractBaseUser, PermissionsMixin
)
 
class User(AbstractBaseUser, PermissionsMixin):
    «»»
    An abstract base class implementing a fully featured User model with
    admin-compliant permissions.
 
    «»»
    email = models.EmailField(max_length=40, unique=True)
    first_name = models.CharField(max_length=30, blank=True)
    last_name = models.CharField(max_length=30, blank=True)
    is_active = models.BooleanField(default=True)
    is_staff = models.BooleanField(default=False)
    date_joined = models.DateTimeField(default=timezone.now)
 
    objects = UserManager()
 
    USERNAME_FIELD = ’email’
    REQUIRED_FIELDS = [‘first_name’, ‘last_name’]
 
    def save(self, *args, **kwargs):
        super(User, self).save(*args, **kwargs)
        return self

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

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
from django.contrib.auth.models import (
    AbstractBaseUser, PermissionsMixin, BaseUserManager
)
 
class UserManager(BaseUserManager):
 
    def _create_user(self, email, password, **extra_fields):
        «»»
        Creates and saves a User with the given email,and password.
        «»»
        if not email:
            raise ValueError(‘The given email must be set’)
        try:
            with transaction.atomic():
                user = self.model(email=email, **extra_fields)
                user.set_password(password)
                user.save(using=self._db)
                return user
        except:
            raise
 
    def create_user(self, email, password=None, **extra_fields):
        extra_fields.setdefault(‘is_staff’, False)
        extra_fields.setdefault(‘is_superuser’, False)
        return self._create_user(email, password, **extra_fields)
 
    def create_superuser(self, email, password, **extra_fields):
        extra_fields.setdefault(‘is_staff’, True)
        extra_fields.setdefault(‘is_superuser’, True)
 
        return self._create_user(email, password=password, **extra_fields)

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

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

1
2
3
python manage.py make migrations users
 
python manage.py migrate

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

1
python manage.py createsuperuser

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

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
# users/serializers.py
from rest_framework import serializers
from.models import User
 
 
class UserSerializer(serializers.ModelSerializer):
 
    date_joined = serializers.ReadOnlyField()
 
    class Meta(object):
        model = User
        fields = (‘id’, ’email’, ‘first_name’, ‘last_name’,
                  ‘date_joined’, ‘password’)
        extra_kwargs = {‘password’: {‘write_only’: True}}

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

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

01
02
03
04
05
06
07
08
09
10
11
# users/views.py
class CreateUserAPIView(APIView):
    # Allow any user (authenticated or not) to access this url
    permission_classes = (AllowAny,)
 
    def post(self, request):
        user = request.data
        serializer = UserSerializer(data=user)
        serializer.is_valid(raise_exception=True)
        serializer.save()
        return Response(serializer.data, status=status.HTTP_201_CREATED)

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

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

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

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

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

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

Настройка URL

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

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

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

01
02
03
04
05
06
07
08
09
10
# settings.py
import datetime
JWT_AUTH = {
 
    ‘JWT_VERIFY’: True,
    ‘JWT_VERIFY_EXPIRATION’: True,
    ‘JWT_EXPIRATION_DELTA’: datetime.timedelta(seconds=3000),
    ‘JWT_AUTH_HEADER_PREFIX’: ‘Bearer’,
 
}
  • JWT_VERIFY : Это вызовет ошибку jwt.DecodeEr, если секрет неверен.
  • JWT_VERIFY_EXPIRATION : Устанавливает срок действия True, означая, что токены истекают через определенный промежуток времени. Время по умолчанию составляет пять минут.
  • JWT_AUTH_HEADER_PREFIX : Префикс значения заголовка авторизации, который необходимо отправить вместе с токеном. Мы установили его как Bearer , и по умолчанию используется JWT .

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@api_view([‘POST’])
@permission_classes([AllowAny, ])
def authenticate_user(request):
 
    try:
        email = request.data[’email’]
        password = request.data[‘password’]
 
        user = User.objects.get(email=email, password=password)
        if user:
            try:
                payload = jwt_payload_handler(user)
                token = jwt.encode(payload, settings.SECRET_KEY)
                user_details = {}
                user_details[‘name’] = «%s %s» % (
                    user.first_name, user.last_name)
                user_details[‘token’] = token
                user_logged_in.send(sender=user.__class__,
                                    request=request, user=user)
                return Response(user_details, status=status.HTTP_200_OK)
 
            except Exception as e:
                raise e
        else:
            res = {
                ‘error’: ‘can not authenticate with the given credentials or the account has been deactivated’}
            return Response(res, status=status.HTTP_403_FORBIDDEN)
    except KeyError:
        res = {‘error’: ‘please provide a email and a password’}
        return Response(res)

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

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

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

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

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

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

Образец ответа

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

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class UserRetrieveUpdateAPIView(RetrieveUpdateAPIView):
 
   # Allow only authenticated users to access this url
   permission_classes = (IsAuthenticated,)
   serializer_class = UserSerializer
 
   def get(self, request, *args, **kwargs):
       # serializer to handle turning our `User` object into something that
       # can be JSONified and sent to the client.
       serializer = self.serializer_class(request.user)
 
       return Response(serializer.data, status=status.HTTP_200_OK)
 
   def put(self, request, *args, **kwargs):
       serializer_data = request.data.get(‘user’, {})
 
       serializer = UserSerializer(
           request.user, data=serializer_data, partial=True
       )
       serializer.is_valid(raise_exception=True)
       serializer.save()
 
       return Response(serializer.data, status=status.HTTP_200_OK)

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

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

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

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

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

Заголовки с токеном JWT

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

Пример ошибки

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

Пример JWT_EXPIRATION_DELTA

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