{"title":"Django中的密码管理","version":"4.1","locale":"zh-hans","docname":"topics/auth/passwords","url":"/zh-hans/4.1/topics/auth/passwords/","canonical":"https://djangodocs.dev/zh-hans/4.1/topics/auth/passwords/","summary":"密码管理通常不应该被重新再设计，Django 努力提供了一个安全且灵活的管理用户密码的工具。","html":"<h1>Django中的密码管理<a class=\"heading-anchor\" href=\"#password-management-in-django\"><span class=\"visually-hidden\">Link to this heading</span><span aria-hidden=\"true\">#</span></a></h1>\n<p>密码管理通常不应该被重新再设计，Django 努力提供了一个安全且灵活的管理用户密码的工具。这篇文档描述了 Django 如何存储密码，如何配置存储哈希，和一些使用哈希密码的工具。</p>\n<aside class=\"admonition admonition-seealso\" role=\"note\">\n<p class=\"admonition-title\">See also</p>\n<p>即使用户使用了很强壮的密码，攻击者还是可以窃听他们的网络链接。用户使用 <a class=\"reference internal\" href=\"/zh-hans/4.1/topics/security/#security-recommendation-ssl\"><span class=\"std std-ref\">HTTPS</span></a> 可以避免通过纯 HTTP 链接发送密码（或其他一些敏感数据），因为它们很容易被密码嗅探。</p>\n</aside>\n<section id=\"how-django-stores-passwords\">\n<span id=\"auth-password-storage\"></span><h2>Django 如何存储密码<a class=\"heading-anchor\" href=\"#how-django-stores-passwords\"><span class=\"visually-hidden\">Link to this heading</span><span aria-hidden=\"true\">#</span></a></h2>\n<p>Django 提供灵活的密码存储系统，默认使用 PBKDF2。</p>\n<p><a class=\"reference internal\" href=\"/zh-hans/4.1/ref/contrib/auth/#django.contrib.auth.models.User\" title=\"django.contrib.auth.models.User\"><code class=\"xref py py-class docutils literal notranslate\">User</code></a> 对象的 <a class=\"reference internal\" href=\"/zh-hans/4.1/ref/contrib/auth/#django.contrib.auth.models.User.password\" title=\"django.contrib.auth.models.User.password\"><code class=\"xref py py-attr docutils literal notranslate\">password</code></a> 属性是如下这种格式：</p>\n<div class=\"code-block\" data-language=\"default\"><div class=\"code-block-toolbar\"><span class=\"code-block-language\">Code</span><button type=\"button\" class=\"copy-button\" data-copy hidden><span class=\"copy-button-label\">Copy</span></button></div><pre role=\"group\" tabindex=\"0\" aria-label=\"Code code\"><code>&lt;algorithm&gt;$&lt;iterations&gt;$&lt;salt&gt;$&lt;hash&gt;\n</code></pre></div>\n<p>这些是用来存储用户密码的插件，以美元符号分隔，包括：哈希算法，算法迭代次数（工作因子），随机 Salt 和最终的密码哈希值。该算法是 Django 可以使用的单向哈希或密码存储算法中的一种；见下文。迭代描述了算法在哈希上运行的次数。Salt 是所使用的随机种子，哈希是单向函数的结果。</p>\n<p>默认情况下，Django 使用带有 SHA256 哈希的 <a class=\"reference external\" href=\"https://en.wikipedia.org/wiki/PBKDF2\">PBKDF2</a> 算法，它是 NIST 推荐的密码延展机制。它足够安全，需要大量的运算时间才能破解，这对大部分用户来说足够了。</p>\n<p>但是，根据你的需求，你可以选择不同的算法，甚至使用自定义的算法来匹配特定的安全场景。再次强调，大部分用户没必要这么做，如果你不确定的话，很可能并不需要。如果你坚持要做，请继续阅读：</p>\n<p>Django chooses the algorithm to use by consulting the\n<a class=\"reference internal\" href=\"/zh-hans/4.1/ref/settings/#std-setting-PASSWORD_HASHERS\"><code class=\"xref std std-setting docutils literal notranslate\">PASSWORD_HASHERS</code></a> setting. This is a list of hashing algorithm\nclasses that this Django installation supports.</p>\n<p>For storing passwords, Django will use the first hasher in\n<a class=\"reference internal\" href=\"/zh-hans/4.1/ref/settings/#std-setting-PASSWORD_HASHERS\"><code class=\"xref std std-setting docutils literal notranslate\">PASSWORD_HASHERS</code></a>. To store new passwords with a different algorithm,\nput your preferred algorithm first in <a class=\"reference internal\" href=\"/zh-hans/4.1/ref/settings/#std-setting-PASSWORD_HASHERS\"><code class=\"xref std std-setting docutils literal notranslate\">PASSWORD_HASHERS</code></a>.</p>\n<p>For verifying passwords, Django will find the hasher in the list that matches\nthe algorithm name in the stored password. If a stored password names an\nalgorithm not found in <a class=\"reference internal\" href=\"/zh-hans/4.1/ref/settings/#std-setting-PASSWORD_HASHERS\"><code class=\"xref std std-setting docutils literal notranslate\">PASSWORD_HASHERS</code></a>, trying to verify it will\nraise <code class=\"docutils literal notranslate\">ValueError</code>.</p>\n<p><a class=\"reference internal\" href=\"/zh-hans/4.1/ref/settings/#std-setting-PASSWORD_HASHERS\"><code class=\"xref std std-setting docutils literal notranslate\">PASSWORD_HASHERS</code></a> 的默认值是：</p>\n<div class=\"code-block\" data-language=\"default\"><div class=\"code-block-toolbar\"><span class=\"code-block-language\">Code</span><button type=\"button\" class=\"copy-button\" data-copy hidden><span class=\"copy-button-label\">Copy</span></button></div><pre role=\"group\" tabindex=\"0\" aria-label=\"Code code\"><code>PASSWORD_HASHERS <span class=\"o\">=</span> <span class=\"p\">[</span>\n    <span class=\"s1\">&#39;django.contrib.auth.hashers.PBKDF2PasswordHasher&#39;</span><span class=\"p\">,</span>\n    <span class=\"s1\">&#39;django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher&#39;</span><span class=\"p\">,</span>\n    <span class=\"s1\">&#39;django.contrib.auth.hashers.Argon2PasswordHasher&#39;</span><span class=\"p\">,</span>\n    <span class=\"s1\">&#39;django.contrib.auth.hashers.BCryptSHA256PasswordHasher&#39;</span><span class=\"p\">,</span>\n    <span class=\"s1\">&#39;django.contrib.auth.hashers.ScryptPasswordHasher&#39;</span><span class=\"p\">,</span>\n<span class=\"p\">]</span>\n</code></pre></div>\n<p>这意味着 Django 除了使用 PBKDF2 来存储所有密码，也支持使用 PBKDF2SHA1 、argon2 和 bcrypt 来检测已存储的密码。</p>\n<p>接下来的部分描述了高级用户修改这个配置的几个常见方法。</p>\n<section id=\"using-argon2-with-django\">\n<span id=\"argon2-usage\"></span><h3>在 Django 中使用 Argon2<a class=\"heading-anchor\" href=\"#using-argon2-with-django\"><span class=\"visually-hidden\">Link to this heading</span><span aria-hidden=\"true\">#</span></a></h3>\n<p><a class=\"reference external\" href=\"https://en.wikipedia.org/wiki/Argon2\">Argon2</a> is the winner of the 2015 <a class=\"reference external\" href=\"https://www.password-hashing.net/\">Password Hashing Competition</a>, a community\norganized open competition to select a next generation hashing algorithm. It's\ndesigned not to be easier to compute on custom hardware than it is to compute\non an ordinary CPU. The default variant for the Argon2 password hasher is\nArgon2id.</p>\n<p>Argon2 并不是 Django 的默认首选，因为它依赖第三方库。尽管哈希密码竞赛主办方建议立即使用 Argon2 ，而不是 Django 提供的其他算法。</p>\n<p>To use Argon2id as your default storage algorithm, do the following:</p>\n<ol class=\"arabic\">\n<li><p>安装 <a class=\"reference external\" href=\"https://pypi.org/project/argon2-cffi/\">argon2-cffi library</a> 库，可通过  <code class=\"docutils literal notranslate\">python -m pip install django[argon2]</code> 安装，相当于 <code class=\"docutils literal notranslate\">python -m pip install argon2-cffi</code> （以及 Django 的 <code class=\"docutils literal notranslate\">setup.cfg</code> 的任何版本要求）。</p></li>\n<li><p>修改 <a class=\"reference internal\" href=\"/zh-hans/4.1/ref/settings/#std-setting-PASSWORD_HASHERS\"><code class=\"xref std std-setting docutils literal notranslate\">PASSWORD_HASHERS</code></a> 配置，把 <code class=\"docutils literal notranslate\">Argon2PasswordHasher</code> 放在首位。如下：</p>\n<div class=\"code-block\" data-language=\"default\"><div class=\"code-block-toolbar\"><span class=\"code-block-language\">Code</span><button type=\"button\" class=\"copy-button\" data-copy hidden><span class=\"copy-button-label\">Copy</span></button></div><pre role=\"group\" tabindex=\"0\" aria-label=\"Code code\"><code>PASSWORD_HASHERS <span class=\"o\">=</span> <span class=\"p\">[</span>\n    <span class=\"s1\">&#39;django.contrib.auth.hashers.Argon2PasswordHasher&#39;</span><span class=\"p\">,</span>\n    <span class=\"s1\">&#39;django.contrib.auth.hashers.PBKDF2PasswordHasher&#39;</span><span class=\"p\">,</span>\n    <span class=\"s1\">&#39;django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher&#39;</span><span class=\"p\">,</span>\n    <span class=\"s1\">&#39;django.contrib.auth.hashers.BCryptSHA256PasswordHasher&#39;</span><span class=\"p\">,</span>\n    <span class=\"s1\">&#39;django.contrib.auth.hashers.ScryptPasswordHasher&#39;</span><span class=\"p\">,</span>\n<span class=\"p\">]</span>\n</code></pre></div>\n<p>如果你需要 Django 升级密码（ <a class=\"reference internal\" href=\"#password-upgrades\"><span class=\"std std-ref\">upgrade passwords</span></a> ），请保留或添加这个列表中的任何条目。</p>\n</li>\n</ol>\n</section>\n<section id=\"using-bcrypt-with-django\">\n<span id=\"bcrypt-usage\"></span><h3>在 Django 中使用 <code class=\"docutils literal notranslate\">bcrypt</code><a class=\"heading-anchor\" href=\"#using-bcrypt-with-django\"><span class=\"visually-hidden\">Link to this heading</span><span aria-hidden=\"true\">#</span></a></h3>\n<p>Bcrypt 是一个非常流行的密码存储算法，尤其是为长期密码存储设计。Django 默认不使用它，因为它需要使用第三方库，但由于很多人想使用它，Django 只需要很少的努力就能支持 bcrypt 。</p>\n<p>使用 Bcrypt 作为你的默认存储算法，需要以下步骤：</p>\n<ol class=\"arabic\">\n<li><p>安装 <a class=\"reference external\" href=\"https://pypi.org/project/bcrypt/\">bcrypt library</a> 库。通过 <code class=\"docutils literal notranslate\">python -m pip install django[bcrypt]</code> 安装，相当于 <code class=\"docutils literal notranslate\">python -m pip install bcrypt</code> （以及 Django 的 <code class=\"docutils literal notranslate\">setup.cfg</code> 的任何版本要求）。</p></li>\n<li><p>修改 <a class=\"reference internal\" href=\"/zh-hans/4.1/ref/settings/#std-setting-PASSWORD_HASHERS\"><code class=\"xref std std-setting docutils literal notranslate\">PASSWORD_HASHERS</code></a> 配置，把 <code class=\"docutils literal notranslate\">BCryptSHA256PasswordHasher</code> 放在首位。如下：</p>\n<div class=\"code-block\" data-language=\"default\"><div class=\"code-block-toolbar\"><span class=\"code-block-language\">Code</span><button type=\"button\" class=\"copy-button\" data-copy hidden><span class=\"copy-button-label\">Copy</span></button></div><pre role=\"group\" tabindex=\"0\" aria-label=\"Code code\"><code>PASSWORD_HASHERS <span class=\"o\">=</span> <span class=\"p\">[</span>\n    <span class=\"s1\">&#39;django.contrib.auth.hashers.BCryptSHA256PasswordHasher&#39;</span><span class=\"p\">,</span>\n    <span class=\"s1\">&#39;django.contrib.auth.hashers.PBKDF2PasswordHasher&#39;</span><span class=\"p\">,</span>\n    <span class=\"s1\">&#39;django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher&#39;</span><span class=\"p\">,</span>\n    <span class=\"s1\">&#39;django.contrib.auth.hashers.Argon2PasswordHasher&#39;</span><span class=\"p\">,</span>\n    <span class=\"s1\">&#39;django.contrib.auth.hashers.ScryptPasswordHasher&#39;</span><span class=\"p\">,</span>\n<span class=\"p\">]</span>\n</code></pre></div>\n<p>如果你需要 Django 升级密码（ <a class=\"reference internal\" href=\"#password-upgrades\"><span class=\"std std-ref\">upgrade passwords</span></a> ），请保留或添加这个列表中的任何条目。</p>\n</li>\n</ol>\n<p>现在 Django 将使用 Bcrypt 作为默认存储算法。</p>\n</section>\n<section id=\"using-scrypt-with-django\">\n<span id=\"scrypt-usage\"></span><h3>在 Django 中使用 <code class=\"docutils literal notranslate\">scrypt</code><a class=\"heading-anchor\" href=\"#using-scrypt-with-django\"><span class=\"visually-hidden\">Link to this heading</span><span aria-hidden=\"true\">#</span></a></h3>\n<aside class=\"version-note version-added\" data-version=\"4.0\" role=\"note\">\n<p class=\"version-note-title\">New in Django 4.0</p></aside>\n<p><a class=\"reference external\" href=\"https://en.wikipedia.org/wiki/Scrypt\">scrypt</a> is similar to PBKDF2 and bcrypt in utilizing a set number of iterations\nto slow down brute-force attacks. However, because PBKDF2 and bcrypt do not\nrequire a lot of memory, attackers with sufficient resources can launch\nlarge-scale parallel attacks in order to speed up the attacking process.\n<a class=\"reference external\" href=\"https://en.wikipedia.org/wiki/Scrypt\">scrypt</a> is specifically designed to use more memory compared to other\npassword-based key derivation functions in order to limit the amount of\nparallelism an attacker can use, see <span class=\"target\" id=\"index-0\"></span><a class=\"rfc reference external\" href=\"https://datatracker.ietf.org/doc/html/rfc7914.html\"><strong>RFC 7914</strong></a> for more details.</p>\n<p>使用 <a class=\"reference external\" href=\"https://en.wikipedia.org/wiki/Scrypt\">scrypt</a> 作为你的默认存储算法，需要以下步骤：</p>\n<ol class=\"arabic\">\n<li><p>Modify <a class=\"reference internal\" href=\"/zh-hans/4.1/ref/settings/#std-setting-PASSWORD_HASHERS\"><code class=\"xref std std-setting docutils literal notranslate\">PASSWORD_HASHERS</code></a> to list <code class=\"docutils literal notranslate\">ScryptPasswordHasher</code> first.\nThat is, in your settings file:</p>\n<div class=\"code-block\" data-language=\"default\"><div class=\"code-block-toolbar\"><span class=\"code-block-language\">Code</span><button type=\"button\" class=\"copy-button\" data-copy hidden><span class=\"copy-button-label\">Copy</span></button></div><pre role=\"group\" tabindex=\"0\" aria-label=\"Code code\"><code>PASSWORD_HASHERS <span class=\"o\">=</span> <span class=\"p\">[</span>\n    <span class=\"s1\">&#39;django.contrib.auth.hashers.ScryptPasswordHasher&#39;</span><span class=\"p\">,</span>\n    <span class=\"s1\">&#39;django.contrib.auth.hashers.PBKDF2PasswordHasher&#39;</span><span class=\"p\">,</span>\n    <span class=\"s1\">&#39;django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher&#39;</span><span class=\"p\">,</span>\n    <span class=\"s1\">&#39;django.contrib.auth.hashers.Argon2PasswordHasher&#39;</span><span class=\"p\">,</span>\n    <span class=\"s1\">&#39;django.contrib.auth.hashers.BCryptSHA256PasswordHasher&#39;</span><span class=\"p\">,</span>\n<span class=\"p\">]</span>\n</code></pre></div>\n<p>如果你需要 Django 升级密码（ <a class=\"reference internal\" href=\"#password-upgrades\"><span class=\"std std-ref\">upgrade passwords</span></a> ），请保留或添加这个列表中的任何条目。</p>\n</li>\n</ol>\n<aside class=\"admonition admonition-note\" role=\"note\">\n<p class=\"admonition-title\">Note</p>\n<p><code class=\"docutils literal notranslate\">scrypt</code> 需要 OpenSSL 1.1及更高版本。</p>\n</aside>\n</section>\n<section id=\"increasing-the-salt-entropy\">\n<h3>增加盐的熵值<a class=\"heading-anchor\" href=\"#increasing-the-salt-entropy\"><span class=\"visually-hidden\">Link to this heading</span><span aria-hidden=\"true\">#</span></a></h3>\n<p>大多数密码哈希值包括一个盐，与他们的密码哈希值一起，以防止彩虹表攻击。盐本身是一个随机值，它增加了彩虹表的大小和成本，目前在 <code class=\"docutils literal notranslate\">BasePasswordHasher</code> 中的 <code class=\"docutils literal notranslate\">salt_entropy</code> 值设置为 128 比特。随着计算和存储成本的降低，这个值应该被提高。当实现你自己的密码散列器时，你可以自由地覆盖这个值，以便为你的密码散列器使用一个理想的熵值。<code class=\"docutils literal notranslate\">salt_entropy</code> 是以比特为单位。</p>\n<aside class=\"admonition-implementation-detail admonition\" role=\"note\">\n<p class=\"admonition-title\">实现细节</p>\n<p>由于盐值的存储方法，<code class=\"docutils literal notranslate\">salt_entropy</code> 值实际上是一个最小值。例如，一个 128 的值将提供一个实际包含 131 位熵的盐。</p>\n</aside>\n</section>\n<section id=\"increasing-the-work-factor\">\n<span id=\"increasing-password-algorithm-work-factor\"></span><h3>增加工作因子<a class=\"heading-anchor\" href=\"#increasing-the-work-factor\"><span class=\"visually-hidden\">Link to this heading</span><span aria-hidden=\"true\">#</span></a></h3>\n<section id=\"pbkdf2-and-bcrypt\">\n<h4>PBKDF2 和 bcrypt<a class=\"heading-anchor\" href=\"#pbkdf2-and-bcrypt\"><span class=\"visually-hidden\">Link to this heading</span><span aria-hidden=\"true\">#</span></a></h4>\n<p>The PBKDF2 and bcrypt algorithms use a number of iterations or rounds of\nhashing. This deliberately slows down attackers, making attacks against hashed\npasswords harder. However, as computing power increases, the number of\niterations needs to be increased. We've chosen a reasonable default (and will\nincrease it with each release of Django), but you may wish to tune it up or\ndown, depending on your security needs and available processing power. To do so,\nyou'll subclass the appropriate algorithm and override the <code class=\"docutils literal notranslate\">iterations</code>\nparameter (use the <code class=\"docutils literal notranslate\">rounds</code> parameter when subclassing a bcrypt hasher). For\nexample, to increase the number of iterations used by the default PBKDF2\nalgorithm:</p>\n<ol class=\"arabic\">\n<li><p>创建 <code class=\"docutils literal notranslate\">django.contrib.auth.hashers.PBKDF2PasswordHasher</code> 的子类：</p>\n<div class=\"code-block\" data-language=\"default\"><div class=\"code-block-toolbar\"><span class=\"code-block-language\">Code</span><button type=\"button\" class=\"copy-button\" data-copy hidden><span class=\"copy-button-label\">Copy</span></button></div><pre role=\"group\" tabindex=\"0\" aria-label=\"Code code\"><code><span class=\"kn\">from</span> <span class=\"nn\">django.contrib.auth.hashers</span> <span class=\"kn\">import</span> PBKDF2PasswordHasher\n\n<span class=\"k\">class</span> <span class=\"nc\">MyPBKDF2PasswordHasher</span><span class=\"p\">(</span>PBKDF2PasswordHasher<span class=\"p\">):</span>\n    <span class=\"sd\">&quot;&quot;&quot;</span>\n<span class=\"sd\">    A subclass of PBKDF2PasswordHasher that uses 100 times more iterations.</span>\n<span class=\"sd\">    &quot;&quot;&quot;</span>\n    iterations <span class=\"o\">=</span> PBKDF2PasswordHasher<span class=\"o\">.</span>iterations <span class=\"o\">*</span> <span class=\"mi\">100</span>\n</code></pre></div>\n<p>在你的项目某些位置中保存。比如，你可以放在类似 <code class=\"docutils literal notranslate\">myproject/hashers.py</code> 里。</p>\n</li>\n<li><p>在 <a class=\"reference internal\" href=\"/zh-hans/4.1/ref/settings/#std-setting-PASSWORD_HASHERS\"><code class=\"xref std std-setting docutils literal notranslate\">PASSWORD_HASHERS</code></a> 中把新哈希放在首位：</p>\n<div class=\"code-block\" data-language=\"default\"><div class=\"code-block-toolbar\"><span class=\"code-block-language\">Code</span><button type=\"button\" class=\"copy-button\" data-copy hidden><span class=\"copy-button-label\">Copy</span></button></div><pre role=\"group\" tabindex=\"0\" aria-label=\"Code code\"><code>PASSWORD_HASHERS <span class=\"o\">=</span> <span class=\"p\">[</span>\n    <span class=\"s1\">&#39;myproject.hashers.MyPBKDF2PasswordHasher&#39;</span><span class=\"p\">,</span>\n    <span class=\"s1\">&#39;django.contrib.auth.hashers.PBKDF2PasswordHasher&#39;</span><span class=\"p\">,</span>\n    <span class=\"s1\">&#39;django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher&#39;</span><span class=\"p\">,</span>\n    <span class=\"s1\">&#39;django.contrib.auth.hashers.Argon2PasswordHasher&#39;</span><span class=\"p\">,</span>\n    <span class=\"s1\">&#39;django.contrib.auth.hashers.BCryptSHA256PasswordHasher&#39;</span><span class=\"p\">,</span>\n    <span class=\"s1\">&#39;django.contrib.auth.hashers.ScryptPasswordHasher&#39;</span><span class=\"p\">,</span>\n<span class=\"p\">]</span>\n</code></pre></div>\n</li>\n</ol>\n<p>现在 Django 使用 PBKDF2 存储密码时将会多次迭代。</p>\n<aside class=\"admonition admonition-note\" role=\"note\">\n<p class=\"admonition-title\">Note</p>\n<p>bcrypt <code class=\"docutils literal notranslate\">rounds</code> is a logarithmic work factor, e.g. 12 rounds means\n<code class=\"docutils literal notranslate\">2 ** 12</code> iterations.</p>\n</aside>\n</section>\n<section id=\"argon2\">\n<h4>Argon2<a class=\"heading-anchor\" href=\"#argon2\"><span class=\"visually-hidden\">Link to this heading</span><span aria-hidden=\"true\">#</span></a></h4>\n<p>Argon2 has the following attributes that can be customized:</p>\n<ol class=\"arabic simple\">\n<li><p><code class=\"docutils literal notranslate\">time_cost</code> 控制哈希的次数。</p></li>\n<li><p><code class=\"docutils literal notranslate\">memory_cost</code> 控制被用来计算哈希时的内存大小。</p></li>\n<li><p><code class=\"docutils literal notranslate\">parallelism</code> 控制并行计算哈希的 CPU 数量。</p></li>\n</ol>\n<p>这三个属性的默认值足够适合你。如果你确定密码哈希过快或过慢，可以按如下方式调整它：</p>\n<ol class=\"arabic simple\">\n<li><p>选择 <code class=\"docutils literal notranslate\">parallelism</code> 你可以节省计算哈希的线程数。</p></li>\n<li><p>选择 <code class=\"docutils literal notranslate\">memory_cost</code> 你可以节省内存的 KiB 。</p></li>\n<li><p>调整 <code class=\"docutils literal notranslate\">time_cost</code> 和估计哈希一个密码所需的时间。挑选出你可以接受的 <code class=\"docutils literal notranslate\">time_cost</code> 。如果设置为1的  <code class=\"docutils literal notranslate\">time_cost</code> 慢的无法接受，则调低 <code class=\"docutils literal notranslate\">memory_cost</code> 。</p></li>\n</ol>\n<aside class=\"admonition-memory-cost-interpretation admonition\" role=\"note\">\n<p class=\"admonition-title\"><code class=\"docutils literal notranslate\">memory_cost</code> 说明</p>\n<p>argon2 命令行工具和一些其他的库解释了 <code class=\"docutils literal notranslate\">memory_cost</code> 参数不同于 Django 使用的值。换算公式是``memory_cost == 2 ** memory_cost_commandline`` 。</p>\n</aside>\n</section>\n<section id=\"scrypt\">\n<h4><code class=\"docutils literal notranslate\">scrypt</code><a class=\"heading-anchor\" href=\"#scrypt\"><span class=\"visually-hidden\">Link to this heading</span><span aria-hidden=\"true\">#</span></a></h4>\n<aside class=\"version-note version-added\" data-version=\"4.0\" role=\"note\">\n<p class=\"version-note-title\">New in Django 4.0</p></aside>\n<p><a class=\"reference external\" href=\"https://en.wikipedia.org/wiki/Scrypt\">scrypt</a> has the following attributes that can be customized:</p>\n<ol class=\"arabic simple\">\n<li><p><code class=\"docutils literal notranslate\">work_factor</code> 控制哈希的次数。</p></li>\n<li><p><code class=\"docutils literal notranslate\">block_size</code></p></li>\n<li><p><code class=\"docutils literal notranslate\">parallelism</code> controls how many threads will run in parallel.</p></li>\n<li><p><code class=\"docutils literal notranslate\">maxmem</code> limits the maximum size of memory that can be used during the\ncomputation of the hash. Defaults to <code class=\"docutils literal notranslate\">0</code>, which means the default\nlimitation from the OpenSSL library.</p></li>\n</ol>\n<p>We've chosen reasonable defaults, but you may wish to tune it up or down,\ndepending on your security needs and available processing power.</p>\n<aside class=\"admonition-estimating-memory-usage admonition\" role=\"note\">\n<p class=\"admonition-title\">Estimating memory usage</p>\n<p>scrypt_的最低内存需求是:</p>\n<div class=\"code-block\" data-language=\"default\"><div class=\"code-block-toolbar\"><span class=\"code-block-language\">Code</span><button type=\"button\" class=\"copy-button\" data-copy hidden><span class=\"copy-button-label\">Copy</span></button></div><pre role=\"group\" tabindex=\"0\" aria-label=\"Code code\"><code>work_factor <span class=\"o\">*</span> <span class=\"mi\">2</span> <span class=\"o\">*</span> block_size <span class=\"o\">*</span> <span class=\"mi\">64</span>\n</code></pre></div>\n<p>so you may need to tweak <code class=\"docutils literal notranslate\">maxmem</code> when changing the <code class=\"docutils literal notranslate\">work_factor</code> or\n<code class=\"docutils literal notranslate\">block_size</code> values.</p>\n</aside>\n</section>\n</section>\n<section id=\"password-upgrading\">\n<span id=\"password-upgrades\"></span><h3>密码升级<a class=\"heading-anchor\" href=\"#password-upgrading\"><span class=\"visually-hidden\">Link to this heading</span><span aria-hidden=\"true\">#</span></a></h3>\n<p>当用户登录时，如果用户的密码使用首选算法以外的算法保存，Django 会自动升级这个算法成为首选算法。这意味着旧的 Django 安装会在用户登录时自动得到更多的安全，并且当它们创建时你可以切换到新的更好的存储算法。</p>\n<p>然而，Django 只会使用 <a class=\"reference internal\" href=\"/zh-hans/4.1/ref/settings/#std-setting-PASSWORD_HASHERS\"><code class=\"xref std std-setting docutils literal notranslate\">PASSWORD_HASHERS</code></a> 提到的算法升级密码，因此当你升级到新系统时你要确保你从没有删除过这个列表的条目。如果你删除过，那么使用的没有列出的算法的用户将不会升级。当增加（或减少） PBKDF2 迭代的次数、bcrypt 的轮次或者 argon2 属性，哈希过的密码将被更新。</p>\n<p>注意，如果数据库内的所有密码没有在默认哈希算法里编码，则由于非默认算法的密码编码的用户登录请求持续时间和不存在用户（运行过默认哈希）的登录请求持续时间的不同，你可能会受到用户枚举时间攻击。你可以使用升级旧密码的哈希值来缓解此问题。</p>\n</section>\n<section id=\"password-upgrading-without-requiring-a-login\">\n<span id=\"wrapping-password-hashers\"></span><h3>无需登录的密码升级<a class=\"heading-anchor\" href=\"#password-upgrading-without-requiring-a-login\"><span class=\"visually-hidden\">Link to this heading</span><span aria-hidden=\"true\">#</span></a></h3>\n<p>如果数据库拥有老旧低效的哈希算法，比如 MD5 或 SHA1，那么你也许希望自己升级哈希而不是等待用户登录后升级（如果一个用户不再登录站点，密码就不会升级了）。在这里，你可以包装一下密码哈希。</p>\n<p>比如，你想迁移一个 SHA1 哈希集合来使用 PBKDF2(SHA1(password)) ，并添加相应的密码哈希来检查用户在登陆时是否输入了正确的密码。我们假设我们正在使用内建的 <code class=\"docutils literal notranslate\">User</code> 模型，并有一个 <code class=\"docutils literal notranslate\">accounts</code> app 。你可以修改模式以使用任何算法或自定义的模型。</p>\n<p>首先，我们添加一个自定义的哈希：</p>\n<figure class=\"code-block code-block-captioned\" data-language=\"python\"><figcaption class=\"code-block-caption\"><code class=\"docutils literal notranslate\">accounts/hashers.py</code></figcaption>\n<div class=\"code-block-toolbar\"><span class=\"code-block-language\">Python</span><button type=\"button\" class=\"copy-button\" data-copy hidden><span class=\"copy-button-label\">Copy</span></button></div><pre role=\"group\" tabindex=\"0\" aria-label=\"Python code\"><code><span class=\"kn\">from</span> <span class=\"nn\">django.contrib.auth.hashers</span> <span class=\"kn\">import</span> <span class=\"p\">(</span>\n    PBKDF2PasswordHasher<span class=\"p\">,</span> SHA1PasswordHasher<span class=\"p\">,</span>\n<span class=\"p\">)</span>\n\n\n<span class=\"k\">class</span> <span class=\"nc\">PBKDF2WrappedSHA1PasswordHasher</span><span class=\"p\">(</span>PBKDF2PasswordHasher<span class=\"p\">):</span>\n    algorithm <span class=\"o\">=</span> <span class=\"s1\">&#39;pbkdf2_wrapped_sha1&#39;</span>\n\n    <span class=\"k\">def</span> <span class=\"nf\">encode_sha1_hash</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> sha1_hash<span class=\"p\">,</span> salt<span class=\"p\">,</span> iterations<span class=\"o\">=</span><span class=\"kc\">None</span><span class=\"p\">):</span>\n        <span class=\"k\">return</span> <span class=\"nb\">super</span><span class=\"p\">()</span><span class=\"o\">.</span>encode<span class=\"p\">(</span>sha1_hash<span class=\"p\">,</span> salt<span class=\"p\">,</span> iterations<span class=\"p\">)</span>\n\n    <span class=\"k\">def</span> <span class=\"nf\">encode</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> password<span class=\"p\">,</span> salt<span class=\"p\">,</span> iterations<span class=\"o\">=</span><span class=\"kc\">None</span><span class=\"p\">):</span>\n        _<span class=\"p\">,</span> _<span class=\"p\">,</span> sha1_hash <span class=\"o\">=</span> SHA1PasswordHasher<span class=\"p\">()</span><span class=\"o\">.</span>encode<span class=\"p\">(</span>password<span class=\"p\">,</span> salt<span class=\"p\">)</span><span class=\"o\">.</span>split<span class=\"p\">(</span><span class=\"s1\">&#39;$&#39;</span><span class=\"p\">,</span> <span class=\"mi\">2</span><span class=\"p\">)</span>\n        <span class=\"k\">return</span> <span class=\"bp\">self</span><span class=\"o\">.</span>encode_sha1_hash<span class=\"p\">(</span>sha1_hash<span class=\"p\">,</span> salt<span class=\"p\">,</span> iterations<span class=\"p\">)</span>\n</code></pre></figure>\n<p>数据迁移可能类似于这样：</p>\n<figure class=\"code-block code-block-captioned\" data-language=\"python\"><figcaption class=\"code-block-caption\"><code class=\"docutils literal notranslate\">accounts/migrations/0002_migrate_sha1_passwords.py</code></figcaption>\n<div class=\"code-block-toolbar\"><span class=\"code-block-language\">Python</span><button type=\"button\" class=\"copy-button\" data-copy hidden><span class=\"copy-button-label\">Copy</span></button></div><pre role=\"group\" tabindex=\"0\" aria-label=\"Python code\"><code><span class=\"kn\">from</span> <span class=\"nn\">django.db</span> <span class=\"kn\">import</span> migrations\n\n<span class=\"kn\">from</span> <span class=\"nn\">..hashers</span> <span class=\"kn\">import</span> PBKDF2WrappedSHA1PasswordHasher\n\n\n<span class=\"k\">def</span> <span class=\"nf\">forwards_func</span><span class=\"p\">(</span>apps<span class=\"p\">,</span> schema_editor<span class=\"p\">):</span>\n    User <span class=\"o\">=</span> apps<span class=\"o\">.</span>get_model<span class=\"p\">(</span><span class=\"s1\">&#39;auth&#39;</span><span class=\"p\">,</span> <span class=\"s1\">&#39;User&#39;</span><span class=\"p\">)</span>\n    users <span class=\"o\">=</span> User<span class=\"o\">.</span>objects<span class=\"o\">.</span>filter<span class=\"p\">(</span>password__startswith<span class=\"o\">=</span><span class=\"s1\">&#39;sha1$&#39;</span><span class=\"p\">)</span>\n    hasher <span class=\"o\">=</span> PBKDF2WrappedSHA1PasswordHasher<span class=\"p\">()</span>\n    <span class=\"k\">for</span> user <span class=\"ow\">in</span> users<span class=\"p\">:</span>\n        algorithm<span class=\"p\">,</span> salt<span class=\"p\">,</span> sha1_hash <span class=\"o\">=</span> user<span class=\"o\">.</span>password<span class=\"o\">.</span>split<span class=\"p\">(</span><span class=\"s1\">&#39;$&#39;</span><span class=\"p\">,</span> <span class=\"mi\">2</span><span class=\"p\">)</span>\n        user<span class=\"o\">.</span>password <span class=\"o\">=</span> hasher<span class=\"o\">.</span>encode_sha1_hash<span class=\"p\">(</span>sha1_hash<span class=\"p\">,</span> salt<span class=\"p\">)</span>\n        user<span class=\"o\">.</span>save<span class=\"p\">(</span>update_fields<span class=\"o\">=</span><span class=\"p\">[</span><span class=\"s1\">&#39;password&#39;</span><span class=\"p\">])</span>\n\n\n<span class=\"k\">class</span> <span class=\"nc\">Migration</span><span class=\"p\">(</span>migrations<span class=\"o\">.</span>Migration<span class=\"p\">):</span>\n\n    dependencies <span class=\"o\">=</span> <span class=\"p\">[</span>\n        <span class=\"p\">(</span><span class=\"s1\">&#39;accounts&#39;</span><span class=\"p\">,</span> <span class=\"s1\">&#39;0001_initial&#39;</span><span class=\"p\">),</span>\n        <span class=\"c1\"># replace this with the latest migration in contrib.auth</span>\n        <span class=\"p\">(</span><span class=\"s1\">&#39;auth&#39;</span><span class=\"p\">,</span> <span class=\"s1\">&#39;####_migration_name&#39;</span><span class=\"p\">),</span>\n    <span class=\"p\">]</span>\n\n    operations <span class=\"o\">=</span> <span class=\"p\">[</span>\n        migrations<span class=\"o\">.</span>RunPython<span class=\"p\">(</span>forwards_func<span class=\"p\">),</span>\n    <span class=\"p\">]</span>\n</code></pre></figure>\n<p>注意，迁移将对上千名用户花费大约数十分钟，这取决于你的硬件速度。</p>\n<p>最后，我们在 <a class=\"reference internal\" href=\"/zh-hans/4.1/ref/settings/#std-setting-PASSWORD_HASHERS\"><code class=\"xref std std-setting docutils literal notranslate\">PASSWORD_HASHERS</code></a> 中添加配置：</p>\n<figure class=\"code-block code-block-captioned\" data-language=\"python\"><figcaption class=\"code-block-caption\"><code class=\"docutils literal notranslate\">mysite/settings.py</code></figcaption>\n<div class=\"code-block-toolbar\"><span class=\"code-block-language\">Python</span><button type=\"button\" class=\"copy-button\" data-copy hidden><span class=\"copy-button-label\">Copy</span></button></div><pre role=\"group\" tabindex=\"0\" aria-label=\"Python code\"><code>PASSWORD_HASHERS <span class=\"o\">=</span> <span class=\"p\">[</span>\n    <span class=\"s1\">&#39;django.contrib.auth.hashers.PBKDF2PasswordHasher&#39;</span><span class=\"p\">,</span>\n    <span class=\"s1\">&#39;accounts.hashers.PBKDF2WrappedSHA1PasswordHasher&#39;</span><span class=\"p\">,</span>\n<span class=\"p\">]</span>\n</code></pre></figure>\n<p>包含你的站点使用的此列表中的其他算法。</p>\n</section>\n<section id=\"included-hashers\">\n<span id=\"auth-included-hashers\"></span><h3>已包含的哈希<a class=\"heading-anchor\" href=\"#included-hashers\"><span class=\"visually-hidden\">Link to this heading</span><span aria-hidden=\"true\">#</span></a></h3>\n<p>在 Django 中的所有列出的哈希是：</p>\n<div class=\"code-block\" data-language=\"default\"><div class=\"code-block-toolbar\"><span class=\"code-block-language\">Code</span><button type=\"button\" class=\"copy-button\" data-copy hidden><span class=\"copy-button-label\">Copy</span></button></div><pre role=\"group\" tabindex=\"0\" aria-label=\"Code code\"><code><span class=\"p\">[</span>\n    <span class=\"s1\">&#39;django.contrib.auth.hashers.PBKDF2PasswordHasher&#39;</span><span class=\"p\">,</span>\n    <span class=\"s1\">&#39;django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher&#39;</span><span class=\"p\">,</span>\n    <span class=\"s1\">&#39;django.contrib.auth.hashers.Argon2PasswordHasher&#39;</span><span class=\"p\">,</span>\n    <span class=\"s1\">&#39;django.contrib.auth.hashers.BCryptSHA256PasswordHasher&#39;</span><span class=\"p\">,</span>\n    <span class=\"s1\">&#39;django.contrib.auth.hashers.BCryptPasswordHasher&#39;</span><span class=\"p\">,</span>\n    <span class=\"s1\">&#39;django.contrib.auth.hashers.ScryptPasswordHasher&#39;</span><span class=\"p\">,</span>\n    <span class=\"s1\">&#39;django.contrib.auth.hashers.SHA1PasswordHasher&#39;</span><span class=\"p\">,</span>\n    <span class=\"s1\">&#39;django.contrib.auth.hashers.MD5PasswordHasher&#39;</span><span class=\"p\">,</span>\n    <span class=\"s1\">&#39;django.contrib.auth.hashers.UnsaltedSHA1PasswordHasher&#39;</span><span class=\"p\">,</span>\n    <span class=\"s1\">&#39;django.contrib.auth.hashers.UnsaltedMD5PasswordHasher&#39;</span><span class=\"p\">,</span>\n<span class=\"p\">]</span>\n</code></pre></div>\n<p>相应的算法名是：</p>\n<ul class=\"simple\">\n<li><p><code class=\"docutils literal notranslate\">pbkdf2_sha256</code></p></li>\n<li><p><code class=\"docutils literal notranslate\">pbkdf2_sha1</code></p></li>\n<li><p><code class=\"docutils literal notranslate\">argon2</code></p></li>\n<li><p><code class=\"docutils literal notranslate\">bcrypt_sha256</code></p></li>\n<li><p><code class=\"docutils literal notranslate\">bcrypt</code></p></li>\n<li><p><code class=\"docutils literal notranslate\">scrypt</code></p></li>\n<li><p><code class=\"docutils literal notranslate\">sha1</code></p></li>\n<li><p><code class=\"docutils literal notranslate\">md5</code></p></li>\n<li><p><code class=\"docutils literal notranslate\">unsalted_sha1</code></p></li>\n<li><p><code class=\"docutils literal notranslate\">unsalted_md5</code></p></li>\n</ul>\n</section>\n<section id=\"writing-your-own-hasher\">\n<span id=\"write-your-own-password-hasher\"></span><h3>编写你自己的哈希<a class=\"heading-anchor\" href=\"#writing-your-own-hasher\"><span class=\"visually-hidden\">Link to this heading</span><span aria-hidden=\"true\">#</span></a></h3>\n<p>如果你编写自己的密码哈希包含工作因子，比如迭代数量。你应该实现一个 <code class=\"docutils literal notranslate\">harden_runtime(self, password, encoded)</code> 方法来消除编码密码时提供的工作因子和默认的哈希工作因子之间的运行时间差。这样可以防止用户枚举时间攻击，因为旧的迭代次数中对密码编码的用户与不存在的用户（运行默认哈希的默认迭代次数）在登录时存在差异。</p>\n<p>以 PDKDF2 为例，如果编码包含20000次迭代，并且默认哈希迭代是30000，那么该方法应该通过另外的10000次迭代的 PBKDF2 运行密码。</p>\n<p>如果你的哈希没有工作因子，可以将该方法实现为 no-op (pass) 。</p>\n</section>\n</section>\n<section id=\"module-django.contrib.auth.hashers\">\n<span id=\"manually-managing-a-user-s-password\"></span><h2>手动管理用户的密码<a class=\"heading-anchor\" href=\"#module-django.contrib.auth.hashers\"><span class=\"visually-hidden\">Link to this heading</span><span aria-hidden=\"true\">#</span></a></h2>\n<p>The <a class=\"reference internal\" href=\"#module-django.contrib.auth.hashers\" title=\"django.contrib.auth.hashers\"><code class=\"xref py py-mod docutils literal notranslate\">django.contrib.auth.hashers</code></a> module provides a set of functions\nto create and validate hashed passwords. You can use them independently\nfrom the <code class=\"docutils literal notranslate\">User</code> model.</p>\n<dl class=\"py function\">\n<dt class=\"sig sig-object py\" id=\"django.contrib.auth.hashers.check_password\">\n<span class=\"sig-name descname\">check_password</span><span class=\"sig-paren\">(</span><em class=\"sig-param\">password</em>, <em class=\"sig-param\">encoded</em>, <em class=\"sig-param\">setter<span class=\"o\">=</span><span class=\"default_value\">None</span></em>, <em class=\"sig-param\">preferred<span class=\"o\">=</span><span class=\"default_value\">'default'</span></em><span class=\"sig-paren\">)</span><a class=\"heading-anchor\" href=\"#django.contrib.auth.hashers.check_password\"><span class=\"visually-hidden\">Link to this definition</span><span aria-hidden=\"true\">#</span></a></dt>\n<dd><p>If you'd like to manually authenticate a user by comparing a plain-text\npassword to the hashed password in the database, use the convenience\nfunction <a class=\"reference internal\" href=\"#django.contrib.auth.hashers.check_password\" title=\"django.contrib.auth.hashers.check_password\"><code class=\"xref py py-func docutils literal notranslate\">check_password()</code></a>. It takes two mandatory arguments: the\nplain-text password to check, and the full value of a user's <code class=\"docutils literal notranslate\">password</code>\nfield in the database to check against. It returns <code class=\"docutils literal notranslate\">True</code> if they match,\n<code class=\"docutils literal notranslate\">False</code> otherwise. Optionally, you can pass a callable <code class=\"docutils literal notranslate\">setter</code> that\ntakes the password and will be called when you need to regenerate it. You\ncan also pass <code class=\"docutils literal notranslate\">preferred</code> to change a hashing algorithm if you don't want\nto use the default (first entry of <code class=\"docutils literal notranslate\">PASSWORD_HASHERS</code> setting). See\n<a class=\"reference internal\" href=\"#auth-included-hashers\"><span class=\"std std-ref\">已包含的哈希</span></a> for the algorithm name of each hasher.</p>\n</dd></dl>\n\n<dl class=\"py function\">\n<dt class=\"sig sig-object py\" id=\"django.contrib.auth.hashers.make_password\">\n<span class=\"sig-name descname\">make_password</span><span class=\"sig-paren\">(</span><em class=\"sig-param\">password</em>, <em class=\"sig-param\">salt<span class=\"o\">=</span><span class=\"default_value\">None</span></em>, <em class=\"sig-param\">hasher<span class=\"o\">=</span><span class=\"default_value\">'default'</span></em><span class=\"sig-paren\">)</span><a class=\"heading-anchor\" href=\"#django.contrib.auth.hashers.make_password\"><span class=\"visually-hidden\">Link to this definition</span><span aria-hidden=\"true\">#</span></a></dt>\n<dd><p>通过此应用的格式创建一个哈希密码。它需要一个必需的参数：纯文本密码（字符串或字节）。或者，如果你不想使用默认配置（ <code class=\"docutils literal notranslate\">PASSWORD_HASHERS</code> 配置的首个条目 ），那么可以提供 salt 和 使用的哈希算法。有关每个哈希的算法名，可查看 <a class=\"reference internal\" href=\"#auth-included-hashers\"><span class=\"std std-ref\">已包含的哈希</span></a> 。如果密码参数是 <code class=\"docutils literal notranslate\">None</code> ，将返回一个不可用的密码（永远不会被 <a class=\"reference internal\" href=\"#django.contrib.auth.hashers.check_password\" title=\"django.contrib.auth.hashers.check_password\"><code class=\"xref py py-func docutils literal notranslate\">check_password()</code></a> 通过的密码）。</p>\n</dd></dl>\n\n<dl class=\"py function\">\n<dt class=\"sig sig-object py\" id=\"django.contrib.auth.hashers.is_password_usable\">\n<span class=\"sig-name descname\">is_password_usable</span><span class=\"sig-paren\">(</span><em class=\"sig-param\">encoded_password</em><span class=\"sig-paren\">)</span><a class=\"heading-anchor\" href=\"#django.contrib.auth.hashers.is_password_usable\"><span class=\"visually-hidden\">Link to this definition</span><span aria-hidden=\"true\">#</span></a></dt>\n<dd><p>如果密码是 <a class=\"reference internal\" href=\"/zh-hans/4.1/ref/contrib/auth/#django.contrib.auth.models.User.set_unusable_password\" title=\"django.contrib.auth.models.User.set_unusable_password\"><code class=\"xref py py-meth docutils literal notranslate\">User.set_unusable_password()</code></a> 的结果，则返回 <code class=\"docutils literal notranslate\">False</code> 。</p>\n</dd></dl>\n\n</section>\n<section id=\"module-django.contrib.auth.password_validation\">\n<span id=\"id3\"></span><span id=\"password-validation\"></span><h2>密码验证<a class=\"heading-anchor\" href=\"#module-django.contrib.auth.password_validation\"><span class=\"visually-hidden\">Link to this heading</span><span aria-hidden=\"true\">#</span></a></h2>\n<p>用户经常会选择弱密码。为了缓解这个问题，Django 提供可插拔的密码验证。你可以同时配置多个密码验证。Django 已经包含了一些验证，但你也可以编写你自己的验证。</p>\n<p>每个密码验证器必须提供给用户提供帮助文案以向用户解释要求，验证密码并在不符合要求时返回错误信息，并且可选择接受已经设置过的密码。验证器也可以使用可选设置来微调它们的行为。</p>\n<p>验证由 <a class=\"reference internal\" href=\"/zh-hans/4.1/ref/settings/#std-setting-AUTH_PASSWORD_VALIDATORS\"><code class=\"xref std std-setting docutils literal notranslate\">AUTH_PASSWORD_VALIDATORS</code></a> 控制。默认的设置是一个空列表，这意味着默认是不验证的。在使用默认的 <a class=\"reference internal\" href=\"/zh-hans/4.1/ref/django-admin/#django-admin-startproject\"><code class=\"xref std std-djadmin docutils literal notranslate\">startproject</code></a> 创建的新项目中，默认启用了验证器集合。</p>\n<p>默认情况下，验证器在重置或修改密码的表单中使用，也可以在 <a class=\"reference internal\" href=\"/zh-hans/4.1/ref/django-admin/#django-admin-createsuperuser\"><code class=\"xref std std-djadmin docutils literal notranslate\">createsuperuser</code></a> 和 <a class=\"reference internal\" href=\"/zh-hans/4.1/ref/django-admin/#django-admin-changepassword\"><code class=\"xref std std-djadmin docutils literal notranslate\">changepassword</code></a> 命令中使用。验证器不能应用在模型层，比如 <code class=\"docutils literal notranslate\">User.objects.create_user()</code> 和 <code class=\"docutils literal notranslate\">create_superuser()</code> ，因为我们假设开发者（非用户）会在模型层与 Django 进行交互，也因为模型验证不会在创建模型时自动运行。</p>\n<aside class=\"admonition admonition-note\" role=\"note\">\n<p class=\"admonition-title\">Note</p>\n<p>密码验证器可以防止使用很多类型的弱密码。但是，密码通过所有的验证器并不能保证它就是强密码。这里有很多因素削弱即便最先进的密码验证程序也检测不到的密码。</p>\n</aside>\n<section id=\"enabling-password-validation\">\n<h3>启用密码验证<a class=\"heading-anchor\" href=\"#enabling-password-validation\"><span class=\"visually-hidden\">Link to this heading</span><span aria-hidden=\"true\">#</span></a></h3>\n<p>在 <a class=\"reference internal\" href=\"/zh-hans/4.1/ref/settings/#std-setting-AUTH_PASSWORD_VALIDATORS\"><code class=\"xref std std-setting docutils literal notranslate\">AUTH_PASSWORD_VALIDATORS</code></a> 中设置密码验证：</p>\n<div class=\"code-block\" data-language=\"default\"><div class=\"code-block-toolbar\"><span class=\"code-block-language\">Code</span><button type=\"button\" class=\"copy-button\" data-copy hidden><span class=\"copy-button-label\">Copy</span></button></div><pre role=\"group\" tabindex=\"0\" aria-label=\"Code code\"><code>AUTH_PASSWORD_VALIDATORS <span class=\"o\">=</span> <span class=\"p\">[</span>\n    <span class=\"p\">{</span>\n        <span class=\"s1\">&#39;NAME&#39;</span><span class=\"p\">:</span> <span class=\"s1\">&#39;django.contrib.auth.password_validation.UserAttributeSimilarityValidator&#39;</span><span class=\"p\">,</span>\n    <span class=\"p\">},</span>\n    <span class=\"p\">{</span>\n        <span class=\"s1\">&#39;NAME&#39;</span><span class=\"p\">:</span> <span class=\"s1\">&#39;django.contrib.auth.password_validation.MinimumLengthValidator&#39;</span><span class=\"p\">,</span>\n        <span class=\"s1\">&#39;OPTIONS&#39;</span><span class=\"p\">:</span> <span class=\"p\">{</span>\n            <span class=\"s1\">&#39;min_length&#39;</span><span class=\"p\">:</span> <span class=\"mi\">9</span><span class=\"p\">,</span>\n        <span class=\"p\">}</span>\n    <span class=\"p\">},</span>\n    <span class=\"p\">{</span>\n        <span class=\"s1\">&#39;NAME&#39;</span><span class=\"p\">:</span> <span class=\"s1\">&#39;django.contrib.auth.password_validation.CommonPasswordValidator&#39;</span><span class=\"p\">,</span>\n    <span class=\"p\">},</span>\n    <span class=\"p\">{</span>\n        <span class=\"s1\">&#39;NAME&#39;</span><span class=\"p\">:</span> <span class=\"s1\">&#39;django.contrib.auth.password_validation.NumericPasswordValidator&#39;</span><span class=\"p\">,</span>\n    <span class=\"p\">},</span>\n<span class=\"p\">]</span>\n</code></pre></div>\n<p>这个例子启用了所有包含的验证器：</p>\n<ul class=\"simple\">\n<li><p><code class=\"docutils literal notranslate\">UserAttributeSimilarityValidator</code> 检查密码和一组用户属性集合之间的相似性。</p></li>\n<li><p><code class=\"docutils literal notranslate\">MinimumLengthValidator</code> 用来检查密码是否符合最小长度。这个验证器可以自定义设置：它现在需要最短9位字符，而不是默认的8个字符。</p></li>\n<li><p><code class=\"docutils literal notranslate\">CommonPasswordValidator</code> 检查密码是否在常用密码列表中。默认情况下，它会与列表中的2000个常用密码作比较。</p></li>\n<li><p><code class=\"docutils literal notranslate\">NumericPasswordValidator</code> 检查密码是否是完全是数字的。</p></li>\n</ul>\n<p>对于 <code class=\"docutils literal notranslate\">UserAttributeSimilarityValidator</code> 和 <code class=\"docutils literal notranslate\">CommonPasswordValidator</code> ，我们在这个例子里使用默认配置。<code class=\"docutils literal notranslate\">NumericPasswordValidator</code> 不需要设置。</p>\n<p>帮助文本和来自密码验证器的任何错误信息始终按照  <a class=\"reference internal\" href=\"/zh-hans/4.1/ref/settings/#std-setting-AUTH_PASSWORD_VALIDATORS\"><code class=\"xref std std-setting docutils literal notranslate\">AUTH_PASSWORD_VALIDATORS</code></a> 列出的顺序返回。</p>\n</section>\n<section id=\"included-validators\">\n<h3>已包含的验证器<a class=\"heading-anchor\" href=\"#included-validators\"><span class=\"visually-hidden\">Link to this heading</span><span aria-hidden=\"true\">#</span></a></h3>\n<p>Django 包含了四种验证器：</p>\n<dl class=\"py class\">\n<dt class=\"sig sig-object py\" id=\"django.contrib.auth.password_validation.MinimumLengthValidator\">\n<em class=\"property\"><span class=\"k\">class</span> </em><span class=\"sig-name descname\">MinimumLengthValidator</span><span class=\"sig-paren\">(</span><em class=\"sig-param\">min_length<span class=\"o\">=</span><span class=\"default_value\">8</span></em><span class=\"sig-paren\">)</span><a class=\"heading-anchor\" href=\"#django.contrib.auth.password_validation.MinimumLengthValidator\"><span class=\"visually-hidden\">Link to this definition</span><span aria-hidden=\"true\">#</span></a></dt>\n<dd><p>Validates that the password is of a minimum length.\nThe minimum length can be customized with the <code class=\"docutils literal notranslate\">min_length</code> parameter.</p>\n</dd></dl>\n\n<dl class=\"py class\">\n<dt class=\"sig sig-object py\" id=\"django.contrib.auth.password_validation.UserAttributeSimilarityValidator\">\n<em class=\"property\"><span class=\"k\">class</span> </em><span class=\"sig-name descname\">UserAttributeSimilarityValidator</span><span class=\"sig-paren\">(</span><em class=\"sig-param\">user_attributes<span class=\"o\">=</span><span class=\"default_value\">DEFAULT_USER_ATTRIBUTES</span></em>, <em class=\"sig-param\">max_similarity<span class=\"o\">=</span><span class=\"default_value\">0.7</span></em><span class=\"sig-paren\">)</span><a class=\"heading-anchor\" href=\"#django.contrib.auth.password_validation.UserAttributeSimilarityValidator\"><span class=\"visually-hidden\">Link to this definition</span><span aria-hidden=\"true\">#</span></a></dt>\n<dd><p>Validates that the password is sufficiently different from certain\nattributes of the user.</p>\n<p><code class=\"docutils literal notranslate\">user_attributes</code> 参数应该是可比较的用户属性名的可迭代参数。如果没有提供这个参数，默认使用：<code class=\"docutils literal notranslate\">'username', 'first_name', 'last_name', 'email'</code> 。不存在的属性会被忽略。</p>\n<p>The maximum allowed similarity of passwords can be set on a scale of 0.1\nto 1.0 with the <code class=\"docutils literal notranslate\">max_similarity</code> parameter. This is compared to the\nresult of <a class=\"reference external\" href=\"https://docs.python.org/3/library/difflib.html#difflib.SequenceMatcher.quick_ratio\" title=\"(in Python v3.14)\"><code class=\"xref py py-meth docutils literal notranslate\">difflib.SequenceMatcher.quick_ratio()</code></a>. A value of 0.1\nrejects passwords unless they are substantially different from the\n<code class=\"docutils literal notranslate\">user_attributes</code>, whereas a value of 1.0 rejects only passwords that are\nidentical to an attribute's value.</p>\n<aside class=\"version-note version-changed\" data-version=\"2.2.26\" role=\"note\">\n<p class=\"version-note-title\">Changed in Django 2.2.26</p><p>The <code class=\"docutils literal notranslate\">max_similarity</code> parameter was limited to a minimum value of 0.1.</p>\n</aside>\n</dd></dl>\n\n<dl class=\"py class\">\n<dt class=\"sig sig-object py\" id=\"django.contrib.auth.password_validation.CommonPasswordValidator\">\n<em class=\"property\"><span class=\"k\">class</span> </em><span class=\"sig-name descname\">CommonPasswordValidator</span><span class=\"sig-paren\">(</span><em class=\"sig-param\">password_list_path<span class=\"o\">=</span><span class=\"default_value\">DEFAULT_PASSWORD_LIST_PATH</span></em><span class=\"sig-paren\">)</span><a class=\"heading-anchor\" href=\"#django.contrib.auth.password_validation.CommonPasswordValidator\"><span class=\"visually-hidden\">Link to this definition</span><span aria-hidden=\"true\">#</span></a></dt>\n<dd><p>Validates that the password is not a common password. This converts the\npassword to lowercase (to do a case-insensitive comparison) and checks it\nagainst a list of 20,000 common password created by <a class=\"reference external\" href=\"https://gist.github.com/roycewilliams/281ce539915a947a23db17137d91aeb7\">Royce Williams</a>.</p>\n<p><code class=\"docutils literal notranslate\">password_list_path</code> 用来设置自定义的常用密码列表文件的路径。这个文件应该每行包含一个小写密码，并且文件是纯文本或 gzip 压缩过的。</p>\n</dd></dl>\n\n<dl class=\"py class\">\n<dt class=\"sig sig-object py\" id=\"django.contrib.auth.password_validation.NumericPasswordValidator\">\n<em class=\"property\"><span class=\"k\">class</span> </em><span class=\"sig-name descname\">NumericPasswordValidator</span><a class=\"heading-anchor\" href=\"#django.contrib.auth.password_validation.NumericPasswordValidator\"><span class=\"visually-hidden\">Link to this definition</span><span aria-hidden=\"true\">#</span></a></dt>\n<dd><p>Validate that the password is not entirely numeric.</p>\n</dd></dl>\n\n</section>\n<section id=\"integrating-validation\">\n<h3>集成检查<a class=\"heading-anchor\" href=\"#integrating-validation\"><span class=\"visually-hidden\">Link to this heading</span><span aria-hidden=\"true\">#</span></a></h3>\n<p><code class=\"docutils literal notranslate\">django.contrib.auth.password_validation</code> 包含一些你可以在表单或其他地方调用的函数，用来集成密码检查。如果你使用自定义表单来进行密码设置或者你有允许密码设置的 API 调用，此功能会很有用。</p>\n<dl class=\"py function\">\n<dt class=\"sig sig-object py\" id=\"django.contrib.auth.password_validation.validate_password\">\n<span class=\"sig-name descname\">validate_password</span><span class=\"sig-paren\">(</span><em class=\"sig-param\">password</em>, <em class=\"sig-param\">user<span class=\"o\">=</span><span class=\"default_value\">None</span></em>, <em class=\"sig-param\">password_validators<span class=\"o\">=</span><span class=\"default_value\">None</span></em><span class=\"sig-paren\">)</span><a class=\"heading-anchor\" href=\"#django.contrib.auth.password_validation.validate_password\"><span class=\"visually-hidden\">Link to this definition</span><span aria-hidden=\"true\">#</span></a></dt>\n<dd><p>验证密码。如果所有验证器验证密码有效，则返回 <code class=\"docutils literal notranslate\">None</code> 。如果一个或多个验证器拒绝此密码，将会引发 <a class=\"reference internal\" href=\"/zh-hans/4.1/ref/exceptions/#django.core.exceptions.ValidationError\" title=\"django.core.exceptions.ValidationError\"><code class=\"xref py py-exc docutils literal notranslate\">ValidationError</code></a> 和验证器的错误信息。</p>\n<p><code class=\"docutils literal notranslate\">user</code> 对象是可选的：如果不提供用户对象，一些验证器将不能执行验证，并将接受所有密码。</p>\n</dd></dl>\n\n<dl class=\"py function\">\n<dt class=\"sig sig-object py\" id=\"django.contrib.auth.password_validation.password_changed\">\n<span class=\"sig-name descname\">password_changed</span><span class=\"sig-paren\">(</span><em class=\"sig-param\">password</em>, <em class=\"sig-param\">user<span class=\"o\">=</span><span class=\"default_value\">None</span></em>, <em class=\"sig-param\">password_validators<span class=\"o\">=</span><span class=\"default_value\">None</span></em><span class=\"sig-paren\">)</span><a class=\"heading-anchor\" href=\"#django.contrib.auth.password_validation.password_changed\"><span class=\"visually-hidden\">Link to this definition</span><span aria-hidden=\"true\">#</span></a></dt>\n<dd><p>通知所有验证器密码已经更改。这可以由验证器使用，例如防止密码重用。一旦密码更改成功，则调用此方法。</p>\n<p>对于 <a class=\"reference internal\" href=\"/zh-hans/4.1/topics/auth/customizing/#django.contrib.auth.models.AbstractBaseUser\" title=\"django.contrib.auth.models.AbstractBaseUser\"><code class=\"xref py py-class docutils literal notranslate\">AbstractBaseUser</code></a> 子类，当调用 <a class=\"reference internal\" href=\"/zh-hans/4.1/topics/auth/customizing/#django.contrib.auth.models.AbstractBaseUser.set_password\" title=\"django.contrib.auth.models.AbstractBaseUser.set_password\"><code class=\"xref py py-meth docutils literal notranslate\">set_password()</code></a> 是会将密码字段标记为 &quot;dirty&quot; ，这会在用户保存后调用 <code class=\"docutils literal notranslate\">password_changed()</code> 。</p>\n</dd></dl>\n\n<dl class=\"py function\">\n<dt class=\"sig sig-object py\" id=\"django.contrib.auth.password_validation.password_validators_help_texts\">\n<span class=\"sig-name descname\">password_validators_help_texts</span><span class=\"sig-paren\">(</span><em class=\"sig-param\">password_validators<span class=\"o\">=</span><span class=\"default_value\">None</span></em><span class=\"sig-paren\">)</span><a class=\"heading-anchor\" href=\"#django.contrib.auth.password_validation.password_validators_help_texts\"><span class=\"visually-hidden\">Link to this definition</span><span aria-hidden=\"true\">#</span></a></dt>\n<dd><p>返回一个所有验证器帮助文案的列表。这些向用户解释了密码要求。</p>\n</dd></dl>\n\n<dl class=\"py function\">\n<dt class=\"sig sig-object py\" id=\"django.contrib.auth.password_validation.password_validators_help_text_html\">\n<span class=\"sig-name descname\">password_validators_help_text_html</span><span class=\"sig-paren\">(</span><em class=\"sig-param\">password_validators<span class=\"o\">=</span><span class=\"default_value\">None</span></em><span class=\"sig-paren\">)</span><a class=\"heading-anchor\" href=\"#django.contrib.auth.password_validation.password_validators_help_text_html\"><span class=\"visually-hidden\">Link to this definition</span><span aria-hidden=\"true\">#</span></a></dt>\n<dd><p>返回一个``&lt;ul&gt;`` ，包含所有帮助文案的 HTML 字符串。这在表单中添加密码验证时有帮助，因为你可以直接将输出传递到表单字段的 <code class=\"docutils literal notranslate\">help_text</code> 参数。</p>\n</dd></dl>\n\n<dl class=\"py function\">\n<dt class=\"sig sig-object py\" id=\"django.contrib.auth.password_validation.get_password_validators\">\n<span class=\"sig-name descname\">get_password_validators</span><span class=\"sig-paren\">(</span><em class=\"sig-param\">validator_config</em><span class=\"sig-paren\">)</span><a class=\"heading-anchor\" href=\"#django.contrib.auth.password_validation.get_password_validators\"><span class=\"visually-hidden\">Link to this definition</span><span aria-hidden=\"true\">#</span></a></dt>\n<dd><p>返回一个基于 <code class=\"docutils literal notranslate\">validator_config</code> 的验证器对象的集合。默认情况下，所有函数使用 <a class=\"reference internal\" href=\"/zh-hans/4.1/ref/settings/#std-setting-AUTH_PASSWORD_VALIDATORS\"><code class=\"xref std std-setting docutils literal notranslate\">AUTH_PASSWORD_VALIDATORS</code></a> 定义的验证器，但通过一个验证器替代集合来调用此函数，然后向其他函数传递的密码验证器参数传递结果，将使用你自定义的验证器集合。当你有一个应用于大多数场景的通用的验证器集合时，需要一个自定义的集合来用于特殊情况。当你始终使用同一个验证器集合时，则不需要这个函数，因为默认使用是 <a class=\"reference internal\" href=\"/zh-hans/4.1/ref/settings/#std-setting-AUTH_PASSWORD_VALIDATORS\"><code class=\"xref std std-setting docutils literal notranslate\">AUTH_PASSWORD_VALIDATORS</code></a> 的配置。</p>\n<p><code class=\"docutils literal notranslate\">validator_config</code> 的结构和 <a class=\"reference internal\" href=\"/zh-hans/4.1/ref/settings/#std-setting-AUTH_PASSWORD_VALIDATORS\"><code class=\"xref std std-setting docutils literal notranslate\">AUTH_PASSWORD_VALIDATORS</code></a> 的结构相同。这个函数的返回值可以传递给上述函数列表的``password_validators`` 参数。</p>\n</dd></dl>\n\n<p>注意，如果将密码传递给其中一个函数，应该始终是明文密码，而不是哈希过的密码。</p>\n</section>\n<section id=\"writing-your-own-validator\">\n<h3>编写自定义的验证器<a class=\"heading-anchor\" href=\"#writing-your-own-validator\"><span class=\"visually-hidden\">Link to this heading</span><span aria-hidden=\"true\">#</span></a></h3>\n<p>如果 Django 内置的验证器不满足你的需求，你可以编写自定义的验证器。验证器的接口很小。它们必须实现两个方法：</p>\n<ul class=\"simple\">\n<li><p><code class=\"docutils literal notranslate\">validate(self, password, user=None)</code> ：验证密码。如果密码有效，返回 <code class=\"docutils literal notranslate\">None</code> ，否则引发 <a class=\"reference internal\" href=\"/zh-hans/4.1/ref/exceptions/#django.core.exceptions.ValidationError\" title=\"django.core.exceptions.ValidationError\"><code class=\"xref py py-exc docutils literal notranslate\">ValidationError</code></a> 错误。你必须能够处理 <code class=\"docutils literal notranslate\">user</code> 为 <code class=\"docutils literal notranslate\">None</code> 的情况，如果这样会让验证器无法运行，只需返回 <code class=\"docutils literal notranslate\">None</code> 即可。</p></li>\n<li><p><code class=\"docutils literal notranslate\">get_help_text()</code> ：提供一个帮助文本向用户解释密码要求。</p></li>\n</ul>\n<p>验证器的 <a class=\"reference internal\" href=\"/zh-hans/4.1/ref/settings/#std-setting-AUTH_PASSWORD_VALIDATORS\"><code class=\"xref std std-setting docutils literal notranslate\">AUTH_PASSWORD_VALIDATORS</code></a> 中， <code class=\"docutils literal notranslate\">OPTIONS</code> 里的任何条目将会传递到构造器中。所有构造器参数应该有一个默认值。</p>\n<p>这里是一个验证器的基本示例，其中包含一个可选的设置：</p>\n<div class=\"code-block\" data-language=\"default\"><div class=\"code-block-toolbar\"><span class=\"code-block-language\">Code</span><button type=\"button\" class=\"copy-button\" data-copy hidden><span class=\"copy-button-label\">Copy</span></button></div><pre role=\"group\" tabindex=\"0\" aria-label=\"Code code\"><code><span class=\"kn\">from</span> <span class=\"nn\">django.core.exceptions</span> <span class=\"kn\">import</span> ValidationError\n<span class=\"kn\">from</span> <span class=\"nn\">django.utils.translation</span> <span class=\"kn\">import</span> gettext <span class=\"k\">as</span> _\n\n<span class=\"k\">class</span> <span class=\"nc\">MinimumLengthValidator</span><span class=\"p\">:</span>\n    <span class=\"k\">def</span> <span class=\"fm\">__init__</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> min_length<span class=\"o\">=</span><span class=\"mi\">8</span><span class=\"p\">):</span>\n        <span class=\"bp\">self</span><span class=\"o\">.</span>min_length <span class=\"o\">=</span> min_length\n\n    <span class=\"k\">def</span> <span class=\"nf\">validate</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> password<span class=\"p\">,</span> user<span class=\"o\">=</span><span class=\"kc\">None</span><span class=\"p\">):</span>\n        <span class=\"k\">if</span> <span class=\"nb\">len</span><span class=\"p\">(</span>password<span class=\"p\">)</span> <span class=\"o\">&lt;</span> <span class=\"bp\">self</span><span class=\"o\">.</span>min_length<span class=\"p\">:</span>\n            <span class=\"k\">raise</span> ValidationError<span class=\"p\">(</span>\n                _<span class=\"p\">(</span><span class=\"s2\">&quot;This password must contain at least </span><span class=\"si\">%(min_length)d</span><span class=\"s2\"> characters.&quot;</span><span class=\"p\">),</span>\n                code<span class=\"o\">=</span><span class=\"s1\">&#39;password_too_short&#39;</span><span class=\"p\">,</span>\n                params<span class=\"o\">=</span><span class=\"p\">{</span><span class=\"s1\">&#39;min_length&#39;</span><span class=\"p\">:</span> <span class=\"bp\">self</span><span class=\"o\">.</span>min_length<span class=\"p\">},</span>\n            <span class=\"p\">)</span>\n\n    <span class=\"k\">def</span> <span class=\"nf\">get_help_text</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">):</span>\n        <span class=\"k\">return</span> _<span class=\"p\">(</span>\n            <span class=\"s2\">&quot;Your password must contain at least </span><span class=\"si\">%(min_length)d</span><span class=\"s2\"> characters.&quot;</span>\n            <span class=\"o\">%</span> <span class=\"p\">{</span><span class=\"s1\">&#39;min_length&#39;</span><span class=\"p\">:</span> <span class=\"bp\">self</span><span class=\"o\">.</span>min_length<span class=\"p\">}</span>\n        <span class=\"p\">)</span>\n</code></pre></div>\n<p>你也可以实现 <code class=\"docutils literal notranslate\">password_changed(password, user=None)</code> ，在密码修改成功后调用。比如说用来防止密码重用。但是，如果你决定存储用户之前的密码，则不应该以明文形式存储。</p>\n</section>\n</section>","rootId":"password-management-in-django","toc":[{"title":"Django 如何存储密码","anchor":"how-django-stores-passwords","children":[{"title":"在 Django 中使用 Argon2","anchor":"using-argon2-with-django","children":[]},{"title":"在 Django 中使用 bcrypt","anchor":"using-bcrypt-with-django","children":[]},{"title":"在 Django 中使用 scrypt","anchor":"using-scrypt-with-django","children":[]},{"title":"增加盐的熵值","anchor":"increasing-the-salt-entropy","children":[]},{"title":"增加工作因子","anchor":"increasing-the-work-factor","children":[{"title":"PBKDF2 和 bcrypt","anchor":"pbkdf2-and-bcrypt","children":[]},{"title":"Argon2","anchor":"argon2","children":[]},{"title":"scrypt","anchor":"scrypt","children":[]}]},{"title":"密码升级","anchor":"password-upgrading","children":[]},{"title":"无需登录的密码升级","anchor":"password-upgrading-without-requiring-a-login","children":[]},{"title":"已包含的哈希","anchor":"included-hashers","children":[]},{"title":"编写你自己的哈希","anchor":"writing-your-own-hasher","children":[]}]},{"title":"手动管理用户的密码","anchor":"module-django.contrib.auth.hashers","children":[]},{"title":"密码验证","anchor":"module-django.contrib.auth.password_validation","children":[{"title":"启用密码验证","anchor":"enabling-password-validation","children":[]},{"title":"已包含的验证器","anchor":"included-validators","children":[]},{"title":"集成检查","anchor":"integrating-validation","children":[]},{"title":"编写自定义的验证器","anchor":"writing-your-own-validator","children":[]}]}],"breadcrumbs":[{"docname":"topics/index","title":"使用 Django","url":"/zh-hans/4.1/topics/"},{"docname":"topics/auth/index","title":"Django中的用户认证","url":"/zh-hans/4.1/topics/auth/"}],"prev":{"docname":"topics/auth/default","title":"使用 Django 的验证系统","url":"/zh-hans/4.1/topics/auth/default/"},"next":{"docname":"topics/auth/customizing","title":"Django 中的自定义验证","url":"/zh-hans/4.1/topics/auth/customizing/"},"formats":{"html":"/zh-hans/4.1/topics/auth/passwords/","markdown":"/zh-hans/4.1/topics/auth/passwords.md","json":"/zh-hans/4.1/topics/auth/passwords.json"},"source":"https://github.com/django/django/blob/stable/4.1.x/docs/topics/auth/passwords.txt","official":"https://docs.djangoproject.com/zh-hans/4.1/topics/auth/passwords/","inVersions":["6.1","6.0","5.2","5.1","5.0","4.2","4.1","4.0","3.2","3.1","3.0","2.2","2.1","2.0"],"inLocales":["en","zh-hans","fr","ja","id","it","pt-br","ko","es","el","pl"]}