Django通过DRF发送重置链接后密码重置表单不显示问题
Django DRF密码重置链接缺失表单问题
问题描述
通过Django标准网站流程发送的密码重置链接,用户点击后能正常显示密码重置表单;但通过DRF API(Flutter应用触发)发送的链接,打开页面后仅显示「Reset Password」按钮,密码输入表单完全缺失。移除自定义CustomPasswordResetConfirmView后问题仍存在,确认是DRF默认密码重置流程导致的异常。
相关代码与配置
- 原自定义
CustomPasswordResetConfirmView代码(已移除,问题仍存在)
class CustomPasswordResetConfirmView(PasswordResetConfirmView): form_class = SetPasswordForm success_url = reverse_lazy('users:password_reset_complete') template_name = 'users/password_reset_confirm.html' @method_decorator(sensitive_post_parameters('new_password1', 'new_password2')) @method_decorator(csrf_protect) def dispatch(self, *args, **kwargs): return super().dispatch(*args, **kwargs) def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context['form'] = self.form_class(user=self.request.user) return context
- 密码重置模板
users/password_reset_confirm.html
<main class="mt-5" > <div class="container dark-grey-text mt-5"> <div class="content-section"> <form method="POST"> {% csrf_token %} <fieldset class="form-group"> <legend class="border-bottom mb-4">Reset Password</legend> {{ form|crispy }} </fieldset> <div class="form-group"> <button class="btn btn-outline-info" type="submit">Reset Password</button> </div> </form> </div> </div> </main>
- 两种流程的重置链接差异
- 网站流程链接:
https://www.[Domain].com/password-reset-confirm/NTM/bxogt6-68772***************f41836/ - DRF流程链接:
https://www.[Domain].com/password-reset-confirm/1h/bxogub-7eebb4**************deaa0e/
- settings.py相关配置
REST_FRAMEWORK = { 'DEFAULT_AUTHENTICATION_CLASSES': [ 'rest_framework.authentication.BasicAuthentication', 'rest_framework.authentication.SessionAuthentication', 'rest_framework.authentication.TokenAuthentication' ] } AUTH_USER_MODEL = 'auth.User' REST_AUTH_SERIALIZERS = { 'PASSWORD_RESET_SERIALIZER': 'api.serializers.PasswordResetSerializer', } AUTHENTICATION_BACKENDS=['users.models.EmailBackend']
- 未使用的
password_reset_email.html模板
{% autoescape off %} You're receiving this e-mail because you requested a password reset for your user account at {{ site_name }}. Please go to the following page and choose a new password: {% block reset_link %} {{ protocol }}://{{ domain }}/api/{% url 'password_reset_confirm' uidb64=uid token=token %} {% endblock %} Your username, in case you've forgotten: {{ user.username }} Thanks for using our site! The {{ site_name }} team. {% endautoescape %}
问题根源分析
DRF默认密码重置流程生成的uidb64格式与Django原生流程不兼容:
- Django原生采用整数用户ID的base64编码(示例中的
NTM对应十进制53) - DRF流程生成的是字符串/UUID格式的base64编码(示例中的
1h)
Django原生的PasswordResetConfirmView仅能正确解析整数ID的编码,当传入字符串/UUID格式的uidb64时,视图无法获取有效用户,导致表单未初始化,模板中{{ form|crispy }}渲染为空,最终只显示按钮。此外,自定义PasswordResetSerializer若未对齐原生逻辑,也会加剧该问题。
解决方案
方案1:统一DRF与原生的UID生成逻辑
修改自定义PasswordResetSerializer,采用Django原生方式生成uidb64和token:
# api/serializers.py from django.contrib.auth.tokens import default_token_generator from django.utils.encoding import force_bytes from django.utils.http import urlsafe_base64_encode from django.core.mail import send_mail from django.conf import settings from rest_framework import serializers from django.contrib.auth import get_user_model User = get_user_model() class PasswordResetSerializer(serializers.Serializer): email = serializers.EmailField() def validate_email(self, value): self.user = User.objects.filter(email=value).first() if not self.user: raise serializers.ValidationError("用户不存在") return value def save(self): # 原生方式生成uidb64和token uid = urlsafe_base64_encode(force_bytes(self.user.pk)) token = default_token_generator.make_token(self.user) # 构建指向网站重置视图的链接 reset_url = f"{settings.FRONTEND_DOMAIN}/password-reset-confirm/{uid}/{token}/" # 发送重置邮件 subject = "重置你的密码" message = f"请点击以下链接重置密码:{reset_url}" send_mail(subject, message, settings.DEFAULT_FROM_EMAIL, [self.user.email])
方案2:修改视图兼容UUID/字符串格式UID
若用户模型使用UUID作为主键,修改PasswordResetConfirmView以支持多格式UID解析:
from django.utils.encoding import force_str, force_bytes from django.utils.http import urlsafe_base64_decode, urlsafe_base64_encode from django.contrib.auth.tokens import default_token_generator from django.contrib.auth.views import PasswordResetConfirmView from django.contrib.auth import get_user_model from django.shortcuts import redirect User = get_user_model() class CustomPasswordResetConfirmView(PasswordResetConfirmView): form_class = SetPasswordForm success_url = reverse_lazy('users:password_reset_complete') template_name = 'users/password_reset_confirm.html' def dispatch(self, request, *args, **kwargs): self.user = None # 先尝试解析UUID格式UID try: uid = force_str(urlsafe_base64_decode(kwargs['uidb64'])) self.user = User.objects.get(pk=uid) except (TypeError, ValueError, OverflowError, User.DoesNotExist): # 回退到整数ID解析 try: uid = force_str(urlsafe_base64_decode(kwargs['uidb64'])) self.user = User.objects.get(pk=int(uid)) except (TypeError, ValueError, OverflowError, User.DoesNotExist): pass if self.user is None or not default_token_generator.check_token(self.user, kwargs['token']): return redirect('password_reset_invalid') return super().dispatch(request, *args, **kwargs)
方案3:修正邮件模板链接路径
确保DRF发送的重置链接指向网站视图而非API路径,若使用dj-rest-auth可在settings中配置:
# settings.py DJ_REST_AUTH = { 'PASSWORD_RESET_CONFIRM_URL': 'password-reset-confirm/{uidb64}/{token}/', }
同时修改邮件模板,移除/api/前缀:
{% block reset_link %} {{ protocol }}://{{ domain }}/{% url 'password_reset_confirm' uidb64=uid token=token %} {% endblock %}
内容的提问来源于stack exchange,提问作者A_K
相关产品推荐
相关产品推荐

