{"title":"Django中的密码管理","version":"3.1","locale":"zh-hans","docname":"topics/auth/passwords","url":"/zh-hans/3.1/topics/auth/passwords/","canonical":"https://djangodocs.dev/zh-hans/3.1/topics/auth/passwords/","summary":"密码管理通常不应该被重新再设计，Django 努力提供了一个安全且灵活的管理用户密码的工具。这篇文档描述了 Django 如何存储密码，如何配置存储哈希，和一些使用哈希密码的工具。 See also 即使用户使用了很强壮的密码，攻击者还是可以窃听他们的网络链接。用户使用 HTTPS 可以避免通过纯 HTTP…","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\">\n<p class=\"admonition-title\">See also</p>\n<p>即使用户使用了很强壮的密码，攻击者还是可以窃听他们的网络链接。用户使用 <a class=\"reference internal\" href=\"/zh-hans/3.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/3.1/ref/contrib/auth/#django.contrib.auth.models.User\" title=\"django.contrib.auth.models.User\"><code class=\"xref py py-class docutils literal notranslate\"><span class=\"pre\">User</span></code></a> 对象的 <a class=\"reference internal\" href=\"/zh-hans/3.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\"><span class=\"pre\">password</span></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 通过查阅 <a class=\"reference internal\" href=\"/zh-hans/3.1/ref/settings/#std-setting-PASSWORD_HASHERS\"><code class=\"xref std std-setting docutils literal notranslate\"><span class=\"pre\">PASSWORD_HASHERS</span></code></a> 的设置来选择算法。这是一个 Django 支持的哈希算法类列表，第一个条目（ <code class=\"docutils literal notranslate\"><span class=\"pre\">settings.PASSWORD_HASHERS[0]</span></code> ）将被用来存储密码，其他条目都是有效的哈希函数，可用来检测已存密码。这意味着如果你想使用不同算法，你需要修改 <a class=\"reference internal\" href=\"/zh-hans/3.1/ref/settings/#std-setting-PASSWORD_HASHERS\"><code class=\"xref std std-setting docutils literal notranslate\"><span class=\"pre\">PASSWORD_HASHERS</span></code></a> ，在列表中首选列出你的算法。</p>\n<p><a class=\"reference internal\" href=\"/zh-hans/3.1/ref/settings/#std-setting-PASSWORD_HASHERS\"><code class=\"xref std std-setting docutils literal notranslate\"><span class=\"pre\">PASSWORD_HASHERS</span></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><span class=\"n\">PASSWORD_HASHERS</span> <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=\"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>Argon2 是2015年哈希密码竞赛的获胜者，这是一个社区为选择下一代哈希算法而主办的公开竞赛。它被设计成在定制硬件上计算不比在普通CPU上计算更容易。</p>\n<p>Argon2 并不是 Django 的默认首选，因为它依赖第三方库。尽管哈希密码竞赛主办方建议立即使用 Argon2 ，而不是 Django 提供的其他算法。</p>\n<p>使用 Argon2 作为你的默认存储算法，需要以下步骤：</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\"><span class=\"pre\">python</span> <span class=\"pre\">-m</span> <span class=\"pre\">pip</span> <span class=\"pre\">install</span> <span class=\"pre\">django[argon2]</span></code> 安装，相当于 <code class=\"docutils literal notranslate\"><span class=\"pre\">python</span> <span class=\"pre\">-m</span> <span class=\"pre\">pip</span> <span class=\"pre\">install</span> <span class=\"pre\">argon2-cffi</span></code> （以及 Django 的 <code class=\"docutils literal notranslate\"><span class=\"pre\">setup.cfg</span></code> 的任何版本要求）。</p></li>\n<li><p>修改 <a class=\"reference internal\" href=\"/zh-hans/3.1/ref/settings/#std-setting-PASSWORD_HASHERS\"><code class=\"xref std std-setting docutils literal notranslate\"><span class=\"pre\">PASSWORD_HASHERS</span></code></a> 配置，把 <code class=\"docutils literal notranslate\"><span class=\"pre\">Argon2PasswordHasher</span></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=\"n\">PASSWORD_HASHERS</span> <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=\"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\"><span class=\"pre\">bcrypt</span></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\"><span class=\"pre\">python</span> <span class=\"pre\">-m</span> <span class=\"pre\">pip</span> <span class=\"pre\">install</span> <span class=\"pre\">django[bcrypt]</span></code> 安装，相当于 <code class=\"docutils literal notranslate\"><span class=\"pre\">python</span> <span class=\"pre\">-m</span> <span class=\"pre\">pip</span> <span class=\"pre\">install</span> <span class=\"pre\">bcrypt</span></code> （以及 Django 的 <code class=\"docutils literal notranslate\"><span class=\"pre\">setup.cfg</span></code> 的任何版本要求）。</p></li>\n<li><p>修改 <a class=\"reference internal\" href=\"/zh-hans/3.1/ref/settings/#std-setting-PASSWORD_HASHERS\"><code class=\"xref std std-setting docutils literal notranslate\"><span class=\"pre\">PASSWORD_HASHERS</span></code></a> 配置，把 <code class=\"docutils literal notranslate\"><span class=\"pre\">BCryptSHA256PasswordHasher</span></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=\"n\">PASSWORD_HASHERS</span> <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=\"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=\"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>PBKDF2 和 bcrypt 算法使用一些迭代或几轮哈希。这样有意降低了攻击者的速度，使得破解密码变得更困难。然而，随着算力的增加，迭代的次数也需要增加。我们已经选择了合理的默认配置（也会对 Django 的每个版本加入），但你可能希望根据你的安全需求和可支配的能力来调高或调低它。为此，你将子类化合适的算法并覆盖 <code class=\"docutils literal notranslate\"><span class=\"pre\">iterations</span></code> 参数。比如，增加默认 PBKDF2 算法使用的一些迭代：</p>\n<ol class=\"arabic\">\n<li><p>创建 <code class=\"docutils literal notranslate\"><span class=\"pre\">django.contrib.auth.hashers.PBKDF2PasswordHasher</span></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=\"w\"> </span><span class=\"nn\">django.contrib.auth.hashers</span><span class=\"w\"> </span><span class=\"kn\">import</span> <span class=\"n\">PBKDF2PasswordHasher</span>\n\n<span class=\"k\">class</span><span class=\"w\"> </span><span class=\"nc\">MyPBKDF2PasswordHasher</span><span class=\"p\">(</span><span class=\"n\">PBKDF2PasswordHasher</span><span class=\"p\">):</span>\n<span class=\"w\">    </span><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    <span class=\"n\">iterations</span> <span class=\"o\">=</span> <span class=\"n\">PBKDF2PasswordHasher</span><span class=\"o\">.</span><span class=\"n\">iterations</span> <span class=\"o\">*</span> <span class=\"mi\">100</span>\n</code></pre></div>\n<p>在你的项目某些位置中保存。比如，你可以放在类似 <code class=\"docutils literal notranslate\"><span class=\"pre\">myproject/hashers.py</span></code> 里。</p>\n</li>\n<li><p>在 <a class=\"reference internal\" href=\"/zh-hans/3.1/ref/settings/#std-setting-PASSWORD_HASHERS\"><code class=\"xref std std-setting docutils literal notranslate\"><span class=\"pre\">PASSWORD_HASHERS</span></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><span class=\"n\">PASSWORD_HASHERS</span> <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=\"p\">]</span>\n</code></pre></div>\n</li>\n</ol>\n<p>现在 Django 使用 PBKDF2 存储密码时将会多次迭代。</p>\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 有三个可以自定义的属性：</p>\n<ol class=\"arabic simple\">\n<li><p><code class=\"docutils literal notranslate\"><span class=\"pre\">time_cost</span></code> 控制哈希的次数。</p></li>\n<li><p><code class=\"docutils literal notranslate\"><span class=\"pre\">memory_cost</span></code> 控制被用来计算哈希时的内存大小。</p></li>\n<li><p><code class=\"docutils literal notranslate\"><span class=\"pre\">parallelism</span></code> 控制并行计算哈希的 CPU 数量。</p></li>\n</ol>\n<p>这三个属性的默认值足够适合你。如果你确定密码哈希过快或过慢，可以按如下方式调整它：</p>\n<ol class=\"arabic simple\">\n<li><p>选择 <code class=\"docutils literal notranslate\"><span class=\"pre\">parallelism</span></code> 你可以节省计算哈希的线程数。</p></li>\n<li><p>选择 <code class=\"docutils literal notranslate\"><span class=\"pre\">memory_cost</span></code> 你可以节省内存的 KiB 。</p></li>\n<li><p>调整 <code class=\"docutils literal notranslate\"><span class=\"pre\">time_cost</span></code> 和估计哈希一个密码所需的时间。挑选出你可以接受的 <code class=\"docutils literal notranslate\"><span class=\"pre\">time_cost</span></code> 。如果设置为1的  <code class=\"docutils literal notranslate\"><span class=\"pre\">time_cost</span></code> 慢的无法接受，则调低 <code class=\"docutils literal notranslate\"><span class=\"pre\">memory_cost</span></code> 。</p></li>\n</ol>\n<aside class=\"admonition-memory-cost-interpretation admonition\">\n<p class=\"admonition-title\"><code class=\"docutils literal notranslate\"><span class=\"pre\">memory_cost</span></code> 说明</p>\n<p>argon2 命令行工具和一些其他的库解释了 <code class=\"docutils literal notranslate\"><span class=\"pre\">memory_cost</span></code> 参数不同于 Django 使用的值。换算公式是``memory_cost == 2 ** memory_cost_commandline`` 。</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/3.1/ref/settings/#std-setting-PASSWORD_HASHERS\"><code class=\"xref std std-setting docutils literal notranslate\"><span class=\"pre\">PASSWORD_HASHERS</span></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\"><span class=\"pre\">User</span></code> 模型，并有一个 <code class=\"docutils literal notranslate\"><span class=\"pre\">accounts</span></code> app 。你可以修改模式以使用任何算法或自定义的模型。</p>\n<p>首先，我们添加一个自定义的哈希：</p>\n<figure class=\"code-block code-block-captioned\" data-language=\"python\"><figcaption class=\"code-block-caption\">accounts/hashers.py</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=\"w\"> </span><span class=\"nn\">django.contrib.auth.hashers</span><span class=\"w\"> </span><span class=\"kn\">import</span> <span class=\"p\">(</span>\n    <span class=\"n\">PBKDF2PasswordHasher</span><span class=\"p\">,</span> <span class=\"n\">SHA1PasswordHasher</span><span class=\"p\">,</span>\n<span class=\"p\">)</span>\n\n\n<span class=\"k\">class</span><span class=\"w\"> </span><span class=\"nc\">PBKDF2WrappedSHA1PasswordHasher</span><span class=\"p\">(</span><span class=\"n\">PBKDF2PasswordHasher</span><span class=\"p\">):</span>\n    <span class=\"n\">algorithm</span> <span class=\"o\">=</span> <span class=\"s1\">&#39;pbkdf2_wrapped_sha1&#39;</span>\n\n    <span class=\"k\">def</span><span class=\"w\"> </span><span class=\"nf\">encode_sha1_hash</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span class=\"n\">sha1_hash</span><span class=\"p\">,</span> <span class=\"n\">salt</span><span class=\"p\">,</span> <span class=\"n\">iterations</span><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><span class=\"n\">encode</span><span class=\"p\">(</span><span class=\"n\">sha1_hash</span><span class=\"p\">,</span> <span class=\"n\">salt</span><span class=\"p\">,</span> <span class=\"n\">iterations</span><span class=\"p\">)</span>\n\n    <span class=\"k\">def</span><span class=\"w\"> </span><span class=\"nf\">encode</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span class=\"n\">password</span><span class=\"p\">,</span> <span class=\"n\">salt</span><span class=\"p\">,</span> <span class=\"n\">iterations</span><span class=\"o\">=</span><span class=\"kc\">None</span><span class=\"p\">):</span>\n        <span class=\"n\">_</span><span class=\"p\">,</span> <span class=\"n\">_</span><span class=\"p\">,</span> <span class=\"n\">sha1_hash</span> <span class=\"o\">=</span> <span class=\"n\">SHA1PasswordHasher</span><span class=\"p\">()</span><span class=\"o\">.</span><span class=\"n\">encode</span><span class=\"p\">(</span><span class=\"n\">password</span><span class=\"p\">,</span> <span class=\"n\">salt</span><span class=\"p\">)</span><span class=\"o\">.</span><span class=\"n\">split</span><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><span class=\"n\">encode_sha1_hash</span><span class=\"p\">(</span><span class=\"n\">sha1_hash</span><span class=\"p\">,</span> <span class=\"n\">salt</span><span class=\"p\">,</span> <span class=\"n\">iterations</span><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\">accounts/migrations/0002_migrate_sha1_passwords.py</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=\"w\"> </span><span class=\"nn\">django.db</span><span class=\"w\"> </span><span class=\"kn\">import</span> <span class=\"n\">migrations</span>\n\n<span class=\"kn\">from</span><span class=\"w\"> </span><span class=\"nn\">..hashers</span><span class=\"w\"> </span><span class=\"kn\">import</span> <span class=\"n\">PBKDF2WrappedSHA1PasswordHasher</span>\n\n\n<span class=\"k\">def</span><span class=\"w\"> </span><span class=\"nf\">forwards_func</span><span class=\"p\">(</span><span class=\"n\">apps</span><span class=\"p\">,</span> <span class=\"n\">schema_editor</span><span class=\"p\">):</span>\n    <span class=\"n\">User</span> <span class=\"o\">=</span> <span class=\"n\">apps</span><span class=\"o\">.</span><span class=\"n\">get_model</span><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    <span class=\"n\">users</span> <span class=\"o\">=</span> <span class=\"n\">User</span><span class=\"o\">.</span><span class=\"n\">objects</span><span class=\"o\">.</span><span class=\"n\">filter</span><span class=\"p\">(</span><span class=\"n\">password__startswith</span><span class=\"o\">=</span><span class=\"s1\">&#39;sha1$&#39;</span><span class=\"p\">)</span>\n    <span class=\"n\">hasher</span> <span class=\"o\">=</span> <span class=\"n\">PBKDF2WrappedSHA1PasswordHasher</span><span class=\"p\">()</span>\n    <span class=\"k\">for</span> <span class=\"n\">user</span> <span class=\"ow\">in</span> <span class=\"n\">users</span><span class=\"p\">:</span>\n        <span class=\"n\">algorithm</span><span class=\"p\">,</span> <span class=\"n\">salt</span><span class=\"p\">,</span> <span class=\"n\">sha1_hash</span> <span class=\"o\">=</span> <span class=\"n\">user</span><span class=\"o\">.</span><span class=\"n\">password</span><span class=\"o\">.</span><span class=\"n\">split</span><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=\"n\">user</span><span class=\"o\">.</span><span class=\"n\">password</span> <span class=\"o\">=</span> <span class=\"n\">hasher</span><span class=\"o\">.</span><span class=\"n\">encode_sha1_hash</span><span class=\"p\">(</span><span class=\"n\">sha1_hash</span><span class=\"p\">,</span> <span class=\"n\">salt</span><span class=\"p\">)</span>\n        <span class=\"n\">user</span><span class=\"o\">.</span><span class=\"n\">save</span><span class=\"p\">(</span><span class=\"n\">update_fields</span><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=\"w\"> </span><span class=\"nc\">Migration</span><span class=\"p\">(</span><span class=\"n\">migrations</span><span class=\"o\">.</span><span class=\"n\">Migration</span><span class=\"p\">):</span>\n\n    <span class=\"n\">dependencies</span> <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    <span class=\"n\">operations</span> <span class=\"o\">=</span> <span class=\"p\">[</span>\n        <span class=\"n\">migrations</span><span class=\"o\">.</span><span class=\"n\">RunPython</span><span class=\"p\">(</span><span class=\"n\">forwards_func</span><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/3.1/ref/settings/#std-setting-PASSWORD_HASHERS\"><code class=\"xref std std-setting docutils literal notranslate\"><span class=\"pre\">PASSWORD_HASHERS</span></code></a> 中添加配置：</p>\n<figure class=\"code-block code-block-captioned\" data-language=\"python\"><figcaption class=\"code-block-caption\">mysite/settings.py</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=\"n\">PASSWORD_HASHERS</span> <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.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=\"s1\">&#39;django.contrib.auth.hashers.CryptPasswordHasher&#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\"><span class=\"pre\">pbkdf2_sha256</span></code></p></li>\n<li><p><code class=\"docutils literal notranslate\"><span class=\"pre\">pbkdf2_sha1</span></code></p></li>\n<li><p><code class=\"docutils literal notranslate\"><span class=\"pre\">argon2</span></code></p></li>\n<li><p><code class=\"docutils literal notranslate\"><span class=\"pre\">bcrypt_sha256</span></code></p></li>\n<li><p><code class=\"docutils literal notranslate\"><span class=\"pre\">bcrypt</span></code></p></li>\n<li><p><code class=\"docutils literal notranslate\"><span class=\"pre\">sha1</span></code></p></li>\n<li><p><code class=\"docutils literal notranslate\"><span class=\"pre\">md5</span></code></p></li>\n<li><p><code class=\"docutils literal notranslate\"><span class=\"pre\">unsalted_sha1</span></code></p></li>\n<li><p><code class=\"docutils literal notranslate\"><span class=\"pre\">unsalted_md5</span></code></p></li>\n<li><p><code class=\"docutils literal notranslate\"><span class=\"pre\">crypt</span></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\"><span class=\"pre\">harden_runtime(self,</span> <span class=\"pre\">password,</span> <span class=\"pre\">encoded)</span></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\"><span class=\"pre\">django.contrib.auth.hashers</span></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\"><span class=\"pre\">User</span></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\"><span class=\"pre\">check_password</span></span><span class=\"sig-paren\">(</span><em class=\"sig-param\"><span class=\"n\"><span class=\"pre\">password</span></span></em>, <em class=\"sig-param\"><span class=\"n\"><span class=\"pre\">encoded</span></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>如果你想通过对比纯文本密码和数据库中的哈希密码来验证用户，可以使用  <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\"><span class=\"pre\">check_password()</span></code></a> 快捷函数。它需要2个参数：要检查的纯文本密码和要检查的数据库中用户密码字段的值。如果匹配成功，返回 <code class=\"docutils literal notranslate\"><span class=\"pre\">True</span></code> ，否则返回 <code class=\"docutils literal notranslate\"><span class=\"pre\">False</span></code> 。</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\"><span class=\"pre\">make_password</span></span><span class=\"sig-paren\">(</span><em class=\"sig-param\"><span class=\"n\"><span class=\"pre\">password</span></span></em>, <em class=\"sig-param\"><span class=\"n\"><span class=\"pre\">salt</span></span><span class=\"o\"><span class=\"pre\">=</span></span><span class=\"default_value\"><span class=\"pre\">None</span></span></em>, <em class=\"sig-param\"><span class=\"n\"><span class=\"pre\">hasher</span></span><span class=\"o\"><span class=\"pre\">=</span></span><span class=\"default_value\"><span class=\"pre\">'default'</span></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\"><span class=\"pre\">PASSWORD_HASHERS</span></code> 配置的首个条目 ），那么可以提供 salt 和 使用的哈希算法。有关每个哈希的算法名，可查看 <a class=\"reference internal\" href=\"#auth-included-hashers\"><span class=\"std std-ref\">已包含的哈希</span></a> 。如果密码参数是 <code class=\"docutils literal notranslate\"><span class=\"pre\">None</span></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\"><span class=\"pre\">check_password()</span></code></a> 通过的密码）。</p>\n<aside class=\"version-note version-changed\" data-version=\"3.1\">\n<p class=\"version-note-title\">Changed in Django 3.1</p><p>如果不为 <code class=\"docutils literal notranslate\"><span class=\"pre\">None</span></code> ，那么 <code class=\"docutils literal notranslate\"><span class=\"pre\">password</span></code> 参数必须是字符串或字节。</p>\n</aside>\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\"><span class=\"pre\">is_password_usable</span></span><span class=\"sig-paren\">(</span><em class=\"sig-param\"><span class=\"n\"><span class=\"pre\">encoded_password</span></span></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/3.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\"><span class=\"pre\">User.set_unusable_password()</span></code></a> 的结果，则返回 <code class=\"docutils literal notranslate\"><span class=\"pre\">False</span></code> 。</p>\n</dd></dl>\n\n</section>\n<section id=\"module-django.contrib.auth.password_validation\">\n<span id=\"id2\"></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/3.1/ref/settings/#std-setting-AUTH_PASSWORD_VALIDATORS\"><code class=\"xref std std-setting docutils literal notranslate\"><span class=\"pre\">AUTH_PASSWORD_VALIDATORS</span></code></a> 控制。默认的设置是一个空列表，这意味着默认是不验证的。在使用默认的 <a class=\"reference internal\" href=\"/zh-hans/3.1/ref/django-admin/#django-admin-startproject\"><code class=\"xref std std-djadmin docutils literal notranslate\"><span class=\"pre\">startproject</span></code></a> 创建的新项目中，默认启用了验证器集合。</p>\n<p>默认情况下，验证器在重置或修改密码的表单中使用，也可以在 <a class=\"reference internal\" href=\"/zh-hans/3.1/ref/django-admin/#django-admin-createsuperuser\"><code class=\"xref std std-djadmin docutils literal notranslate\"><span class=\"pre\">createsuperuser</span></code></a> 和 <a class=\"reference internal\" href=\"/zh-hans/3.1/ref/django-admin/#django-admin-changepassword\"><code class=\"xref std std-djadmin docutils literal notranslate\"><span class=\"pre\">changepassword</span></code></a> 命令中使用。验证器不能应用在模型层，比如 <code class=\"docutils literal notranslate\"><span class=\"pre\">User.objects.create_user()</span></code> 和 <code class=\"docutils literal notranslate\"><span class=\"pre\">create_superuser()</span></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/3.1/ref/settings/#std-setting-AUTH_PASSWORD_VALIDATORS\"><code class=\"xref std std-setting docutils literal notranslate\"><span class=\"pre\">AUTH_PASSWORD_VALIDATORS</span></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><span class=\"n\">AUTH_PASSWORD_VALIDATORS</span> <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\"><span class=\"pre\">UserAttributeSimilarityValidator</span></code> 检查密码和一组用户属性集合之间的相似性。</p></li>\n<li><p><code class=\"docutils literal notranslate\"><span class=\"pre\">MinimumLengthValidator</span></code> 用来检查密码是否符合最小长度。这个验证器可以自定义设置：它现在需要最短9位字符，而不是默认的8个字符。</p></li>\n<li><p><code class=\"docutils literal notranslate\"><span class=\"pre\">CommonPasswordValidator</span></code> 检查密码是否在常用密码列表中。默认情况下，它会与列表中的2000个常用密码作比较。</p></li>\n<li><p><code class=\"docutils literal notranslate\"><span class=\"pre\">NumericPasswordValidator</span></code> 检查密码是否是完全是数字的。</p></li>\n</ul>\n<p>对于 <code class=\"docutils literal notranslate\"><span class=\"pre\">UserAttributeSimilarityValidator</span></code> 和 <code class=\"docutils literal notranslate\"><span class=\"pre\">CommonPasswordValidator</span></code> ，我们在这个例子里使用默认配置。<code class=\"docutils literal notranslate\"><span class=\"pre\">NumericPasswordValidator</span></code> 不需要设置。</p>\n<p>帮助文本和来自密码验证器的任何错误信息始终按照  <a class=\"reference internal\" href=\"/zh-hans/3.1/ref/settings/#std-setting-AUTH_PASSWORD_VALIDATORS\"><code class=\"xref std std-setting docutils literal notranslate\"><span class=\"pre\">AUTH_PASSWORD_VALIDATORS</span></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\"><span class=\"pre\">class</span></span><span class=\"w\"> </span></em><span class=\"sig-name descname\"><span class=\"pre\">MinimumLengthValidator</span></span><span class=\"sig-paren\">(</span><em class=\"sig-param\"><span class=\"n\"><span class=\"pre\">min_length</span></span><span class=\"o\"><span class=\"pre\">=</span></span><span class=\"default_value\"><span class=\"pre\">8</span></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>验证密码是否符合最小长度。最小长度可以在 <code class=\"docutils literal notranslate\"><span class=\"pre\">min_length</span></code> 参数中自定义。</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\"><span class=\"pre\">class</span></span><span class=\"w\"> </span></em><span class=\"sig-name descname\"><span class=\"pre\">UserAttributeSimilarityValidator</span></span><span class=\"sig-paren\">(</span><em class=\"sig-param\"><span class=\"n\"><span class=\"pre\">user_attributes</span></span><span class=\"o\"><span class=\"pre\">=</span></span><span class=\"default_value\"><span class=\"pre\">DEFAULT_USER_ATTRIBUTES</span></span></em>, <em class=\"sig-param\"><span class=\"n\"><span class=\"pre\">max_similarity</span></span><span class=\"o\"><span class=\"pre\">=</span></span><span class=\"default_value\"><span class=\"pre\">0.7</span></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>验证密码是否与用户的某些属性有很大的区别。</p>\n<p><code class=\"docutils literal notranslate\"><span class=\"pre\">user_attributes</span></code> 参数应该是可比较的用户属性名的可迭代参数。如果没有提供这个参数，默认使用：<code class=\"docutils literal notranslate\"><span class=\"pre\">'username',</span> <span class=\"pre\">'first_name',</span> <span class=\"pre\">'last_name',</span> <span class=\"pre\">'email'</span></code> 。不存在的属性会被忽略。</p>\n<p>不合格密码的最小相似度被设置为0到1这个区间。设置为0会拒绝所有密码，而设置为1只会拒绝与属性值相同的密码。</p>\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\"><span class=\"pre\">class</span></span><span class=\"w\"> </span></em><span class=\"sig-name descname\"><span class=\"pre\">CommonPasswordValidator</span></span><span class=\"sig-paren\">(</span><em class=\"sig-param\"><span class=\"n\"><span class=\"pre\">password_list_path</span></span><span class=\"o\"><span class=\"pre\">=</span></span><span class=\"default_value\"><span class=\"pre\">DEFAULT_PASSWORD_LIST_PATH</span></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>验证密码是否是常用密码。先转换密码为小写字母（做一个不区分大小写的比较），然后根据 <a class=\"reference external\" href=\"https://gist.github.com/roycewilliams/281ce539915a947a23db17137d91aeb7\">Royce Williams</a> 创建的2000个常用密码的列表进行检查。</p>\n<p><code class=\"docutils literal notranslate\"><span class=\"pre\">password_list_path</span></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\"><span class=\"pre\">class</span></span><span class=\"w\"> </span></em><span class=\"sig-name descname\"><span class=\"pre\">NumericPasswordValidator</span></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>检查密码是否完全是数字。</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\"><span class=\"pre\">django.contrib.auth.password_validation</span></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\"><span class=\"pre\">validate_password</span></span><span class=\"sig-paren\">(</span><em class=\"sig-param\"><span class=\"n\"><span class=\"pre\">password</span></span></em>, <em class=\"sig-param\"><span class=\"n\"><span class=\"pre\">user</span></span><span class=\"o\"><span class=\"pre\">=</span></span><span class=\"default_value\"><span class=\"pre\">None</span></span></em>, <em class=\"sig-param\"><span class=\"n\"><span class=\"pre\">password_validators</span></span><span class=\"o\"><span class=\"pre\">=</span></span><span class=\"default_value\"><span class=\"pre\">None</span></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\"><span class=\"pre\">None</span></code> 。如果一个或多个验证器拒绝此密码，将会引发 <a class=\"reference internal\" href=\"/zh-hans/3.1/ref/exceptions/#django.core.exceptions.ValidationError\" title=\"django.core.exceptions.ValidationError\"><code class=\"xref py py-exc docutils literal notranslate\"><span class=\"pre\">ValidationError</span></code></a> 和验证器的错误信息。</p>\n<p><code class=\"docutils literal notranslate\"><span class=\"pre\">user</span></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\"><span class=\"pre\">password_changed</span></span><span class=\"sig-paren\">(</span><em class=\"sig-param\"><span class=\"n\"><span class=\"pre\">password</span></span></em>, <em class=\"sig-param\"><span class=\"n\"><span class=\"pre\">user</span></span><span class=\"o\"><span class=\"pre\">=</span></span><span class=\"default_value\"><span class=\"pre\">None</span></span></em>, <em class=\"sig-param\"><span class=\"n\"><span class=\"pre\">password_validators</span></span><span class=\"o\"><span class=\"pre\">=</span></span><span class=\"default_value\"><span class=\"pre\">None</span></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/3.1/topics/auth/customizing/#django.contrib.auth.models.AbstractBaseUser\" title=\"django.contrib.auth.models.AbstractBaseUser\"><code class=\"xref py py-class docutils literal notranslate\"><span class=\"pre\">AbstractBaseUser</span></code></a> 子类，当调用 <a class=\"reference internal\" href=\"/zh-hans/3.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\"><span class=\"pre\">set_password()</span></code></a> 是会将密码字段标记为 &quot;dirty&quot; ，这会在用户保存后调用 <code class=\"docutils literal notranslate\"><span class=\"pre\">password_changed()</span></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\"><span class=\"pre\">password_validators_help_texts</span></span><span class=\"sig-paren\">(</span><em class=\"sig-param\"><span class=\"n\"><span class=\"pre\">password_validators</span></span><span class=\"o\"><span class=\"pre\">=</span></span><span class=\"default_value\"><span class=\"pre\">None</span></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\"><span class=\"pre\">password_validators_help_text_html</span></span><span class=\"sig-paren\">(</span><em class=\"sig-param\"><span class=\"n\"><span class=\"pre\">password_validators</span></span><span class=\"o\"><span class=\"pre\">=</span></span><span class=\"default_value\"><span class=\"pre\">None</span></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\"><span class=\"pre\">help_text</span></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\"><span class=\"pre\">get_password_validators</span></span><span class=\"sig-paren\">(</span><em class=\"sig-param\"><span class=\"n\"><span class=\"pre\">validator_config</span></span></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\"><span class=\"pre\">validator_config</span></code> 的验证器对象的集合。默认情况下，所有函数使用 <a class=\"reference internal\" href=\"/zh-hans/3.1/ref/settings/#std-setting-AUTH_PASSWORD_VALIDATORS\"><code class=\"xref std std-setting docutils literal notranslate\"><span class=\"pre\">AUTH_PASSWORD_VALIDATORS</span></code></a> 定义的验证器，但通过一个验证器替代集合来调用此函数，然后向其他函数传递的密码验证器参数传递结果，将使用你自定义的验证器集合。当你有一个应用于大多数场景的通用的验证器集合时，需要一个自定义的集合来用于特殊情况。当你始终使用同一个验证器集合时，则不需要这个函数，因为默认使用是 <a class=\"reference internal\" href=\"/zh-hans/3.1/ref/settings/#std-setting-AUTH_PASSWORD_VALIDATORS\"><code class=\"xref std std-setting docutils literal notranslate\"><span class=\"pre\">AUTH_PASSWORD_VALIDATORS</span></code></a> 的配置。</p>\n<p><code class=\"docutils literal notranslate\"><span class=\"pre\">validator_config</span></code> 的结构和 <a class=\"reference internal\" href=\"/zh-hans/3.1/ref/settings/#std-setting-AUTH_PASSWORD_VALIDATORS\"><code class=\"xref std std-setting docutils literal notranslate\"><span class=\"pre\">AUTH_PASSWORD_VALIDATORS</span></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\"><span class=\"pre\">validate(self,</span> <span class=\"pre\">password,</span> <span class=\"pre\">user=None)</span></code> ：验证密码。如果密码有效，返回 <code class=\"docutils literal notranslate\"><span class=\"pre\">None</span></code> ，否则引发 <a class=\"reference internal\" href=\"/zh-hans/3.1/ref/exceptions/#django.core.exceptions.ValidationError\" title=\"django.core.exceptions.ValidationError\"><code class=\"xref py py-exc docutils literal notranslate\"><span class=\"pre\">ValidationError</span></code></a> 错误。你必须能够处理 <code class=\"docutils literal notranslate\"><span class=\"pre\">user</span></code> 为 <code class=\"docutils literal notranslate\"><span class=\"pre\">None</span></code> 的情况，如果这样会让验证器无法运行，只需返回 <code class=\"docutils literal notranslate\"><span class=\"pre\">None</span></code> 即可。</p></li>\n<li><p><code class=\"docutils literal notranslate\"><span class=\"pre\">get_help_text()</span></code> ：提供一个帮助文本向用户解释密码要求。</p></li>\n</ul>\n<p>验证器的 <a class=\"reference internal\" href=\"/zh-hans/3.1/ref/settings/#std-setting-AUTH_PASSWORD_VALIDATORS\"><code class=\"xref std std-setting docutils literal notranslate\"><span class=\"pre\">AUTH_PASSWORD_VALIDATORS</span></code></a> 中， <code class=\"docutils literal notranslate\"><span class=\"pre\">OPTIONS</span></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=\"w\"> </span><span class=\"nn\">django.core.exceptions</span><span class=\"w\"> </span><span class=\"kn\">import</span> <span class=\"n\">ValidationError</span>\n<span class=\"kn\">from</span><span class=\"w\"> </span><span class=\"nn\">django.utils.translation</span><span class=\"w\"> </span><span class=\"kn\">import</span> <span class=\"n\">gettext</span> <span class=\"k\">as</span> <span class=\"n\">_</span>\n\n<span class=\"k\">class</span><span class=\"w\"> </span><span class=\"nc\">MinimumLengthValidator</span><span class=\"p\">:</span>\n    <span class=\"k\">def</span><span class=\"w\"> </span><span class=\"fm\">__init__</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span class=\"n\">min_length</span><span class=\"o\">=</span><span class=\"mi\">8</span><span class=\"p\">):</span>\n        <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">min_length</span> <span class=\"o\">=</span> <span class=\"n\">min_length</span>\n\n    <span class=\"k\">def</span><span class=\"w\"> </span><span class=\"nf\">validate</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span class=\"n\">password</span><span class=\"p\">,</span> <span class=\"n\">user</span><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><span class=\"n\">password</span><span class=\"p\">)</span> <span class=\"o\">&lt;</span> <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">min_length</span><span class=\"p\">:</span>\n            <span class=\"k\">raise</span> <span class=\"n\">ValidationError</span><span class=\"p\">(</span>\n                <span class=\"n\">_</span><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                <span class=\"n\">code</span><span class=\"o\">=</span><span class=\"s1\">&#39;password_too_short&#39;</span><span class=\"p\">,</span>\n                <span class=\"n\">params</span><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><span class=\"n\">min_length</span><span class=\"p\">},</span>\n            <span class=\"p\">)</span>\n\n    <span class=\"k\">def</span><span class=\"w\"> </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=\"n\">_</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><span class=\"n\">min_length</span><span class=\"p\">}</span>\n        <span class=\"p\">)</span>\n</code></pre></div>\n<p>你也可以实现 <code class=\"docutils literal notranslate\"><span class=\"pre\">password_changed(password,</span> <span class=\"pre\">user=None)</span></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":"增加工作因子","anchor":"increasing-the-work-factor","children":[{"title":"PBKDF2 和 bcrypt","anchor":"pbkdf2-and-bcrypt","children":[]},{"title":"Argon2","anchor":"argon2","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/3.1/topics/"},{"docname":"topics/auth/index","title":"Django中的用户认证","url":"/zh-hans/3.1/topics/auth/"}],"prev":{"docname":"topics/auth/default","title":"使用 Django 的验证系统","url":"/zh-hans/3.1/topics/auth/default/"},"next":{"docname":"topics/auth/customizing","title":"Django 中的自定义验证","url":"/zh-hans/3.1/topics/auth/customizing/"},"formats":{"html":"/zh-hans/3.1/topics/auth/passwords/","markdown":"/zh-hans/3.1/topics/auth/passwords.md","json":"/zh-hans/3.1/topics/auth/passwords.json"},"source":"https://github.com/django/django/blob/stable/3.1.x/docs/topics/auth/passwords.txt","official":"https://docs.djangoproject.com/zh-hans/3.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","pt-br","ko","es","el","pl"]}