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

  • Django
  • Django Rest Framework

Если вы хотите сразу перейти ко второй части (все, что касается Frontend), нажмите здесь.

JWT Authentication, спорная тема?

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

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

  • Настройте аутентификацию / авторизацию в новом или уже существующем проекте на базе автономного бэкэнда Django Rest Framework и внешнего интерфейса Vue.js.
  • Разместите интерфейс в верхнем домене (например, example.com), а бэкэнд в субдомене (например, api.example.com)
  • Использовать JWT (веб-токены JSON) в качестве метода аутентификации
  • Не выкидывай мой ноутбук в окно

Так почему же аутентификация JWT вызывает споры?

Некоторые разработчики просто скажут вам не использовать JWT, несмотря на его популярность. Я думаю, что во многом эта ненависть к JWT проистекает из того факта, что он не имеет состояния, а это означает, что если злоумышленник может завладеть вашим токеном доступа, сервер будет доверять ему независимо от того, откуда он возник. Межсайтовый скриптинг (XSS) является здесь ключевой уязвимостью, о которой меньше беспокоиться при использовании аутентификации сеанса. С другой стороны, разработчики нередко внедряют JWT без особой необходимости, а аутентификация сеанса является безопасной и надежной альтернативой.

Многие блоги и учебные пособия, объясняющие JWT, небрежно советуют читателю просто продолжить и сохранить токены доступа в локальном хранилище или в файлах cookie. Поскольку Javascript может получить к ним доступ, могут случиться неприятности.

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

Библиотеки аутентификации Django

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

В какой-то момент вы, вероятно, также столкнетесь с OAuth, OAuth2 и OpenID Connect. Это более глубокие темы. Только имейте в виду, что

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

Итак, OAuth - это стандарт, и вы можете использовать JWT в рамках этого стандарта. Существует также сторонняя служба Auth0, которую я постоянно путала с OAuth… так что берегитесь этой службы. Конечно, если вы хотите изучить сторонние инструменты, попробуйте Auth0 и Okta.

Учитывая все обстоятельства, я выбрал Dj Rest Auth. Это был тот вариант, который, как мне показалось, лучше всего соответствовал моему предпочтению, чтобы библиотека просто работала. И на самом деле вы можете настроить его для использования библиотек Simple-JWT и All-Auth в любом случае, так что все это хорошо сочетается. Кроме того, документация поможет вам использовать социальную аутентификацию (например, вход в Facebook или Github), когда вы будете к этому готовы.

Итак, без лишних слов, давайте займемся кодированием.

Настройка бэкэнда Django

Я не буду вдаваться в основы настройки приложения Django, это предполагается. Я также не буду вдаваться в очень простую установку dj-rest-auth; Я доверяю вам следовать отличной документации.

После установки dj-rest-auth (не забудьте применить миграции) вы сразу получите доступ к различным конечным точкам REST, таким как /login и /logout. Хорошая идея - протестировать их в просматриваемом API Django Rest Framework (который обычно находится по адресу localhost:8000).

Если вы столкнулись с ошибками на этом этапе, убедитесь, что вы добавили все необходимые библиотеки в settings.py и установили все в свою виртуальную среду.

INSTALLED_APPS = [
    'django.contrib.auth',
    'django.contrib.admin',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'django_extensions',
    'corsheaders',
    'dj_rest_auth',
    'rest_framework',
    'rest_framework.authtoken',
    'django.contrib.sites',
    'allauth',
    'allauth.account',
    'allauth.socialaccount',
    'dj_rest_auth.registration',
    'sslserver', # we'll discuss this one later
    'users' # This is just the name of my django app
]

Обратите внимание, что этот список включает allauth учетных записей социальных сетей; на самом деле это делается для того, чтобы конечная точка регистрации dj-rest-auth могла работать. Кроме того, убедитесь, что в вашей виртуальной среде установлен djangorestframework-simplejwt.

Хорошо, теперь мы явно начнем использовать JWT. Параметры, применяемые в settings.py:

...
REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework.authentication.TokenAuthentication',
        'dj_rest_auth.jwt_auth.JWTCookieAuthentication'
    ),
    'DEFAULT_SCHEMA_CLASS': \
        rest_framework.schemas.coreapi.AutoSchema',
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.IsAuthenticated'
    ]
}
REST_SESSION_LOGIN = False
REST_USE_JWT = True
JWT_AUTH_COOKIE = 'jwt-access-token'           # you can set these
JWT_AUTH_REFRESH_COOKIE = 'jwt-refresh-token'  # to anything 
JWT_AUTH_SECURE = True
CORS_ALLOW_CREDENTIALS = True
CORS_ALLOWED_ORIGINS = ['https://example.com']
...

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

Последний пункт создает проблему: как мы должны использовать SSL / TLS в среде разработки? Конечно, мы просто не могли установить JWT_AUTH_SECURE, но оказывается, что https нам все равно понадобится. Что приводит нас к…

Файлы cookie (ном ном)

Хорошо, вернемся к противоречию. Я думаю, что люди в основном ненавидят JWT, потому что разработчики продолжают размещать токен доступа и токен обновления в LocalStorage. Не делай этого. И не кладите их в обычные куки.

Что нужно сделать, так это поместить токены в специальный тип cookie, называемый httpOnly cookie. Javascript не может читать этот тип файлов cookie, что обеспечивает некоторую защиту от XSS.

К счастью, dj-rest-auth абстрагируется почти все о файлах cookie httpOnly (ну, может быть, не так уж и хорошо, я выбрал библиотеку в значительной степени из-за этого). Однако не рассматривается, как именно заставить независимое интерфейсное приложение взаимодействовать с серверной частью с помощью этих файлов cookie. Теперь возникают важные отличия, о которых вам не нужно было беспокоиться, просто используя API с возможностью просмотра:

  • dj-rest-auth только файлы cookie, например https
  • Домены должны быть «того же сайта».

Эти моменты сводятся к тому, что нам нужен https даже в нашей среде разработки, и мы должны использовать правильные доменные имена, которые связаны между собой, то есть поддомены. Нам нужно подделать их, чтобы использовать нашу среду разработки. На производстве это не будет проблемой, потому что у нас будет SSL и настоящий DNS.

Примечание. Я не думаю, что мне нужно говорить вам об использовании https в рабочей среде. Если Nginx вас немного сбивает с толку, загляните в CaddyServer, по умолчанию это https.

Как создать среду разработки https с пользовательскими доменами

На сервере есть инструмент для этого: django-sslserver. Установите его в свою среду, добавьте в INSTALLED_APPS и замените runserver на runsslserver. Теперь просто скажите своему браузеру, что можно принять самоподписанный сертификат, и все готово. Магия.

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

Добавьте следующее в /etc/hosts или в другой файл hosts для вашей операционной системы:

127.0.0.1     api.example.com
127.0.0.1     example.com

Нам все еще нужно включить порты, но теперь вы должны иметь возможность посещать просматриваемый API по адресу https://api.example.com:8000 и интерфейс по адресу https://example.com:3000 (или любой другой порт, который прослушивает ваш интерфейс).

Финальная задача серверной части: большое количество нестандартного промежуточного программного обеспечения

Хорошо, мы почти закончили с серверной частью. Тем не менее, есть камень преткновения в отношении dj-rest-auth, о чем говорилось в этой записи Система отслеживания проблем Github. Надеюсь, это будет решено в более позднем выпуске, но на данный момент проблема в том, что наш браузер будет отправлять наши токены доступа в заголовке запроса.

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

В вашем приложении Django создайте middleware.py файл. Добавьте этот код:

Затем вы должны добавить это промежуточное ПО в INSTALLED_APPS:

MIDDLEWARE = [   
     ...
    'yourappname.middleware.MoveJWTRefreshCookieIntoTheBody',
]

После того, как все это настроено, Django теперь должен запускать работающий сервер JWT в вашей среде разработки.

В Части 2 этой статьи мы рассмотрим некоторые настройки внешнего интерфейса и завершим реализацию полного стека.