Flask Web 开发 用户认证_6

来源:互联网 发布:linux系统大全 编辑:程序博客网 时间:2024/05/19 00:52

握草,终于进入用户认证的最终章节了,觉得作者不错,到了这里,已经开始让你尝试自己写代码了

虽然在github上面 Miguelgrinberg 也放上了代码,不过还是尽量自己写吧


首先是对于已经注册认证的用户,他们有时候想修改密码,那我们肯定要为用户专门放一个页面,用来修改密码

那无非是做一个表单和页面,通过表单来链接数据库修改最后的密码

以我们的经验,一般这样的表单有3行,老密码,新密码,确认新密码

所以如下(本来form和路由都自己编名字了。。。。后来发现到后面和书对照起来太麻烦了。。。还是老实点先按照书来做吧。。。。)

class ChangePasswordForm(Form):
 oldpassword=PasswordField('Oldpassword',validators=[Required()])                  
 newpassword=PasswordField('Newpassowrd',validators=[Required(),EqualTo('newpassword2',message='Password must match.')])
 newpassword2=PasswordField('Confirm password',validators=[Required()])
 submit = SubmitField('Register')


而对应的路由设置如下:

@auth.route('/change_password',methods=['GET','POST'])    #修改密码页面
@login_required                                                                       #保护路由,说明要求是在登录状态才能操作
def change_password():
 form = ChangePasswordForm()                                              
 if form.validate_on_submit():
  if current_user.verify_password(form.oldpassword.data):         #在表单提交有效的情况下,如果当前用户表单内输入的老密码验证返回结果是True
   current_user.password=form.newpassword.data                     #则当前用户的密码更新为表单里面的newpassword(这里用到的是password.setter装饰器)
   db.session(current_user)                                                          #提交更新
   db.session.commit()
   return redirect(url_for('main.index'))
  else:
   flash('Your oldpassword is wrong')                                            #不然的话,出现提示消息,老密码错误
 return render_template('auth/change_password.html',form = form)


这里需要注意的是:current_user实际上可以作为object来直接使用的,db.session.add(current_user)就可以显示这个作用


后端逻辑做完了,那前段页面也要做一个

{% extends "base.html" %}
{% import "bootstrap/wtf.html" as wtf %}
{% block title %}Flasky-Change Password{% endblock %}
{% block page_content %}
<div class="page-header">
 <h3>Please reset your password as below</h3>
</div>

{{wtf.quick_form(form)}}                     #快速装饰表单
{% endblock %}



效果图如下:


需要注意的是,我们这里添加了一个change password的按钮,这个超链接最好还是放在base.html里面


这样我们的密码修改功能就完成了







--------------------------------------------------------------------------功能分割线-----------------------------------------------------------------------------------------------


但是有时候你忘记了密码,那你连改密码都改不了

所以就要用到忘记密码功能了,这个功能的作用基本上就是------>通过邮箱发送给你一个链接------->点击链接进入修改密码页面--------->设置新密码


对于我们平时的经验来说,一般是通过邮箱先认证一下,再修改密码

那这样,等于是要有2个页面产生,一个是让你输入邮箱并发送邮件的页面,第二个是你邮箱点击链接返回过来的页面,可以修改密码

而且,需要做2个表单,一个是输入邮箱并发送的表单,另外一个是修改密码的表单


先来看要输入email地址的表单类

class PasswordResetRequestForm(Form):
 email=StringField('Email Address',validators=[Required(),Length(1,64),Email()])
 submit = SubmitField('Send out')                              #提交表单以发送EMAIL


再来看修改密码的表单类

class PasswordResetForm(Form):

email = StringField('Email Address',validators=[Required(),Email()])   #这一行特别注意,如果没有这一行的话,你到最后路由里面,没有办法定位你的具体账号的。
 newpassword=PasswordField('Newpassowrd',validators=[Required(),EqualTo('newpassword2',message='Password must match.')])
 newpassword2=PasswordField('Confirm password',validators=[Required()])
 submit = SubmitField('Save Change')

 def validate_email(self, field):                                              #这里前面学过的东西差点又忘记了,以validate_开头的函数,会和普通验证函数一起被调用
 if User.query.filter_by(email=field.data).first() is None:
  raise ValidationError('Unknown email address.')



有几个新表单,那就有几个新页面,同时也要有几个新路由

我们接着看新路由

@auth.route('/reset_password',methods=['GET','POST'])        #密码忘记通过邮件申请页面
def password_reset_request():                                                   #名字这样取,容易记,是输入email地址的页面page

   if not current_user.is_anonymous:                           
       return redirect(url_for('main.index'))                                                  
 form = SendResetEmail()
 if form.validate_on_submit():
  user = User.query.filter_by(email=form.email.data).first()   #通过表单上的email地址,查询数据库并把用户信息赋值给user
  if user is None:                                                                    #但是,不排除没有这个email地址的可能,所以,如果user返回的结果是None的话
   flash('This email does not exist , please check again !')   #出现提示消息,这个email不存在,请重新确认
  else:
   token = user.generate_reset_password_token()             #如果用户存在,则生成一个修改密码的token,作用和前面的confirmation的token类似
   send_email(user.email,'Reset Password','auth/email/rest_password',user=user,token=token,next=request.args.get('next'))  

   #EMAIL内容使用模板'auth/email/rest_password.html'来进行渲染,但是,最后为什么要加上next这个参数和内容,始终没搞懂


   flash('Please check the email in your inbox !')
 return render_template('auth/reset_password.html',form=form) #而整个输入email地址的页面,则通过send_reset_email_page.html来渲染


Email的渲染模板templates/auth/email/reset_password.html的内容,如下:

<p>Dear {{ user.username }},</p>
<p>Welcome to <b>Flasky</b>!</p>
<p>To reset your password please <a href="{{ url_for('auth.reset_password', token=token, _external=True) }}">click here</a>.</p>
<p>Alternatively, you can paste the following link in your browser's address bar:</p>
<p>{{ url_for('auth.reset_password', token=token, _external=True) }}</p>
<p>Sincerely,</p>
<p>The Flasky Team</p>
<p><small>Note: replies to this email address are not monitored.</small></p>


模板内容,渲染PasswordResetRequestForm表单

{% extends "base.html" %}
{% import "bootstrap/wtf.html" as wtf %}
{% block title %}Flasky-Reset Password Email{% endblock %}
{% block page_content %}
<div class="page-header">
 <h1>Send Email to Reset</h1>
</div>
<div class='col-md-4'>
{{wtf.quick_form(form)}}
</div>
{% endblock %}


而上面我们用到一个函数,叫做generate_reset_password_token,这个函数的作用,其实和confirm里面的token作用是相似的

只是对于重新设置密码这个功能来说,我们是需要自己新建一个的,因为confirm里面最后的目标是修改confirmed属性,而我们这里不需要,我们最终目标是修改密码

那增加功能,就要在models里面修改了,如下:

def generate_reset_password_token(self,expiration=3600):
  s=Serializer(current_app.config['SECRET_KEY'],expiration)
  return s.dumps({'reset':self.id})                                                 #注意,这里加密令牌时,key值自己定义为reset吧,这样逻辑功能比较清晰点。
 
 def reset_password(self,token,new_password):
  s=Serializer(current.config['SECRET_KEY'])
  try:
   data=s.loads(token)
  except:
   return False
  if data.get('reset') !=self.id:
   return False
  self.password = new_password          #这里,又用到了password.setter的装饰器,来重新设定密码
  db.session.add(self)
  return True


好,表单,模型,路由这些后端逻辑设置好了,接下去就是搞前段了

我觉得首先是和平时我们登录的网站一样,要有一个按钮来转到发送email这个页面,就像我们平时看到的 “忘记密码?”这样类似

那我决定把他加载login的页面上


效果图如下

虽然代码写得有点ugly......5个<br>,不过位置图效果出来还是挺满意的,正好在输入密码的边上,挺人性化

#

#

# 这里留给收到的EMAIL邮件内容

#

#


收到EMAIL点击链接返回后

还需要我们配置一个路由,以及模板来显示修改密码的页面

这里贴2个版本,一个是原来我自己写的,另外一个是作者的源代码,我觉得源代码是不是有点重复的地方.......

先贴源代码

@auth.route('/reset/<token>', methods=['GET', 'POST'])
def password_reset(token):
 if not current_user.is_anonymous:                            #这一句确实有必要,判断是否是可以登录的用户
  return redirect(url_for('main.index'))
 form = PasswordResetForm()
 if form.validate_on_submit():
  user = User.query.filter_by(email=form.email.data).first()
  if user is None:                                                             #其实这个根据EMAIL找不到用户的功能,我做在了发送EMAIL的时候,如果找不到user,连EMAIL也不给你发
   return redirect(url_for('main.index'))
  if user.reset_password(token, form.password.data):  #这个我要说一句,本来我的想法是,只要是通过连接返回回来的页面,你通过passwordsetter装饰器来修改就是了

                                                                                    #何必再多写一个函数来reset参数呢,后来我想想,作者这样写,把token带进来,也许是出于安全考虑
   flash('Your password has been updated.')
   return redirect(url_for('auth.login'))
  else:
   return redirect(url_for('main.index'))
 return render_template('auth/reset_password.html', form=form)


讲到上面多写的函数,我们需要重新回到models里面,为User类添加内容

如下:

def reset_password(self,token,newpassword):

    s=Serializer(current.config['SECRET_KEY'])

    try:

        data=s.loads(token)

    except:

        return False

    if data.get('reset') != self.id:

        return False

    self.password = new_password

    db.session.add(self)

    db.session.commit()

    return True



渲染的模板没啥好多说,只是修改下title而已

{% extends "base.html" %}
{% import "bootstrap/wtf.html" as wtf %}
{% block title %}Flasky-Reset Password{% endblock %}
{% block page_content %}
<div class="page-header">
 <h3>Please reset your password</h3>
</div>

{{wtf.quick_form(form)}}
{% endblock %}


#

#

#  这里留给页面的截图,回头补

#

#


----------------------------------------------------------------功能分割线----------------------------------------------------------------------------------------------------------------------



终于来到这一章节的最后一点了,申请更换注册的邮箱!

这和我们平时注册网站时候碰到的更换绑定的邮箱的功能是一样的


这章节的说明要求有点复杂,先看看源码是如何操作的,来分析下



首先,老规矩,要创建一个修改email地址的表单

class ChangeEmailForm(Form):
 email = StringField('New email',validators=[Required(),Email()])
 password = PasswordField('Password',validators=[Required(),Email()])
 submit = SubmitField('Submit')
 
 def validate_email(self,field):                #同样的作用,以validate_开头的,会和一般的验证函数一起作用,这里主要是测试新email是否和原来一样
  if User.query.filter_by(email=form.email.data).first():
   raise ValidationError('Email already registered !')                        #一样的话则抛出一个报错,email已经被注册



随后,我认为是需要在models里面添加函数,来生成修改email用的token了

 def generate_email_change_token(self,new_email,expiration=3600):             #这里注意一下,都了一个new_mail的参数,还不知道作用,先往下看
  s = Serializer(current_app.config['SECRET_KEY'],expiration)
  return s.dumps({'change_email':self.id,'new_email':new_email})


def change_email(self,token):                 #这个更改绑定邮箱的确认函数,非常重要!因为和前面的都不一样,用到了很多判断语句,并且用到了新的模块hashlib
  s=Serializer(current_app.config['SECRET_KEY'])
  try:
   data = s.loads(token)
  except:
   return False
  if data.get('change_email') != self.id:   #如果在data中没有找到change_email,则return False      我个人理解用户身份验证
   return False
  new_email = data.get('new_email')     #如果在data里面没有找到new_email,这个key,则return False.......这个有点自相矛盾啊。。。
  if new_email is None:                         #如果用户没有输入new_email,则return False
   return False
  if self.query.filter_by(email = new_email).first() is not None:     #如果以new_email来查询,和用户目前的email属性相同的话,则return False,意思重复了
   return False
  self.email = new_email                       #如果以上情况均通过,则更新 email属性的值,为new_email,即重新设定成功
  self.avatar_hash = hashlib.md5(self.email.encode('utf-8')).hexdigest() 
  db.session.add(self)
  db.session.commit()
  return True


上面的代码最后部分,用到了hashlib的模块,我觉得廖雪峰老师的Python教程里面关于这部分的讲解非常详细了,还讲到了关于破解和反破解一方面的知识。

http://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000/0014319556588648dd1fb0047a34d0c945ee33e8f4c90cc000

所以,我们在models里面还需要import hashlib并为User类加入avatar_hash的类属性

avatar_hash的值就等于通过hashlib的md5函数,将email值变成摘要信息的内容.


模型部分的修改完成了,接着我们就要做最后一步,添加路由了

@auth.route('/change-email',methods=['GET','POST'])
@login_required
def change_email_request:()
 form = ChangeEmailForm()
 if form.validate_on_submit():
  if current_user.verify_password(form.password.data):                  #如果用户在表单上输入的密码和数据库的匹配
   new_mail = form.email.data                                                          #则将这个email地址赋值给new_email
   token = current_user.generate_email_change_token(new_mail)    #生成token
   send_email(new_mail,'Confirm your email address','auth/email/change_email',user = current_user,token = token)    #发送email,包含new_mail
   return redirect('main.index')
  else:
   flash ('invalid password or email!')
 return render_template('auth/change_email.html',form = form)


发送邮件的路由部分做完了以后,需要做模板来渲染

模板auth/change_email.html如下,没啥好多说的,改个title而已

{% extends "base.html" %}
{% import "bootstrap/wtf.html" as wtf %}
{% block title %}Flasky-Change Email{% endblock %}
{% block page_content %}
<div class="page-header">
 <h1>Change your email address</h1>
</div>
{{ wtf.quick_form(form) }}
{% endblock %}


如下是你收到的email的内容的渲染模板templates/auth/email/change_email.html


#

#  这里留给被渲染后的输入email和密码的表单画面

#  这里留给收到的email的内容截图

#

#


点击email的连接回来以后,就显示页面,后台逻辑检测是否修改成功,但是我个人认为,这样的功能应该放在发送邮件前就昨晚


而修改绑定email的返回页面则简单的多,因为逻辑上的修改只需要用current_user.change_email来执行,只要是True,就返回flash消息提示成功


@auth.route('/change-email/<token>',methods=['GET','POST'])
@login_required
def change_email(token):
 if current_user.change_email(token):
  flash('Your email address has been updated !')
 else:
  flash('Invalid request.')
 return redirect(url_for('main.index'))


#

#

#  这里留给点击email以后返回的画面截图

#

#




终于.....................第八章结束了,太漫长了...............不过知识点很多,需要巩固,有些需要后期补看一下源代码才能理解。



-------------------------------------------------------------分割线:额外的知识点-----------------------------------------------------------------------------

这里碰到一个情况引发思考

如下图,当你在未登录的情况下,尝试点击change password进入此页面时候,他会有一个flash消息提示你,请登录后才有权限访问页面

但是,我查看了所有的路由函数,并没有找到设置这个flash消息的地方

那这个消息肯定是在哪个地方默认设置的咯?百度了一下,发现,他是在Flask-Login的login_view里面设置的,所以我回过头去看前面章节的设置


我们来看Flask-Login的官方文档,下面的红框部分

首先第一点LoginManager.login_message:这里设置的就是默认的flash消息,而这个消息是用在user.login(我的例子里是auth.login)页面的

如果需要修改的话,需要修改login_manager.login_message这个内容


第二点:在未登录状态下,尝试访问一个需要登录状态才能访问的页面时,login_view会放把尝试访问的页面的地址


最后,看一下我们书中的例子,书里面并没有很详细地讲到这一点,所以这个只是点被忽略了






-----------------------------------------------------------------------分割线:知识点结束----------------------------------------------------------------------------





1 0
原创粉丝点击