2.5 关于密码的操作
有关密码的操作,常见的就是重置密码、修改密码和找回密码。关于密码的操作如下图所示。

2.5.1 修改密码
在Django中默认有修改密码的方法,其使用方法与前述的用户登录的内置方法类似。先浏览一下其源码,其位置与内置的login()在同一个文件中。
1 class PasswordChangeView(PasswordContextMixin, FormView): 2 form_class = PasswordChangeForm 3 success_url = reverse_lazy('password_change_done') 4 template_name = 'registration/password_change_form.html' 5 title = _('Password change') 6 7 @method_decorator(sensitive_post_parameters()) 8 @method_decorator(csrf_protect) 9 @method_decorator(login_required) 10 def dispatch(self, *args, **kwargs): 11 return super().dispatch(*args, **kwargs) 12 13 def get_form_kwargs(self): 14 kwargs = super().get_form_kwargs() 15 kwargs['user'] = self.request.user 16 return kwargs 17 18 def form_valid(self, form): 19 form.save() 20 # Updating the password logs out all other sessions for the user 21 # except the current one. 22 update_session_auth_hash(self.request, form.user) 23 return super().form_valid(form) 24 25 26 class PasswordChangeDoneView(PasswordContextMixin, TemplateView): 27 template_name = 'registration/password_change_done.html' 28 title = _('Password change successful') 29 30 @method_decorator(login_required) 31 def dispatch(self, *args, **kwargs): 32 return super().dispatch(*args, **kwargs)
学习计算机编程语言,官方文档是重要的资料,一定要阅读官方文档。
下面来看看这两个函数的参数,重点是看模板的位置和文件名,然后到我们设置的模板目录./templates/registration中查看是否存在上述两个文件,如果没有,可以自己创建或者从Django安装文件的模板中复制过来。在前面操作中已经完成了复制,所以在该目录中存在password_change_form.html和password_change_done.html文件。
设定修改密码的URL,编辑./account/urls.py文件,代码如下:
1 from django.urls import path 2 from .import views 3 from django.contrib.auth import views as auth_views 4 5 app_name = 'account' 6 urlpatterns = [ 7 # path('login/',views.user_login,name='user_login'), #自定义的登录 8 # path('login/',auth_views.LoginView,name='user_login'), 9 path('login/',auth_views.LoginView.as_view(template_name='account/login2.html'),name='user_login'), #django内置的登录 10 path('logout/',auth_views.LogoutView.as_view(template_name='account/logout.html'),name='user_logout'), 11 path('register/',views.register,name="user_register"), 12 # path('password-change/',auth_views.PasswordChangeView, 13 # {"post_change_redirect":"/account/password-change-done"},name='password_change'), 14 # path('password-change-done/',auth_views.PasswordChangeDoneView,name='password_change_done'), 15 path('password-change/',auth_views.PasswordChangeView.as_view(template_name='registration/password_change_form.html'), 16 name='password_change'), 17 path('password-change-done/',auth_views.PasswordChangeDoneView.as_view(template_name ='registration/password_change_done.html' ) 18 ,name='password_change_done'), 19 ]
观察一下模板目录中的两个模板,它们都是默认的模板文件格式。我们还是自定义模板吧,方法有两个,一个是修改./templates/registration/里面的模板文件,另一个是在./templates/account/中创建新的模板文件,并利用前面已经学习的方法,在URL配置中指定模板文件。
这次采用前者。为了突出学习的重点,笔者删除了原有模板的很多内容,重点表现修改密码的部分,当然也兼顾使用了bootstrap的一贯风格。./templates/registration/password_change_form.html的代码如下:
1 {% extends "base.html" %} 2 {% block title %}passowrd change{% endblock %} {% block content %} 3 <div class="row text-center vertical-middle-sm"> 4 <h1>Change Password</h1> 5 <p>Please enter your old password, for security's sake, and then enter your new password twice so we can verify you typed it in correctly.</p> 6 {% if form.new_password1.help_text %} 7 <div class="text-left" style="margin-left:400px"> 8 <p>{{ form.new_password1.help_text|safe }}</p> 9 </div> 10 {% endif %} 11 12 <form class="form-horizontal" action="." method="post">{% csrf_token %} 13 <div class="form-group"> 14 <label class='col-md-5 control-label text-right'> 15 {{ form.old_password.label_tag }} 16 </label> 17 <div class="col-md-6 text-left">{{ form.old_password }}</div> 18 </div> 19 20 <div class="form-group"> 21 <label class='col-md-5 control-label text-right'> 22 {{ form.new_password1.label_tag }} 23 </label> 24 <div class="col-md-6 text-left">{{ form.new_password1 }}</div> 25 </div> 26 27 <div class="form-group"> 28 <label class="col-md-5 control-label text-right"> 29 {{ form.new_password2.label_tag }} 30 </label> 31 <div class="col-md-6 text-left">{{ form.new_password2 }}</div> 32 </div> 33 34 <input type="submit" value="Change my password" class="btn btn-primary btn-lg"/> 35 </form> 36 </div> 37 {% endblock %}
./templates/registration/password_change_done.html的代码重写如下:
1 {% extends "base.html" %} 2 {% block title %}password change done{% endblock %} 3 {% block content %} 4 <div class="row text-center vertical-middle-sm"> 5 <p>Your password was changed.</p> 6 </div> 7 {% endblock %}
先用一个已经存在的账户实现登录,然后另外打开一个网页,输入http://127.0.0.1:8000/account/password-change/,结果如下图所示。

在保证用户已经登录的状态下,重复访问修改密码的界面,如下图所示,成功!还是用这种方法比较好,不要去修改内置方法的源码。

当用户登录之后,在导航条上会显示该用户的用户名,可以将修改密码的入口放在这里。单击用户名,出现一个下拉菜单,单击“修改密码”,跳转到刚才已经建立的修改密码页面。
修改一个模板文件./templates/header.html,修改之后其代码如下:
1 {% load staticfiles %} 2 <div class="container"> 3 <nav class="navbar navbar-default" role="navigation"> 4 <div class="navbar-header"> 5 <a class="navbar-brand" href="http://www.itdiffer.com"><img src="{%static '/images/logo.png'%}" 6 width="100px"></a> 7 </div> 8 <div> 9 <ul class="nav navbar-nav" role="navigation"> 10 <li><a href="{% url 'blog:blog_title' %">BLOG</a> </li> 11 </ul> 12 <ul class="nav navbar-nav navbar-right" style="margin-right:10px"> 13 {% if user.is_authenticated %} 14 <li><!-以下为新增--> 15 <div class="dropdown" style="margin-top:8px"> 16 <button class="btn btn-default dropdown-toggle" type="button" id="dropdownMenu" 17 data-toggle="dropdown">{{ user.username }}<span class="caret"></span> </button> 18 <u1 class="dropdown-menu"> 19 <li><a href="{% url 'account:password_change' %}">修改密码</a> </li> 20 </u1> 21 </div><!-修改结束--> 22 </li> 23 <li><a href="#">{{ user.username }}</a> </li> 24 <li><a href="{% url 'account:user_logout' %}">Logout</a> </li> 25 {% else %} 26 <li><a href="{% url 'account:user_login' %}">LOGIN</a> </li> 27 {% endif %} 28 </ul> 29 </div> 30 </nav> 31 </div> 32 <script src="{% staticjs/jquery-3.3.1.js%}"></script>
33 <script src="{% static 'js/bootstrap.js' %}"></script>
上述代码中<script>部分不要丢失,因为我们在修改密码的功能上使用了bootstrap提供的下拉菜单功能,需要JavaScript脚本支持。
修改后,检查Django服务是否启动,并且保证用户正处于退出状态。然后重新登录,在导航条的右侧显示用户名的位置单击鼠标,在下拉菜单中就能看到“修改密码”了,如下图所示。

点击‘change my password’按钮,页面跳转到password-change-done.html页面,这时表明,该用户的密码已修改成功。

2.5.2 重置密码
如果用户忘记了密码怎么办?找回是不可能的,因为网站对用户的密码都进行了加密,而且好的加密方法都是不可逆的,常用的加密算法有MD5、SHA1等。
Django默认的用户管理系统也自带了加密模块。比如本书前述章节的实例中,用户注册、登录时要输入密码,读者注意观察数据库中用户的密码,显示的不是在键盘上所输入的字符,而是加密后的效果。
如果这个密码被用户忘记了,就不能“找回”,只能“重置”。
Django提供了重置密码的默认功能。因为是内置方法,所以我们还是先去查看一下那个视图文件C:\Python35\Lib\site-packages\django\contrib\auth/views.py,它里面有几个命名中包含了passwrod_reset的函数,这几个函数都是用来重置密码的,并且该视图文件的注释已经说明了重置密码的基本步骤。
1、向用户发送邮件,使用PasswordResetView()类函数。
1 class PasswordResetView(PasswordContextMixin, FormView): 2 email_template_name = 'registration/password_reset_email.html' 3 extra_email_context = None 4 form_class = PasswordResetForm 5 from_email = None 6 html_email_template_name = None 7 subject_template_name = 'registration/password_reset_subject.txt' 8 success_url = reverse_lazy('password_reset_done') 9 template_name = 'registration/password_reset_form.html' 10 title = _('Password reset') 11 token_generator = default_token_generator 12 #...(函数体代码从略)
此函数的几个参数是重点研究对象。
①template_name = 'registration/password_reset_form.html',这条语句是发送邮件的表单模板,因为前文中已经重新规范了本项目的模板位置,所以它应该在./templates/registration/password_reset_form.html,为了保持一贯的风格,可以将它重写,当然还可以在URL中传入新模板文件参数,提倡使用后者。所以在./templates/account中创建password_reset_form.html文件,代码如下。
{% entends "base.html" %} {% block title %}password reset{% endblock %} {% block content %} <div class="row text-center vertical-middle-sm"> <h1>Forgotten your password? Reset,please.</h1> <p>Enter your email to set a new password.</p> <form class="form-horizontal" action="." method="post">{% csrf_token %} <div class="form-group"> <label class="col-md-5 control-label text-right">Email</label> <div class="col-md-6 text-left">{{ form.email }}</div> </div> <input type="submit" value="Send email" class="btn btn-primary btn-lg"> </form> </div> {% endblock %}
②email_template_name = 'registration/password_reset_email.html',这条语句表示这个模板是发送给用户的邮件内容。创建./templates/account/password_reset_email.html文件,代码如下。
1 <p>You're receiving this email because you requested a password reset for your user account 2 at <a href="http://www.itdiffer.com">itdiffer.com</a> </p> 3 <p>Please go to the following page and choose a new password:</p> 4 {{ protocol }}://{{ domain }}{% url 'account:password_reset_confirm' uidb64=uid 5 token=token %} 6 <p>Your username,in case you're forgotten:{{ user.get_username }}</p> 7 <p>Thanks for using our site!</p> 8 <p>The itdiffer.com team</p>
③subject_template_name = 'registration/password_reset_subject.txt',这条语句表示这个文件中的内容将是所发邮件的主题,也可以用类似的方法创建./templates/registration/password_reset_subject.txt
2、PasswordResetDoneView()函数,显示发送成功的信息。
1 class PasswordResetDoneView(PasswordContextMixin, TemplateView): 2 template_name = 'registration/password_reset_done.html' 3 title = _('Password reset sent')
上述代码对应的模板是./templates/account/password_reset_done.html,其代码如下。
1 {% extends "base.html" %} 2 {% block title %}password reset{% endblock %} 3 {% block content %} 4 <div class="row text-center vertical-middle-sm"> 5 <h1>Reset your password</h1> 6 <p>We're emailed you instructions for setting your password,if an account exists with the email you entered. 7 You should receive them shortly.</p> 8 <p>If you don't receive an email,please make sure you've entered the address you registered with,and check 9 your spam folder.</p> 10 </div> 11 {% endblock %}
3、用户到自己的邮箱中查看邮件,单击邮件中的链接,根据URL的配置就会调用视图函数password_reset_confirm(),然后输入新的密码。
1 class PasswordResetConfirmView(PasswordContextMixin, FormView): 2 form_class = SetPasswordForm 3 post_reset_login = False 4 post_reset_login_backend = None 5 success_url = reverse_lazy('password_reset_complete') 6 template_name = 'registration/password_reset_confirm.html' 7 title = _('Enter new password') 8 token_generator = default_token_generator
# ...
从上述源码中可以看出,其参数也要求有一个模板文件。所以,仍然要创建一个新的模板文件./templates/account/password_reset_confirm.html,代码如下。
1 {% extends "base.html" %} 2 {% block title %}password reset{% endblock %} 3 4 {% block content %} 5 <div class="row text-center vertical-middle-sm"> 6 <h1>Reset Password</h1> 7 <p>Please enter your new password twice so we can verify you typed it in correctly.</p> 8 <form class="form-horizontal" action="." method="post">{% csrf_token %} 9 <div class="form-group"> 10 <label class="col-md-5 control-label text-right">New password</label> 11 <div class="col-md-6 text-left">{{ form.new_password }}</div> 12 </div> 13 <div class="form-group"> 14 <label class="col-md-5 control-label text-right">Confirm password</label> 15 <div class="col-md-6 text-left">{{ form.new_password2 }}</div> 16 </div> 17 <input type="submit" value="Change my password" class="btn btn-primary btn-lg"> 18 </form> 19 </div> 20 {% endblock %}
4、如果重置密码成功,就进行到最后一步了,显示 成功信息,这里使用的视图函数就password_reset_complete(),代码如下。
1 class PasswordResetCompleteView(PasswordContextMixin, TemplateView): 2 template_name = 'registration/password_reset_complete.html' 3 title = _('Password reset complete') 4 #...
当然也需要用户创建这个视图函数所要求的模板文件./templates/account/password_reset_complete.html,代码如下。
1 {% extends "base.html" %} 2 {% block title %}password reset{% endblock %} 3 {% block content %} 4 <div class="row text-center vertical-middle-sm"> 5 <h1>Reset your password</h1> 6 <p>Your password has been set.You may go ahead and <a href="{% url 'account:user_login' %}">log in now</a>.</p> 7 </div> 8 {% endblock %}
模板都创建好了,也不需要写视图函数,下面就可以配置URL了。编辑./account/urls.py文件,增加如下代码。
1 from django.urls import path 2 from .import views 3 from django.contrib.auth import views as auth_views 4 5 app_name = 'account' 6 urlpatterns = [ 7 # path('login/',views.user_login,name='user_login'), #自定义的登录 8 # path('login/',auth_views.LoginView,name='user_login'), 9 path('login/',auth_views.LoginView.as_view(template_name='account/login2.html'),name='user_login'), #django内置的登录 10 path('logout/',auth_views.LogoutView.as_view(template_name='account/logout.html'),name='user_logout'), 11 path('register/',views.register,name="user_register"), 12 # path('password-change/',auth_views.PasswordChangeView, 13 # {"post_change_redirect":"/account/password-change-done"},name='password_change'), 14 # path('password-change-done/',auth_views.PasswordChangeDoneView,name='password_change_done'), 15 # path('password-change/',auth_views.PasswordChangeView.as_view(template_name='registration/password_change_form.html'), 16 # name='password_change'), 17 # path('password-change-done/',auth_views.PasswordChangeDoneView.as_view(template_name ='registration/password_change_done.html' ) 18 # ,name='password_change_done'), 19 path('password-change/',auth_views.PasswordChangeView.as_view(template_name='account/password_change_form.html', 20 success_url="/account/password-change-done/"),name='password_change'), 21 path('password-change-done/',auth_views.PasswordChangeDoneView.as_view(template_name ='account/password_change_done.html'), 22 name='password_change_done'), 23 path('password-reset/',auth_views.PasswordResetView.as_view(template_name='account/password_reset_form.html', 24 email_template_name="account/password_reset_email.html",success_url="/account/password-reset-done/"),name='password_reset'), 25 path('password-reset-done/',auth_views.PasswordResetDoneView.as_view(template_name ='account/password_reset_done.html'), 26 name='password_reset_done'), 27 path('password-reset-confirm/<uidb64>/<token>/',auth_views.PasswordResetConfirmView.as_view(template_name='account/password_reset_confirm.html', 28 success_url='/account/password-reset-complete/'),name='password_reset_confirm'), 29 path('password-reset-complete/',auth_views.PasswordResetCompleteView.as_view(template_name ='account/password_reset_complete.html'), 30 name='password_reset_complete'),
这里使用网易的邮箱来发送邮件(目前只有163邮箱互发能接收到),QQ邮箱还不能收到,至少我这边是没接收到。
先来说一说发件箱的设置,至于如何在网易申请免费邮箱,本文不在赘余,只介绍需要注意的发件箱的设置。


我们的意图是在一个服务器上向另一个服务器发送邮件。要实现邮件发送功能,必须要配置邮件发送服务器。在Django中,配置此功能比较简单,编辑./testsite/settings.py文件,在末尾增加如下代码。
1 EMAIL_USE_SSL = True
2 EMAIL_HOST = 'smtp.163.com' #如果是163 改成smtp.163.com
3 EMAIL_PORT = 465
4 EMAIL_HOST_USER = "taizixiaodouding@163.com" #发件人账号
5 EMAIL_HOST_PASSWORD = "rms880909" #发件邮箱的授权码
6 DEFAULT_FROM_EMAIL = EMAIL_HOST_USER
7 # EMAIL_BACKEND ='django.core.mail.backends.console.EmailBackend' #用来在控制台显示发送消息的,不注释的话,登录收件箱的是接收不到邮件的。
运行Django服务,打开浏览器,输入http://127.0.0.1:8000/account/password-reset/,并输入该用户注册时的邮箱(建议使用163邮箱),如下图所示:

点击‘send email’按钮,页面跳转到password-reset-done.html

登录收件邮件查看,是否收到新的邮件。


其中,在./testsite/settings.py中的最后一行代码
EMAIL_BACKEND ='django.core.mail.backends.console.EmailBackend' #作用:将发送的邮件直接显示在控制台

以上重置密码过程使用了Django的内置方法。
2.5.3 利用第三方应用重置密码
首先安装第三方的重置密码应用

安装完成后,在./testsite/settings.py中,将password_reset增加为INSTALLED_APPS中的一项。

接下来,为这个重置密码的应用设置URL,即编辑./testsite/urls.py文件。
1 urlpatterns = [ 2 path('admin/', admin.site.urls), 3 path('blog/',include('blog.urls',namespace='blog')), 4 path('account/',include('account.urls',namespace='account')), 5 path('pwd_reset/',include('password_reset.urls',namespace='pwd_reset')), 6 ]
找到password_reset所在Python第三方库的文件夹C:\Python36-32\Lib\site-packages\password_reset,将其中的templates目录中的password_reset连带里面的文件,复制到本项目的./templates中。除templates里的password_reset外其余的都拷贝到./testsite目录下。如下图所示:

修改./password_reset里面的urls.py(因为该模块还是用的Django1,我现在用的是Django2)
1 from django.urls import path 2 from . import views 3 4 app_name = 'password_reset' 5 6 7 urlpatterns = [ 8 path('recover/<signature>/', views.recover_done,name='password_reset_sent'), 9 path('recover/', views.recover, name='password_reset_recover'), 10 path('reset/done/', views.reset_done, name='password_reset_done'), 11 path('reset/<token>/', views.reset,name='password_reset_reset'), 12 ]
重新启动Django服务,在浏览器的地址栏中输入http://127.0.0.1:8000/pwd_reset/recover/

这个应用的优势在于,它允许输入用户名或者邮箱,不管输入什么,都能够向该用户的邮箱发送重置密码的邮件。

点击重设密码按钮后,页面报错

暂时还没解决。
最后要完成重置密码的入口设置,用户在哪里开始操作重置密码呢?一般是在登录的时候,所以要在登录页面做重置密码的链接。编辑./templates/registration/login.html文件,增加代码如下:
1 </form> 2 <p style="margin-top:10px">Forgot your password? <a href="{% url 'password_reset:password_reset_recover' %}"> 3 reset password</a></p> 4 <p style="margin-top:10px">If you had not a username,<a href="{% url 'account:user_register' %}"> 5 Register to be a user</a>, please</p> 6 </div> 7 {% endblock %}
2.5.4 知识点
1、密码强度
维基百科中如此定义“密码强度”:指一个密码对抗猜测或是暴力破解的有效程度。一般来说,指一个未授权的访问者得到正确密码的平均尝试次数。密码的强度和其长度、复杂度及不可预测度有关。强密码可以降低安全漏洞的整体风险,但不能降低采取其他安全措施的需要。
下面是一些常见的简单密码(源自维基百科的总结),不知道你是否躺着中枪了。
- 顺序或重复的字符:“12345678”、“111111”、“abcdefg”、“asdf”、“qwer”(键盘上的相邻字母)。
- 使用数字或符号的仅外观类似替换,例如使用数字“1”、“0”替换英文字母“i”、“o”,字符“@”替换字母“a”等。
- 登录名的一部分:密码为登录名的一部分或完全与登录名相同。
- 常用的单词或者汉语拼音:如自己和熟人的名字及其缩写、常用的单词及其缩写、宠物的名字等。
- 常用数字:比如自己或熟人的生日、证件编号等,以及这些数字与名字、称号等字母的简单组合。
2、Django的密码管理
比较常用的这类算法有(不仅仅限于如下几种):
- MD5
- PBKDF2
- Bcrypt
查看本项目已经创建的数据库表auth_user(Django默认的用户管理应用的数据库表),会发现每个注册用户的密码都是一大串字符串,虽然我们在完成用户注册功能时,并没有直接使用有关“加密”的函数,但Django依然为我们默默地将用户的密码加密了,如下图所示。

Django默认使用PBKDF2(Password-Based Key Derivation Function2)加密算法,有人能专门撰文研究了破解这类密码所耗费的时间.

浙公网安备 33010602011771号