原文: http://www.catonlinepy.tech/
声明: 原创不易,未经许可,不得转载

接第5天上篇的内容,我们接着介绍用户注册功能的实现

3. 用户的注册

3.1 注册表单及前端渲染

根据经验,当我们在登录某个应用时,如果我们没有在平台注册过账号,平台会提醒我们注册一个账号,注册成功后方可进行登陆。与登录页面的实现类似,前端的注册页面也需要后台传入注册表单,然后Jinjia2将表单渲染成前端浏览器能解析的form表单。由于注册页面通常要求用户输入电子邮箱,用户名,密码以及确认密码,下面我们首先在forms.py文件中增加注册表单类registerForm:

# forms.py文件中新增的内容
# ..
# 在wtforms中还需要导入StringField,ValidationError字段
from wtforms import PasswordField, BooleanField, SubmitField, StringField,ValidationError#  在validators验证器中还需要导入EqualTo, InputRequired验证器
from wtforms.validators import DataRequired, Length, EqualTo, InputRequired# 在def两个函数中使用到User模型,需要导入
from userauth_demo.models import User# ..class registerForm(FlaskForm):username = StringField(u'用户名',validators=[DataRequired(), Length(min=2, max=20)])email = EmailField(u"邮箱",validators=[DataRequired(), Length(min=2, max=20)])password = PasswordField(u'密码',validators=[InputRequired(), EqualTo(u"confirm_password", message="两次输入的密码不一致!")])confirm_password = PasswordField(u'确认密码',validators=[DataRequired()])submit = SubmitField(u'注册')def validate_username(self, username):user = User.query.filter_by(username=username.data).first()if user is not None:raise ValidationError('该用户名已被注册!')def validate_email(self, email):user = User.query.filter_by(email=email.data).first()if user is not None:raise ValidationError('该邮箱已被注册!')

在上面的注册表单类registerForm中,为了减少输入错误的风险,使用到了password和confirm_password,password密码中用到了EqualTo验证器,它需要与confirm_password中输入的密码一致才通过验证。这个注册表单中还使用到了两个自定义函数,它们以validate开头,后面接上_,这里的filedname都是WTForms提供的自定义字段,当前端用户点提交按键时,就会触发这两个函数的调用。在这里,当用户提供了注册信息后,会在数据库中的user表中进行查询,如果用户名和邮箱不存在,则可顺利的完成注册,否则,就会执行if语句内的错误提醒程序,这些错误提醒会在浏览器页面渲染出来。

注册表单写好后,需要在前端HTML模板中渲染表单,它与登陆功能的模板类似,在register.html文件中输入如下内容:

<!-- register.html 文件中的内容-->{% extends "layout.html" %}{% block content %}<h2>注册</h2><form action="" method="post"><!--防跨站伪造请求-->{{html_form.hidden_tag() }}<div>{{ html_form.username.label }}<br />{{ html_form.username() }}{% if html_form.username.errors %}<div >{% for error in html_form.username.errors %}<span>{{ error }}</span>{% endfor %}</div>{% endif %}</div><div>{{ html_form.email.label }}<br />{{ html_form.email() }}{% if html_form.email.errors %}<div >{% for error in html_form.email.errors %}<span>{{ error }}</span>{% endfor %}</div>{% endif %}</div><div>{{ html_form.password.label }}<br />{{ html_form.password() }}</div><div>{{ html_form.confirm_password.label }}<br />{{ html_form.confirm_password() }}</div><div>{{ html_form.submit() }}</div></form><!-- 添加跳转到登陆页面的超链接 --><p>已经有账户? <a  href="{{ url_for('login')}}">登陆</a></p>
{% endblock content %}

3.2 为注册表单编写路由函数

在编写路由函数之前,先来讲讲密码哈希的重要性。因为在注册页面中,用户需要输入密码,而密码最终需要存入到数据库中的,但是如果把明文密码存到数据库,这是一件很危险的事儿。假如数据库被黑客获取后(脱裤),用户的个人账号、密码就完全泄漏给了黑客。这时,就需要有工具将密码加密后再写到数据库。flask_bcrypt就是干这个活儿的插件,负责对明文密码进行加密操作,下面先在虚拟环境中安装这个插件:

(miao_venv) maojie@Thinkpad:~/flask-plan/flask-course-primary/day5$ pip install Flask_Bcrypt

安装完成后,我们先在python shell中练练手,熟悉熟悉这个插件的基本用法:

(miao_venv) maojie@Thinkpad:~/flask-plan/flask-course-primary/day5$ python
Python 3.6.7 (default, Oct 22 2018, 11:32:17)
[GCC 8.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from flask_bcrypt import Bcrypt                                      # 从flask_bcrypt中导入Bcrypt类
>>> bcrypt = Bcrypt()                                                    # 对Bcrypt()进行实例
>>> bcrypt.generate_password_hash('miaojie')                             # 产生哈希密码
b'$2b$12$4D2eChUFV45DkC/VMsc0.u3JSjkSPvSZHHtokDHmrhD7tnKldFf/a'
>>> bcrypt.generate_password_hash('miaojie').decode('utf-8')             # 对哈希密码进行解码
'$2b$12$Q8.Io9ld5mwXiCKc2TiWI.8c7FPDwh440zo80NkwUwvtebXy.8xoO'
>>> bcrypt.generate_password_hash('miaojie').decode('utf-8')             # 每次产生的密码会不一样,这使得得通过哈希过的密码无法查看它原始的密码
'$2b$12$YXKa1TSyROPvpprL7UfjOOoWdq16z/ZH2n8dpn8tCc8nDTwNoB.Jm'
>>> hashed_password = bcrypt.generate_password_hash('miaojie').decode('utf-8')
>>> bcrypt.check_password_hash(hashed_password,'password')                # 哈希过的密码和明文密码进行对比,匹配则返回True,不匹配则返回False
False
>>> bcrypt.check_password_hash(hashed_password,'miaojie')
True
>>>

熟悉了flask_bcrypt插件后,我们就可以使用它了,在__init__.py文件中添加bcrypt对象,如下所示:

# 在__init__.py文件中新增的内容
# ..
# 从flask_bcrypt中导入Bcrypt类
from flask_bcrypt import Bcrypt
# Bcrypt()的实例
bcrypt = Bcrypt()
# ..

在routes.py文件中新增注册表单的路由函数register,如下所示:

# ..
from userauth_demo.forms import registerForm
from userauth_demo import db,bcrypt
from userauth_demo.models import User# ..@app.route("/register", methods=["GET", "POST"])
def register():if current_user.is_authenticated:return redirect(url_for('index'))form = registerForm()# 发送post请求if form.validate_on_submit():hashed_password = bcrypt.generate_password_hash(form.password.data).decode('utf-8')user = User(username=form.username.data, email=form.email.data, password=hashed_password)db.session.add(user)db.session.commit()flash('该用户名已注册,请登陆!', 'success')return redirect(url_for('login'))return render_template('register.html', title="第五天", html_form=form)

当用户填写完注册信息,点击注册按键后,就会向后台发送post请求,后台接收到post请求后,会将用户名、邮箱以及哈希后的密码保存到数据库中。注册成功后,提示flash消息,并重定向到登陆页面。如果是发送get请求,则直接会对模板进行渲染,显示出注册页面的表单。

通过python run.py将web程序拉起,开始验证结果,在浏览器中输入http://127.0.0.1:5005/register,效果图如下所示:

如图,在注册表单中输入用户名、邮箱及密码,点击注册按钮后,如果注册成功,就会跳转到下图所示的登陆页面。

4. 真正的登录过程

上面我们在注册页面的最下方,添加了跳转到登陆页面的超链接,这里也需要在登陆页面的最下方添加跳转到注册页面的超链接,方便未注册的用户快速找到注册页面。这里在login.html文件的标签下方添加注册超链接:

<! --login.html文件中添加注册链接-->
# ..<p>没有账号? <a  href="{{ url_for('register')}}">注册</a>
</p>
# ..

效果如下所示:

4.1 登陆用户

在第5天上篇的课程中,我们完成了登陆表单路由函数的实现。其中,当用户发送post请求时,后台直接使用了假的邮箱和密码(不是从数据库中读取出来的用户信息)进行验证。但是真实的登录过程中,后台程序通过用户输入的邮箱,从数据库中读取出用户的密码信息,然后将数据库中的密码与用户在前端输入的密码进行比较,如果用户输入的密码是正确的,就将该用户标记为登录状态,否则就提示用户密码输入错误。修改routes.py文件的内容,添加用户登录认证的代码:

# routes.py文件中修改登陆表单路由函数
# ..
# 从userauth_demo中导入bcrypt实例
from userauth_demo import db, bcrypt# 从flask_login中导入login_user函数
from flask_login import current_user, login_user# 从userauth_demo.models中导入User模型
from userauth_demo.models import User#.. @app.route('/login', methods=['GET','POST'])
def login():if current_user.is_authenticated:return redirect(url_for('index'))# 对类LoginForm的实例form = LoginForm()if form.validate_on_submit():user = User.query.filter_by(email=form.email.data).first()if user and bcrypt.check_password_hash(user.password, form.password.data):login_user(user, remember=form.remember.data)else:flash('登陆不成功,请检查邮箱和密码!', 'danger')return render_template('login.html', title='第五天', html_form=form)# ..

我们对上面代码的工作流程进行简单的讲解:首先,用户发送post请求后,后台接收到该请求后,通过用户输入的邮箱从数据库中找到该用户,再使用first()方法返回第一个匹配的用户。接下来的if语句判断该用户是否存在,并且使用check_password_hash函数验证用户输入的密码是否和数据库中的密码一致,如果条件都满足则会执行login_user()函数,并将用户登陆状态记为已登陆,否则,会执行else语句,flash消息提示登陆不成功,请检查邮箱和密码。

4.2 页面访问权限控制

web开发过程中,经常会碰到网页权限控制的问题,即有些页面需要登录状态才能访问,Flask_Login直接为我们提供了该功能:如果用户访问的页面需要登录权限,而此时用户不是处于登录状态,则会强制用户跳转到登录页面。为了实现这个功能,Flask_Login需要知道哪个视图函数用于处理登陆机制,在__init__.py文件中的添加如下代码:

# 在__init__.py文件中添加内容login_manager = LoginManager(app)
login_manager.login_view = 'login'   #login是登录页面视图函数的函数名

Flask_Login使用@login_required装饰器让已通过认证的用户进行访问,如果将该装饰器放到@app.route装饰器下的视图函数上面时,则该路由函数受到保护,不会让匿名(未登录)的用户进行访问,Flask-Login 会拦截请求,把用户发往登录页面。对routes.py文件进行修改,如下所示:

# routes.py文件中添加内容
# ..
# 从flask_login中导入login_required类
from flask_login import current_user, login_user, login_required
# ..
@app.route('/')
# 在主页的视图函数上面加上@login_required装饰器
@login_required
def index():return render_template('index.html', title='第五天')

此时,我们在未登录状态下访问主页/,则会跳转到如下图所示的登录页面:

login_required装饰器拦截请求并将主页重定向到登录页面,此时URL中还包含其它的信息,如/login?next=%2F。next参数可以帮助用户在登录成功后,直接跳转到之前没有权限访问的页面。下面这段代码,展示了如何查询和处理next参数,在routes.py中进行修改:

# routes.py文件中添加内容
# 从flask中导入request
from flask import render_template, redirect, url_for, flash, request
# ..
@app.route('/login', methods=['GET','POST'])
def login():if current_user.is_authenticated:return redirect(url_for('index'))form = LoginForm()if form.validate_on_submit():user = User.query.filter_by(email=form.email.data).first()if user and bcrypt.check_password_hash(user.password, form.password.data):login_user(user, remember=form.remember.data)# 查询next参数nextpage = request.args.get('next')# 对next参数进行处理if nextpage:return redirect(nextpage)else:url = url_for('index')return redirect(url)else:flash('登陆不成功,请检查邮箱和密码!', 'danger')return render_template('login.html', title='第五天', html_form=form)
# ..

request.args.get可以得到next的参数赋值给nextpage,下面的if语句判断nextpage是否存在,如果存在,则直接重定向到nextpage所在页面,如果不存在,则会重定向到主页。

下面,我们在登录页面输入正确的用户邮箱和密码,点击登陆按钮后页面将重定向到主页,效果如下图所示:

4.3 用户注销

通常当用户登录后,想要退出登录状态,那么注销功能也是必须的,借用Flask_Login的logout_user()函数,可以轻松的实现用户的注销功能。在routes.py文件中添注销功能的代码,如下:

# routes.py文件中添加的内容
# ..
# 从flask_login中导入logout_user类
from flask_login import current_user, login_user, login_required, logout_user# ..# 添加注销功能的视图函数
@app.route('/logout')
def logout():logout_user()return redirect(url_for('index'))

此外,还需要在基模板中添加注销的前端代码,修改layout.html文件中的内容:

<! -- layout.html文件中添加内容 -->
# ..
<header><div><a href="{{ url_for('index') }}">主页</a>{% if current_user.is_authenticated() %}<a href="{{ url_for('logout') }}">注销</a>{% else %}<a href="{{ url_for('login') }}">登陆</a>{% endif %}</div>
</header>#..

添加完以上代码后,我们只需刷新一下主页,就会在主页看到新增的注销按钮,效果图如下所示:

5. 总结

学习完今天的内容,我们掌握了如下技能:

  1. 学习了Flask_Login插件的基本使用方法
  2. 学习了如何对明文密码进行加密、比对
  3. 学习了如何利用数据库完成用户注册功能
  4. 学习了如何实现用户的登陆及页面权限管理
  5. 学习了如何控制用户的登录状态和注销状态

下一课的教程,猫姐将带领大家一起学习个人主页的实现及用户头像功能的管理。今天的内容就到这里,喜欢的同学们可以在下面点赞留言,或是访问我的博客地址:http://www.catonlinepy.tech/ 加入我们的QQ群进一步交流学习!

6. 代码的获取

大家可以到github上获取今天教程的所有代码:https://github.com/miaojie19/flask-course-primary.git

具体下载代码的命令如下:

# 使用git命令下载flask-course-primary仓库所有的代码
git clone https://github.com/miaojie19/flask-course-primary.git# 下载完成后,进入day5目录下面,即可看到今天的代码
cd flask-course-primary
cd day5

第5天下篇:在Flask应用中使用用户认证—Flask_Login相关推荐

  1. django 1.8 官方文档翻译: 13-1-1 Django 中的用户认证

    Django 中的用户认证 Django从开始就带有一个用户认证系统.它处理用户账号.组.权限以及基于cookie的用户会话.本节文档解释默认的实现如何直接使用,以及如何扩展和定制它以适合你项目的需要 ...

  2. django官方文档——django中的用户认证

    Django 中的用户认证¶ Django 自带一个用户认证系统,这个系统处理用户帐户.组.权限和基于 cookie 的会话.本文说明这个系统是如何工作的. 概览¶ 认证系统由以下部分组成: 用户 权 ...

  3. springsecurity 中获取用户信息

    一.使用注解 @AuthenticationPrincipal @GetMapping("/user")public String user(@AuthenticationPrin ...

  4. flask项目中无法更改端口号

    flask项目中无法更改端口号 app.run(port=8000) 问题 启动后是 127.0.0.1:5000 解决方法

  5. python处理信号机制_Python的Flask框架中的signals信号机制

    Flask 提供了信号(Signals)功能,是一种消息分发机制.类似于钩子(Hooks).使用信号功能可以降低程序的耦合,分解复杂的业务模型.例如在更新了产品数据后,可以发送一个信号.当有需要对产品 ...

  6. python flask框架下登录注册界面_Python的Flask框架中实现简单的登录功能的教程

    Python 的 Flask 框架中实现简单的登录功能的教程 , 登录是各个 web 框架中的基础功能 , 需要的朋友可以参考下 回顾 在前面的系列章节中, 我们创建了一个数据库并且学着用用户和邮件来 ...

  7. Flask 框架中 上下文基础理念,包括cookie,session存储方法,requset属性,current_app模块和g模块...

    Flask中上下文,分为请求上下文和应用上下文.既状态留存 ,就是把变量存在某一个地方可以调用 请求上下文:实际就是request和session用法理念,既都是可以存储东西. 应用上下文:既变量共享 ...

  8. flask框架中的Jinja2模板引擎

    简介 在flask框架中通常使用Jinja2模板引擎来实现复杂页面的渲染. 本章主要介绍Jinja2模板引擎的基本结构和使用方法. 如何使用flask框架渲染模板 在模板中传递一个或者多个参数 if语 ...

  9. Flask扩展系列(八)–用户会话管理

    安装和启用 遵循标准的Flask扩展安装和启用方式,先通过pip来安装扩展: $ pip install Flask-Login 接下来创建扩展对象实例: 1 2 3 4 5 from flask i ...

最新文章

  1. 洛谷P2763 试题库问题
  2. Centos 7 学习之静态IP设置
  3. 使用jquery实现的计算器功能
  4. getsockname和getpeername
  5. Linux中查看端口占用情况及结束相应进程
  6. 还有另一个报告生成器?
  7. SplitConcatWithAMP----Array转换为String,连接;String转换为Array,切割
  8. 4个数字,如何最快找到最小的2个数
  9. 基于MPI并行的VTI介质逆时偏移成像与ADCIGs提取
  10. java通讯录管理系统答辩_java版通讯录管理系统
  11. Python 使用turtle在画布的随机位置绘制颜色随机的五角星
  12. python中转义符的用法_19.Python转义字符及用法
  13. react 组件 进阶之 ref (ts 版本)
  14. OpenStack 快速进阶教程
  15. python----小计买书问题
  16. python人工智能面试题爱奇艺面试题_【爱奇艺Python面试】爱奇艺大数据面试 python-看准网...
  17. 租用稳定的网通服务器,网通租用服务器
  18. c语言实现按键精灵区域找图,区域遍历所有图片(找图,多点找色)----1个函数实现...
  19. 百度音乐爬取文件练习
  20. 20191005 (16)金融学课笔记(得到北大经融学课)——复利加速思维

热门文章

  1. 解决Android10读取不到/sdcard/、/storage/emulated/0/文件的问题
  2. 什么是free variable
  3. 【转】解决“Microsoft Word已停止工作”的两个办法
  4. 从程序员打人事件谈一谈“产品经理”
  5. OpenCV视觉学习之混合高斯模型进行背景建模
  6. 200台PS3可破解MD5加密算法 安全网页不再安全
  7. SQL SERVER 2005镜像配置(包含见证服务器)
  8. Qt中文编码和QString类Unicode编码转换
  9. 女人怎么去爱自己的男人!!(给女士们看看)声明我是男的 ^_^
  10. Python使用Pillow包报错 ImportError: DLL load failed while importing _imaging: 找不到指定的程序。