Django 3.1 auth模块:如何验证重置密码Token并更新用户密码?
Django 3.1 纯API应用密码重置后续流程问题
已实现的密码重置发起视图
基于Django 3.1的auth模块开发纯API应用,已实现发起密码重置的视图:
class ResetPasswordView(SuccessMessageMixin, PasswordResetView): reset_password_template_name = 'templates/users/password_reset.html' email_template_name = 'users/password_reset_email.html' subject_template_name = 'users/password_reset_subject' success_message = "We've emailed you instructions for setting your password, " \ "if an account exists with the email you entered. You should receive them shortly." \ " If you don't receive an email, " \ "please make sure you've entered the address you registered with, and check your spam folder." success_url = reverse_lazy('users-home') @method_decorator(csrf_exempt) def dispatch(self, request, *args, **kwargs): request.csrf_processing_done = True return super().dispatch(request, *args, **kwargs) def post(self, request, *args, **kwargs): email = json.loads(request.body).get('username') try: if User.objects.get(email=email).is_active: form = PasswordResetForm({'email': email}) print("form valid? %s" % form.is_valid()) if form.is_valid(): request = HttpRequest() request.META['SERVER_NAME'] = socket.gethostbyname('localhost') #'127.0.0.1' request.META['SERVER_PORT'] = 8000 # calling save() sends the email # check the form in the source code for the signature and defaults form.save(request=request, use_https=False, from_email="laredotornado@yahoo.com", email_template_name='../templates/users/password_reset_email.html') print("email: %s " % email) return super(ResetPasswordView, self).post(request, *args, **kwargs) except Exception as e: print("\n\nerror ...\n\n") print(e) # this for if the email is not in the db of the system return super(ResetPasswordView, self).post(request, *args, **kwargs)
生成的重置链接示例
视图发送的邮件中包含如下格式的重置链接:
http://127.0.0.1:8000/password-reset-confirm/Mg/bhd3nc-29fa9003c9c61c2bda5cff0a66b38bdf/
问题
如何将链接中的Token与用户设置的新密码提交至服务器,完成Token验证并更新用户密码?
补充的错误栈追踪
尝试过程中出现如下错误:
Traceback (most recent call last): File "/Users/davea/Documents/workspace/chicommons/maps/web/venv/lib/python3.9/site-packages/django/core/handlers/exception.py", line 47, in inner response = get_response(request) File "/Users/davea/Documents/workspace/chicommons/maps/web/venv/lib/python3.9/site-packages/django/core/handlers/base.py", line 181, in _get_response response = wrapped_callback(request, *callback_args, **callback_kwargs) File "/Users/davea/Documents/workspace/chicommons/maps/web/venv/lib/python3.9/site-packages/django/views/generic/base.py", line 70, in view return self.dispatch(request, *args, **kwargs) File "/Users/davea/Documents/workspace/chicommons/maps/web/venv/lib/python3.9/site-packages/django/utils/decorators.py", line 43, in _wrapper return bound_method(*args, **kwargs) File "/Users/davea/Documents/workspace/chicommons/maps/web/venv/lib/python3.9/site-packages/django/views/decorators/csrf.py", line 54, in wrapped_view return view_func(*args, **kwargs) File "/Users/davea/Documents/workspace/chicommons/maps/web/directory/views.py", line 395, in dispatch return super().dispatch(request, **data) File "/Users/davea/Documents/workspace/chicommons/maps/web/venv/lib/python3.9/site-packages/django/utils/decorators.py", line 43, in _wrapper return bound_method(*args, **kwargs) File "/Users/davea/Documents/workspace/chicommons/maps/web/venv/lib/python3.9/site-packages/django/views/decorators/debug.py", line 89, in sensitive_post_parameters_wrapper return view(request, *args, **kwargs) File "/Users/davea/Documents/workspace/chicommons/maps/web/venv/lib/python3.9/site-packages/django/utils/decorators.py", line 43, in _wrapper return bound_method(*args, **kwargs) File "/Users/davea/Documents/workspace/chicommons/maps/web/venv/lib/python3.9/site-packages/django/views/decorators/cache.py", line 44, in _wrapped_view_func response = view_func(request, *args, **kwargs) File "/Users/davea/Documents/workspace/chicommons/maps/web/venv/lib/python3.9/site-packages/django/contrib/auth/views.py", line 260, in dispatch assert 'uidb64' in kwargs and 'token' in kwargs 异常类型: AssertionError at /password-reset-complete
解决方案
1. 实现密码重置确认API视图
继承PasswordResetConfirmView,适配API场景,处理JSON请求并完成Token验证、密码更新:
from django.contrib.auth.views import PasswordResetConfirmView from django.utils.decorators import method_decorator from django.views.decorators.csrf import csrf_exempt from django.http import JsonResponse from django.contrib.auth.tokens import default_token_generator from django.utils.http import urlsafe_base64_decode from django.contrib.auth.models import User import json @method_decorator(csrf_exempt, name='dispatch') class ResetPasswordConfirmAPIView(PasswordResetConfirmView): def dispatch(self, request, *args, **kwargs): request.csrf_processing_done = True return super().dispatch(request, *args, **kwargs) def post(self, request, uidb64, token, *args, **kwargs): try: # 解析请求体中的新密码 data = json.loads(request.body) new_password = data.get('new_password') confirm_password = data.get('confirm_password') # 校验密码一致性 if not new_password or new_password != confirm_password: return JsonResponse({'status': 'error', 'message': '两次密码输入不一致'}, status=400) # 解码用户ID并获取用户 uid = urlsafe_base64_decode(uidb64).decode() user = User.objects.get(pk=uid) # 验证重置Token有效性 if not default_token_generator.check_token(user, token): return JsonResponse({'status': 'error', 'message': '重置链接无效或已过期'}, status=400) # 更新用户密码 user.set_password(new_password) user.save() return JsonResponse({'status': 'success', 'message': '密码重置成功'}) except User.DoesNotExist: return JsonResponse({'status': 'error', 'message': '用户不存在'}, status=404) except json.JSONDecodeError: return JsonResponse({'status': 'error', 'message': '请求格式错误,需提交JSON数据'}, status=400) except Exception as e: return JsonResponse({'status': 'error', 'message': str(e)}, status=500)
2. 配置正确的URL路由
确保重置确认视图的URL包含uidb64和token参数,避免路由错误:
from django.urls import path from .views import ResetPasswordView, ResetPasswordConfirmAPIView urlpatterns = [ # 发起密码重置 path('password-reset/', ResetPasswordView.as_view(), name='password_reset'), # 密码重置确认API path('password-reset-confirm/<uidb64>/<token>/', ResetPasswordConfirmAPIView.as_view(), name='password_reset_confirm'), # 纯API应用可移除password-reset-complete路由,无需前端跳转页面 ]
3. 前端提交请求示例
前端通过POST请求提交新密码,请求URL为邮件中的重置链接:
// 示例:使用fetch提交请求 const resetUrl = 'http://127.0.0.1:8000/password-reset-confirm/Mg/bhd3nc-29fa9003c9c61c2bda5cff0a66b38bdf/'; const newPassword = 'your_secure_new_password'; fetch(resetUrl, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ new_password: newPassword, confirm_password: newPassword }) }) .then(response => response.json()) .then(result => { if (result.status === 'success') { console.log('密码重置成功'); } else { console.error('重置失败:', result.message); } }) .catch(error => console.error('请求出错:', error));
解决AssertionError错误
错误原因是/password-reset-complete路由绑定了需要uidb64和token参数的视图,或路由配置错误。处理方式:
- 确保
password-reset-confirm路由包含<uidb64>和<token>参数,且对应到正确的确认视图 - 纯API应用无需
password-reset-complete页面,直接移除该路由即可
内容的提问来源于stack exchange,提问作者Dave
相关产品推荐
相关产品推荐

