Spec-Zone.ru › Django 2.2

Управление паролями в Django

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

См. также

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

Как Django хранит пароли

Django предоставляет гибкую систему хранения паролей и по умолчанию использует PBKDF2.

Атрибут password объекта User представляет собой строку в следующем формате:

<algorithm>$<iterations>$<salt>$<hash>

Это компоненты, используемые для хранения пароля пользователя, разделенные символом «доллар» и состоящие из: алгоритма хэширования, количества итераций алгоритма (фактор сложности), случайного соли и результирующего хэша пароля. Алгоритм — это один из ряда односторонних алгоритмов хэширования или хранения паролей, которые Django может использовать; см. ниже. Итерации описывают количество раз, когда алгоритм выполняется над хэшем. Соль — это случайное начальное значение, а хэш — результат односторонней функции.

По умолчанию Django использует алгоритм PBKDF2 с хэшем SHA256, механизм растяжения паролей, рекомендуемый NIST. Это должно быть достаточно для большинства пользователей: оно довольно надежно, и для взлома требуется огромное количество вычислительной работы.

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

Django выбирает используемый алгоритм, обращаясь к настройке PASSWORD_HASHERS. Это список классов алгоритмов хэширования, которые поддерживает эта установка Django. Первый элемент в этом списке (то есть settings.PASSWORD_HASHERS[0]) будет использоваться для хранения паролей, а все остальные элементы — это допустимые хэшировщики, которые могут использоваться для проверки существующих паролей. Это означает, что если вы хотите использовать другой алгоритм, вам нужно изменить PASSWORD_HASHERS, чтобы поместить предпочитаемый алгоритм первым в списке.

Значение по умолчанию для PASSWORD_HASHERS такое:

PASSWORD_HASHERS = [
    'django.contrib.auth.hashers.PBKDF2PasswordHasher',
    'django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher',
    'django.contrib.auth.hashers.Argon2PasswordHasher',
    'django.contrib.auth.hashers.BCryptSHA256PasswordHasher',
]

Это означает, что Django будет использовать PBKDF2 для хранения всех паролей, но будет поддерживать проверку паролей, сохраненных с помощью PBKDF2SHA1, argon2 и bcrypt.

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

Использование Argon2 с Django

Argon2 — победитель конкурса Password Hashing Competition 2015 года, открытого сообществом конкурса по выбору алгоритма хэширования следующего поколения. Он разработан так, чтобы не быть проще для вычисления на специализированном оборудовании, чем на обычном процессоре.

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

Чтобы использовать Argon2 в качестве основного алгоритма хранения, выполните следующие действия:

  1. Установите библиотеку argon2-cffi. Это можно сделать, выполнив pip install django[argon2], что эквивалентно pip install argon2-cffi (вместе с любыми требованиями к версии из setup.py Django).
  2. Измените PASSWORD_HASHERS, чтобы поставить Argon2PasswordHasher на первое место. То есть в файле настроек вы бы указали:

    PASSWORD_HASHERS = [
        'django.contrib.auth.hashers.Argon2PasswordHasher',
        'django.contrib.auth.hashers.PBKDF2PasswordHasher',
        'django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher',
        'django.contrib.auth.hashers.BCryptSHA256PasswordHasher',
    ]
    

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

Использование bcrypt с Django

Bcrypt — популярный алгоритм хранения паролей, специально разработанный для долгосрочного хранения паролей. Он не используется по умолчанию в Django, поскольку требует использования сторонних библиотек, но поскольку многие люди могут захотеть его использовать, Django поддерживает bcrypt с минимальными усилиями.

Чтобы использовать Bcrypt в качестве основного алгоритма хранения, выполните следующие действия:

  1. Установите библиотеку bcrypt. Это можно сделать, выполнив pip install django[bcrypt], что эквивалентно pip install bcrypt (вместе с любыми требованиями к версии из setup.py Django).
  2. Измените PASSWORD_HASHERS, чтобы поставить BCryptSHA256PasswordHasher на первое место. То есть в файле настроек вы бы указали:

    PASSWORD_HASHERS = [
        'django.contrib.auth.hashers.BCryptSHA256PasswordHasher',
        'django.contrib.auth.hashers.PBKDF2PasswordHasher',
        'django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher',
        'django.contrib.auth.hashers.Argon2PasswordHasher',
    ]
    

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

Это все — теперь ваша установка Django будет использовать Bcrypt в качестве основного алгоритма хранения.

Увеличение фактора сложности алгоритма паролей

PBKDF2 и bcrypt

Алгоритмы PBKDF2 и bcrypt используют количество итераций или раундов хэширования. Это преднамеренно замедляет злоумышленников, делая атаки на хэшированные пароли сложнее. Однако, с увеличением вычислительной мощности количество итераций необходимо увеличивать. Мы выбрали разумное значение по умолчанию (и будем увеличивать его с каждым выпуском Django), но вы можете настроить его вверх или вниз в зависимости от ваших потребностей в безопасности и доступной вычислительной мощности. Для этого вы будете подклассировать соответствующий алгоритм и переопределять параметры iterations. Например, чтобы увеличить количество итераций, используемых алгоритмом PBKDF2 по умолчанию:

  1. Создайте подкласс django.contrib.auth.hashers.PBKDF2PasswordHasher:

    from django.contrib.auth.hashers import PBKDF2PasswordHasher
    
    class MyPBKDF2PasswordHasher(PBKDF2PasswordHasher):
        """
        A subclass of PBKDF2PasswordHasher that uses 100 times more iterations.
        """
        iterations = PBKDF2PasswordHasher.iterations * 100
    

    Сохраните это где-нибудь в вашем проекте. Например, вы можете поместить это в файл, такой как myproject/hashers.py.

  2. Добавьте ваш новый хэшировщик в качестве первого элемента в PASSWORD_HASHERS:

    PASSWORD_HASHERS = [
        'myproject.hashers.MyPBKDF2PasswordHasher',
        'django.contrib.auth.hashers.PBKDF2PasswordHasher',
        'django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher',
        'django.contrib.auth.hashers.Argon2PasswordHasher',
        'django.contrib.auth.hashers.BCryptSHA256PasswordHasher',
    ]
    

Это все — теперь ваша установка Django будет использовать больше итераций при хранении паролей с помощью PBKDF2.

Argon2

У Argon2 есть три атрибута, которые можно настроить:

  1. time_cost управляет количеством итераций внутри хэша.
  2. memory_cost управляет размером памяти, которая должна быть использована во время вычисления хэша.
  3. parallelism управляет тем, на скольких процессорах вычисление хэша может быть распараллелено.

Значения по умолчанию для этих атрибутов, вероятно, подойдут. Если вы определите, что хэш пароля слишком быстрый или слишком медленный, вы можете настроить его следующим образом:

  1. Выберите parallelism в качестве количества потоков, которые вы можете выделить для вычисления хэша.
  2. Выберите memory_cost в качестве памяти в КиБ, которую вы можете выделить.
  3. Настройте time_cost и измерьте время хэширования пароля. Выберите time_cost , которое занимает приемлемое время для вас. Если time_cost задано в 1, это неприемлемо медленно, уменьшите memory_cost.

memory_cost интерпретация

Утилита argon2 командной строки и некоторые другие библиотеки интерпретируют параметр memory_cost по-другому, чем значение, которое использует Django. Преобразование приведено в memory_cost == 2 ** memory_cost_commandline.

Обновление паролей

При входе в систему, если пароли хранятся с использованием любого алгоритма, отличного от предпочитаемого, Django автоматически обновит алгоритм до предпочитаемого. Это означает, что старые установки Django станут автоматически более безопасными по мере входа пользователей, а также означает, что вы можете перейти к новым (и лучшим) алгоритмам хранения, как они будут изобретены.

Однако Django может обновлять только пароли, использующие алгоритмы, указанные в PASSWORD_HASHERS, поэтому, переходя на новые системы, вы должны убедиться, что никогда не удаляете записи из этого списка. Если вы это сделаете, пользователи, использующие не указанные алгоритмы, не смогут обновить свой пароль. Хэшированные пароли будут обновлены при увеличении (или уменьшении) количества итераций PBKDF2 или раундов bcrypt.

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

Обновление паролей без необходимости входа в систему

Если у вас есть существующая база данных со старым, слабым хэшем, например, MD5 или SHA1, вы можете сами обновить эти хэши вместо того, чтобы ждать обновления при входе пользователя (что может никогда не произойти, если пользователь не вернется на ваш сайт). В этом случае вы можете использовать «оболочечный» хэшировщик паролей.

END_OF_DOCUMENT_MARKER

В этом примере мы мигрируем коллекцию хешей SHA1 для использования PBKDF2(SHA1(пароль)) и добавляем соответствующий хеширователь паролей для проверки, ввел ли пользователь правильный пароль при входе в систему. Мы предполагаем, что используем встроенную User модель и что наш проект имеет accounts приложение. Вы можете изменить шаблон, чтобы он работал с любым алгоритмом или с настраиваемой моделью пользователя.

Сначала мы добавим настраиваемый хеширователь:

accounts/hashers.py
from django.contrib.auth.hashers import (
    PBKDF2PasswordHasher, SHA1PasswordHasher,
)


class PBKDF2WrappedSHA1PasswordHasher(PBKDF2PasswordHasher):
    algorithm = 'pbkdf2_wrapped_sha1'

    def encode_sha1_hash(self, sha1_hash, salt, iterations=None):
        return super().encode(sha1_hash, salt, iterations)

    def encode(self, password, salt, iterations=None):
        _, _, sha1_hash = SHA1PasswordHasher().encode(password, salt).split('$', 2)
        return self.encode_sha1_hash(sha1_hash, salt, iterations)

Миграция данных может выглядеть примерно так:

accounts/migrations/0002_migrate_sha1_passwords.py
from django.db import migrations

from ..hashers import PBKDF2WrappedSHA1PasswordHasher


def forwards_func(apps, schema_editor):
    User = apps.get_model('auth', 'User')
    users = User.objects.filter(password__startswith='sha1$')
    hasher = PBKDF2WrappedSHA1PasswordHasher()
    for user in users:
        algorithm, salt, sha1_hash = user.password.split('$', 2)
        user.password = hasher.encode_sha1_hash(sha1_hash, salt)
        user.save(update_fields=['password'])


class Migration(migrations.Migration):

    dependencies = [
        ('accounts', '0001_initial'),
        # replace this with the latest migration in contrib.auth
        ('auth', '####_migration_name'),
    ]

    operations = [
        migrations.RunPython(forwards_func),
    ]

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

Наконец, мы добавим настройку PASSWORD_HASHERS:

mysite/settings.py
PASSWORD_HASHERS = [
    'django.contrib.auth.hashers.PBKDF2PasswordHasher',
    'accounts.hashers.PBKDF2WrappedSHA1PasswordHasher',
]

В этот список включите все другие хеширователи, которые использует ваш сайт.

Включенные хеширователи

Полный список хеширователей, включенных в Django:

[
    'django.contrib.auth.hashers.PBKDF2PasswordHasher',
    'django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher',
    'django.contrib.auth.hashers.Argon2PasswordHasher',
    'django.contrib.auth.hashers.BCryptSHA256PasswordHasher',
    'django.contrib.auth.hashers.BCryptPasswordHasher',
    'django.contrib.auth.hashers.SHA1PasswordHasher',
    'django.contrib.auth.hashers.MD5PasswordHasher',
    'django.contrib.auth.hashers.UnsaltedSHA1PasswordHasher',
    'django.contrib.auth.hashers.UnsaltedMD5PasswordHasher',
    'django.contrib.auth.hashers.CryptPasswordHasher',
]

Соответствующие имена алгоритмов:

  • pbkdf2_sha256
  • pbkdf2_sha1
  • argon2
  • bcrypt_sha256
  • bcrypt
  • sha1
  • md5
  • unsalted_sha1
  • unsalted_md5
  • crypt

Написание собственного хеширователя паролей

Если вы пишете свой собственный хеширователь паролей, который содержит множитель работы, например, количество итераций, вы должны реализовать метод harden_runtime(self, password, encoded), чтобы устранить разрыв во времени выполнения между множителем работы, указанным в encoded пароле, и значением по умолчанию для хеширователя. Это предотвращает атаку на время перечисления пользователей из-за разницы между запросом входа в систему для пользователя с паролем, закодированным в более старом количестве итераций, и несуществующим пользователем (который выполняет стандартное количество итераций хеширователя по умолчанию).

Принимая PBKDF2 в качестве примера, если encoded содержит 20 000 итераций, а значение по умолчанию хеширователя iterations равно 30 000, метод должен выполнить password дополнительные 10 000 итераций PBKDF2.

Если ваш хеширователь не имеет множителя работы, реализуйте метод как no-op (pass).

Ручное управление паролем пользователя

Модуль django.contrib.auth.hashers предоставляет набор функций для создания и проверки хешированных паролей. Вы можете использовать их независимо от модели User.

check_password(password, encoded) [source]

Если вы хотите вручную аутентифицировать пользователя, сравнивая текстовый пароль с хешированным паролем в базе данных, используйте удобную функцию check_password(). Она принимает два аргумента: текстовый пароль для проверки и полное значение поля password пользователя в базе данных для проверки, и возвращает True, если они совпадают, False, в противном случае.

make_password(password, salt=None, hasher='default') [source]

Создает хешированный пароль в формате, используемом этим приложением. Она принимает один обязательный аргумент: пароль в текстовом виде. Дополнительно вы можете указать соль и алгоритм хеширования, если вы не хотите использовать значения по умолчанию (первый элемент в настройке PASSWORD_HASHERS). См. Включенные хеширователи для имени алгоритма каждого хеширователя. Если аргумент password равен None, возвращается непригодный пароль (который никогда не будет принят функцией check_password()).

is_password_usable(encoded_password) [source]

Возвращает False, если пароль является результатом User.set_unusable_password().

Изменено в Django 2.1:

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

Проверка паролей

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

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

Проверка контролируется настройкой AUTH_PASSWORD_VALIDATORS. Значение по умолчанию для этой настройки — пустой список, что означает, что валидаторы не применяются. В новых проектах, созданных с помощью шаблона по умолчанию startproject, включен набор простых валидаторов.

По умолчанию валидаторы используются в формах для сброса или изменения паролей и в командных утилитах управления createsuperuser и changepassword. Валидаторы не применяются на уровне модели, например, в User.objects.create_user() и create_superuser(), поскольку мы предполагаем, что разработчики, а не пользователи, взаимодействуют с Django на этом уровне, а также потому, что проверка моделей не запускается автоматически при создании моделей.

Примечание

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

Включение проверки паролей

Проверка паролей настраивается в настройке AUTH_PASSWORD_VALIDATORS:

AUTH_PASSWORD_VALIDATORS = [
    {
        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
        'OPTIONS': {
            'min_length': 9,
        }
    },
    {
        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
    },
]

В этом примере включены все четыре встроенных валидатора:

  • UserAttributeSimilarityValidator, который проверяет сходство пароля с набором атрибутов пользователя.
  • MinimumLengthValidator, который просто проверяет, соответствует ли пароль минимальной длине. Этот валидатор настроен с пользовательским параметром: теперь он требует минимальной длины в девять символов вместо значения по умолчанию в восемь.
  • CommonPasswordValidator, который проверяет, встречается ли пароль в списке распространенных паролей. По умолчанию он сравнивает с включенным списком из 20 000 распространенных паролей.
  • NumericPasswordValidator, который проверяет, не состоит ли пароль полностью из цифр.

Для UserAttributeSimilarityValidator и CommonPasswordValidator в этом примере мы просто используем значения по умолчанию. NumericPasswordValidator не имеет настроек.

Тексты подсказок и любые ошибки валидаторов паролей всегда возвращаются в том порядке, в котором они указаны в настройке AUTH_PASSWORD_VALIDATORS.

Встроенные валидаторы

Django включает четыре валидатора:

class MinimumLengthValidator(min_length=8) [source]

Проверяет, соответствует ли пароль минимальной длине. Минимальная длина может быть настраиваема с помощью параметра min_length.

class UserAttributeSimilarityValidator(user_attributes=DEFAULT_USER_ATTRIBUTES, max_similarity=0.7) [source]

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

Параметр user_attributes должен быть итерируемым объектом имен атрибутов пользователя для сравнения. Если этот аргумент не указан, используется значение по умолчанию: 'username', 'first_name', 'last_name', 'email'. Несуществующие атрибуты игнорируются.

Минимальное сходство отклоняемого пароля может быть установлено в диапазоне от 0 до 1 с помощью параметра max_similarity . Значение 0 отклоняет все пароли, а значение 1 отклоняет только пароли, которые идентичны значению атрибута.

END_OF_DOCUMENT_MARKER
class CommonPasswordValidator(password_list_path=DEFAULT_PASSWORD_LIST_PATH) [source]

Проверяет, не является ли пароль распространённым. Преобразует пароль в нижний регистр (для выполнения сравнения без учёта регистра) и проверяет его по списку из 20 000 распространённых паролей, созданных Royce Williams.

Параметр password_list_path может быть установлен на путь к файлу с пользовательскими распространёнными паролями. Этот файл должен содержать один пароль в нижнем регистре на строке и может быть текстовым или сжатым с помощью gzip.

Изменено в Django 2.1:

В более старых версиях использовался список из 1000 распространённых паролей.

class NumericPasswordValidator [source]

Проверяет, не состоит ли пароль полностью из цифр.

Интегрирование проверки

В django.contrib.auth.password_validation есть несколько функций, которые вы можете вызвать из собственных форм или другого кода для интеграции проверки паролей. Это может быть полезно, если вы используете пользовательские формы для установки пароля или у вас есть вызовы API, которые позволяют устанавливать пароли, например.

validate_password(password, user=None, password_validators=None) [source]

Проверяет пароль. Если все валидаторы находят пароль допустимым, возвращает None. Если один или несколько валидаторов отклоняют пароль, вызывает ValidationError со всеми сообщениями об ошибках от валидаторов.

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

password_changed(password, user=None, password_validators=None) [source]

Сообщает всем валидаторам, что пароль был изменён. Это может использоваться валидаторами, такими как тот, который предотвращает повторное использование паролей. Этот метод следует вызывать после успешной смены пароля.

Для подклассов AbstractBaseUser, поле пароля будет помечено как «изменённое» при вызове set_password(), что запускает вызов password_changed() после сохранения пользователя.

password_validators_help_texts(password_validators=None) [source]

Возвращает список текстов справки всех валидаторов. Они объясняют требования к паролю пользователю.

password_validators_help_text_html(password_validators=None)

Возвращает строку HTML со всеми текстами справки в <ul>. Это полезно при добавлении проверки пароля в формы, так как вы можете передать вывод непосредственно параметру help_text поля формы.

get_password_validators(validator_config) [source]

Возвращает набор объектов валидаторов на основе параметра validator_config. По умолчанию все функции используют валидаторы, определённые в AUTH_PASSWORD_VALIDATORS, но вызвав эту функцию с другим набором валидаторов и передав результат в параметр password_validators других функций, вместо этого будет использоваться ваш настроенный набор валидаторов. Это полезно, когда у вас есть типичный набор валидаторов для большинства сценариев, но также есть специальная ситуация, которая требует настроенного набора. Если вы всегда используете один и тот же набор валидаторов, нет необходимости использовать эту функцию, так как по умолчанию используется конфигурация из AUTH_PASSWORD_VALIDATORS.

Структура validator_config идентична структуре AUTH_PASSWORD_VALIDATORS. Возвращаемое значение этой функции можно передать в параметр password_validators перечисленных выше функций.

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

Создание собственного валидатора

Если встроенных валидаторов Django недостаточно, вы можете создать собственные валидаторы паролей. Валидаторы — это довольно простые классы. Они должны реализовывать два метода:

  • validate(self, password, user=None): валидировать пароль. Вернуть None, если пароль допустимый, или вызвать ValidationError с сообщением об ошибке, если пароль недопустимый. Вы должны уметь обрабатывать случай, когда user является None — если это означает, что ваш валидатор не может работать, просто верните None для отсутствия ошибки.
  • get_help_text(): предоставить текст справки для объяснения требований пользователю.

Любые элементы в OPTIONS в AUTH_PASSWORD_VALIDATORS для вашего валидатора будут переданы в конструктор. Все аргументы конструктора должны иметь значение по умолчанию.

Вот базовый пример валидатора с одним необязательным параметром:

from django.core.exceptions import ValidationError
from django.utils.translation import gettext as _

class MinimumLengthValidator:
    def __init__(self, min_length=8):
        self.min_length = min_length

    def validate(self, password, user=None):
        if len(password) < self.min_length:
            raise ValidationError(
                _("This password must contain at least %(min_length)d characters."),
                code='password_too_short',
                params={'min_length': self.min_length},
            )

    def get_help_text(self):
        return _(
            "Your password must contain at least %(min_length)d characters."
            % {'min_length': self.min_length}
        )

Вы также можете реализовать password_changed(password, user=None), который будет вызван после успешной смены пароля. Это можно использовать, например, для предотвращения повторного использования пароля. Однако, если вы решите хранить предыдущие пароли пользователя, никогда не делайте это в виде открытого текста.

© Django Software Foundation and individual contributors
Licensed under the BSD License.
https://docs.djangoproject.com/en/2.2/topics/auth/passwords/

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API