
后端认证鉴权身份认证【免费下载链接】django-allauthIntegrated set of Django applications addressing authentication, registration, account management as well as 3rd party (social) account authentication. Mirror of https://codeberg.org/allauth/django-allauth/项目地址https://gitcode.com/gh_mirrors/dj/django-allauth点击查看免费下载本文是面向正在使用第三方扩展django-allauth-2fa、希望切换到 django-allauth 内置多因素认证MFA实现的团队的技术迁移指南。文章以官方参考迁移代码为核心结合本仓库中allauth.mfa的模型、适配器与内部流程源码逐段讲解 TOTP 密钥与恢复码的迁移原理、数据格式差异、加密存储钩子以及迁移后的验证方式帮助读者在不停用既有用户双因素保护的前提下完成平滑切换。迁移背景为什么需要迁移 TOTP 密钥与恢复码django-allauth 从某个版本起将多因素认证MFA作为内置能力提供涵盖基于 TOTP 的动态口令认证基于恢复码recovery codes的备援认证恢复码的查看、下载与重新生成WebAuthn 凭据与 Passkey 登录默认关闭。内置实现位于 allauth/mfa 包中。而历史项目中很多团队使用独立的第三方扩展 django-allauth-2fa 实现双因素认证该扩展基于django-otp的TOTPDevice与StaticDevice模型保存密钥。两套实现的存储模型完全不同因此在切换时必须将既有用户的TOTP 密钥与恢复码一并迁入 django-allauth 的Authenticator模型否则已开启双因素的用户将无法登录。本仓库官方文档 docs/mfa/django-allauth-2fa.rst 为此提供了参考迁移代码下文将逐段剖析。迁移前置条件启用内置 MFA 应用在编写迁移命令之前需要先让内置 MFA 应用处于可用状态。安装 mfa 扩展依赖内置 MFA 依赖qrcode等额外包需要安装mfaextraspip install django-allauth[mfa]注册应用并执行数据迁移在项目的settings.py中将allauth.mfa加入INSTALLED_APPS参见 docs/mfa/introduction.rstINSTALLED_APPS [ ... allauth, allauth.account, allauth.mfa, ... ]随后执行数据迁移创建Authenticator模型对应的表python manage.py migrateAuthenticator的表结构由 allauth/mfa/migrations/0001_initial.py 及其后续迁移定义其中 0003_authenticator_type_uniq.py 为每个用户施加了(user, type)上的条件唯一约束对totp与recovery_codes两种类型同一用户只能各有一条记录。这一点对迁移脚本的编写有直接影响见下文。新旧数据模型对照迁移的核心是理解两套模型的数据格式差异。django-otp 侧的模型迁移来源django_otp.plugins.otp_totp.models.TOTPDevice保存 TOTP 设备关键字段为key十六进制字符串、confirmed是否已确认启用与user_iddjango_otp.plugins.otp_static.models.StaticDevice保存静态令牌恢复码设备其下通过token_set关联一组StaticToken即恢复码本体。allauth.mfa 侧的模型迁移目标目标模型为 allauth/mfa/models.py 中的Authenticatorclass Authenticator(models.Model): class Type(models.TextChoices): RECOVERY_CODES recovery_codes, _(Recovery codes) TOTP totp, _(TOTP Authenticator) WEBAUTHN webauthn, _(WebAuthn) user models.ForeignKey(settings.AUTH_USER_MODEL, on_deletemodels.CASCADE) type models.CharField(max_length20, choicesType.choices) data models.JSONField() created_at models.DateTimeField(defaulttimezone.now) last_used_at models.DateTimeField(nullTrue)关键差异所有认证方式统一存放在一张表通过type区分totp、recovery_codes、webauthn三种类型的业务数据全部放在dataJSON 字段中TOTP 的data结构为{secret: 加密后的 Base32 密钥}密钥必须为 Base32 编码与标准 TOTP otpauth URI 的格式一致恢复码的data结构为{migrated_codes: [加密后的恢复码, ...]}这是一个迁移专用字段正常新建的恢复码记录使用seedused_mask的派生方案而migrated_codes专门用于承接从外部系统迁入的、无法用新方案重新派生的旧恢复码。为什么 TOTP 密钥要转成 Base32django-otp 的TOTPDevice.key是随机字节的十六进制表示而 django-allauth 的 TOTP 实现见 allauth/mfa/totp/internal/auth.py在generate_totp_secret()中使用base64.b32encode(random_bytes)生成 Base32 密钥并在hotp_value()中以base64.b32decode(secret.encode(ascii), casefoldTrue)解码。因此迁移时要把十六进制密钥解码回原始字节、再重新编码为 Base32secret base64.b32encode(bytes.fromhex(totp.key)).decode(ascii)只有完成这一转换用户手机上的 Authenticator 应用才能继续基于同一密钥生成正确的动态口令。参考迁移命令完整代码官方文档给出的迁移脚本是一个 Django management commandBaseCommand全文如下来自 docs/mfa/django-allauth-2fa.rstimport base64 from allauth.mfa.adapter import get_adapter from allauth.mfa.models import Authenticator from django.core.management.base import BaseCommand from django_otp.plugins.otp_static.models import StaticDevice from django_otp.plugins.otp_totp.models import TOTPDevice class Command(BaseCommand): def handle(self, **options): adapter get_adapter() authenticators [] for totp in TOTPDevice.objects.filter(confirmedTrue).iterator(): recovery_codes set() for sdevice in StaticDevice.objects.filter(confirmedTrue, user_idtotp.user_id).iterator(): recovery_codes.update(sdevice.token_set.values_list(token, flatTrue)) secret base64.b32encode(bytes.fromhex(totp.key)).decode(ascii) totp_authenticator Authenticator( user_idtotp.user_id, typeAuthenticator.Type.TOTP, data{secret: adapter.encrypt(secret)}, ) authenticators.append(totp_authenticator) authenticators.append( Authenticator( user_idtotp.user_id, typeAuthenticator.Type.RECOVERY_CODES, data{ migrated_codes: [adapter.encrypt(c) for c in recovery_codes], }, ) ) Authenticator.objects.bulk_create(authenticators)逐段剖析迁移脚本1. 获取适配器实例adapter get_adapter()get_adapter()定义于 allauth/mfa/adapter.py它依据MFA_ADAPTER设置默认allauth.mfa.adapter.DefaultMFAAdapter实例化适配器def get_adapter() - DefaultMFAAdapter: return import_attribute(app_settings.ADAPTER)()迁移脚本通过适配器调用encrypt()从而自动继承项目自定义的加密策略——这一点非常关键详见下文密钥加密存储小节。2. 遍历已确认的 TOTP 设备for totp in TOTPDevice.objects.filter(confirmedTrue).iterator():confirmedTrue过滤出用户已完成验证、实际可用的 TOTP 设备未确认的设备如用户扫码后从未输入过验证码不具备认证价值不参与迁移。.iterator()避免一次性将所有设备载入内存适合大用户量场景。3. 收集同一用户的恢复码recovery_codes set() for sdevice in StaticDevice.objects.filter(confirmedTrue, user_idtotp.user_id).iterator(): recovery_codes.update(sdevice.token_set.values_list(token, flatTrue))对每个已确认的 TOTP 设备查找同一用户的已确认StaticDevice将其token_set中的全部静态令牌收进一个set。使用set有两个作用自动去重同一恢复码在多个设备中重复出现时只保留一份为后续列表推导提供确定性的迭代行为。注意恢复码是绑定到 TOTP 用户的因此以user_idtotp.user_id为关联键聚合而不是为每个 StaticDevice 单独建一条Authenticator记录。4. 构造 TOTP Authenticator 记录secret base64.b32encode(bytes.fromhex(totp.key)).decode(ascii) totp_authenticator Authenticator( user_idtotp.user_id, typeAuthenticator.Type.TOTP, data{secret: adapter.encrypt(secret)}, )bytes.fromhex(totp.key)把 django-otp 的十六进制密钥还原为原始字节base64.b32encode(...).decode(ascii)重新编码为 Base32 字符串与 allauth/mfa/totp/internal/auth.py 中generate_totp_secret()的输出格式对齐data{secret: adapter.encrypt(secret)}密钥经适配器加密后存入 JSON 字段。TOTP 校验时allauth/mfa/totp/internal/auth.py 的TOTP.validate_code()会先decrypt(self.instance.data[secret])再执行 HMAC-SHA1 动态口令比对与迁移写入的格式一一对应。5. 构造恢复码 Authenticator 记录authenticators.append( Authenticator( user_idtotp.user_id, typeAuthenticator.Type.RECOVERY_CODES, data{ migrated_codes: [adapter.encrypt(c) for c in recovery_codes], }, ) )每个恢复码在存入前同样经过adapter.encrypt()整份列表挂在migrated_codes键下。这条记录与 TOTP 记录一起被收集随后统一bulk_create。6. 批量写入Authenticator.objects.bulk_create(authenticators)一次性批量创建所有用户的认证记录性能优于逐条save()。迁移后恢复码的消费逻辑migrated_codes 的生命周期迁入的恢复码并不是静态数据它会参与后续的认证与校验。其消费逻辑定义在 allauth/mfa/recovery_codes/internal/auth.py 的RecoveryCodes类中def _get_migrated_codes(self) - list[str] | None: codes self.instance.data.get(migrated_codes) if codes is not None: return [decrypt(code) for code in codes] return None def _validate_migrated_code(self, code: str) - bool | None: migrated_codes self._get_migrated_codes() if migrated_codes is None: return None try: idx migrated_codes.index(code) except ValueError: return False else: migrated_codes self.instance.data[migrated_codes] migrated_codes.pop(idx) self.instance.data[migrated_codes] migrated_codes self.instance.save() return True def validate_code(self, code: str) - bool: ret self._validate_migrated_code(code) if ret is not None: return ret ...从中可以看出三个重要事实优先消费迁移代码validate_code()首先尝试_validate_migrated_code()只有migrated_codes字段不存在返回None时才走基于seed的新方案一次性使用匹配到某个迁移恢复码后会将其从migrated_codes列表中pop并持久化该恢复码随即失效不可重复使用——这符合恢复码的安全惯例与原生恢复码共存语义一致迁移代码同样遵守用完即废的语义只是底层存储从seedused_mask位图换成了显式列表。仓库中的测试 tests/apps/mfa/recovery_codes/test_auth.py 对此进行了验证def test_migrated_codes(db, user): auth Authenticator(useruser, data{migrated_codes: [abc, def]}) ... assert rc.instance.data[migrated_codes] []该测试断言两个迁移代码依次使用后migrated_codes列表被清空从侧面印证了使用即移除的实现细节。密钥加密存储adapter.encrypt / decrypt 钩子迁移脚本全程通过adapter.encrypt()写入密钥与恢复码这是 django-allauth 为密钥存储安全预留的扩展点。默认实现位于 allauth/mfa/adapter.pydef encrypt(self, text: str) - str: Secrets such as the TOTP key are stored in the database. This hook can be used to encrypt those so that they are not stored in the clear in the database. return text def decrypt(self, encrypted_text: str) - str: Counter part of encrypt(). text encrypted_text return text默认行为是原样返回即密钥明文存入dataJSON 字段生产环境建议通过设置MFA_ADAPTER指向自定义适配器覆盖encrypt/decrypt实现真正的加密如基于项目的SECRET_KEY派生密钥的对称加密避免 TOTP 密钥与恢复码以明文形式落库所有读写密钥的路径TOTP 校验、恢复码校验都经由allauth.mfa.utils的encrypt/decrypt薄封装见 allauth/mfa/utils.py因此只要适配器实现了对称加解密迁移写入与运行时读取即自动匹配。运行迁移前的核对清单将上述脚本落地为项目内的 management command例如放入yourapp/management/commands/migrate_allauth_2fa.py后建议按以下清单核对后再执行依赖顺序脚本依赖django_otp的模型应确认项目在迁移完成前仍安装着 django-allauth-2fa / django-otp备份数据库迁移脚本会创建新记录执行前对mfa_authenticator及旧otp_totp_totpdevice、otp_static_staticdevice相关表做备份以便回滚核对幂等性参考脚本未做幂等保护重复运行会因 0003_authenticator_type_uniq.py 引入的(user, type)条件唯一约束而违反约束报错同一用户出现两条totp或两条recovery_codes记录。如需可重复执行应先对目标用户过滤已存在Authenticator则跳过空恢复码处理若某用户只有 TOTP 设备而没有 StaticDevicerecovery_codes集合为空此时仍会创建一条migrated_codes[]的恢复码记录。可以接受等价于无恢复码也可以在脚本中跳过空集合迁移后验证迁移完成后用一位测试用户的 TOTP 密钥生成动态口令、用一条旧恢复码各做一次登录验证确认两条路径TOTP.validate_code与RecoveryCodes.validate_code均正常收尾卸载确认全部用户迁移成功且验证通过后方可移除django-allauth-2fa及django-otp相关依赖与INSTALLED_APPS条目。迁移后的运行时配置可选迁移完成后内置 MFA 的默认行为即可满足基本需求。若需调整可参考 docs/mfa/configuration.rst 中与迁移直接相关的设置项MFA_ADAPTER默认allauth.mfa.adapter.DefaultMFAAdapter自定义适配器路径用于实现密钥加密等行为定制MFA_RECOVERY_CODE_COUNT默认10、MFA_RECOVERY_CODE_DIGITS默认8仅影响新生成的恢复码迁移进来的旧恢复码保持原样不受这两个参数影响MFA_TOTP_PERIOD默认30、MFA_TOTP_DIGITS默认6、MFA_TOTP_TOLERANCE默认0TOTP 的步长、位数与时钟容差。迁移的密钥与这些参数解耦——只要手机端应用使用同一 Base32 密钥就能在相同时间窗口内计算出正确口令MFA_RECOVERY_CODES_SHOW_ONCE默认False是否只在生成时展示一次恢复码对已迁入的恢复码同样生效。小结从 django-allauth-2fa 切换到 django-allauth 内置 MFA本质是一次数据迁移把 django-otp 的TOTPDevice.key十六进制转为 Base32 密钥写入Authenticator的totp记录把StaticDevice的令牌写入recovery_codes记录的migrated_codes字段全程经由适配器的encrypt()钩子保证与运行时读取格式一致。参考官方迁移脚本docs/mfa/django-allauth-2fa.rst配合本仓库 allauth/mfa 的源码理解即可在保证既有用户双因素保护不中断的前提下完成平滑切换。赞分享后端认证鉴权身份认证【免费下载链接】django-allauthIntegrated set of Django applications addressing authentication, registration, account management as well as 3rd party (social) account authentication. Mirror of https://codeberg.org/allauth/django-allauth/项目地址https://gitcode.com/gh_mirrors/dj/django-allauth点击查看免费下载相关推荐django-allauth MFA 配置完全指南TOTP、恢复码、WebAuthn 与浏览器信任机制django allauth MFA 配置完全指南TOTP、恢复码、WebAuthn 与浏览器信任机制 django allauth 的 MFA多因素认证后端认证鉴权身份认证django-allauth MFA 多因素认证入门指南TOTP、恢复代码与 WebAuthn/Passkeydjango allauth MFA 多因素认证入门指南TOTP、恢复代码与 WebAuthn/Passkey 本文是 django allauth 内置多因后端认证鉴权身份认证django-allauth MFA 表单深度定制指南MFA_FORMS 配置与源码级解析django allauth MFA 表单深度定制指南MFA_FORMS 配置与源码级解析 本文聚焦 django allauth 的 MFA多因素认证模后端认证鉴权身份认证上一篇【亲测免费】 MuPDF 项目常见问题解决方案下一篇【亲测免费】 Whisper ASR Webservice 项目推荐创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考