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)加密算法,有人能专门撰文研究了破解这类密码所耗费的时间.

 

 

 

 

posted @ 2019-04-18 16:03  taoziya  阅读(458)  评论(0)    收藏  举报