第五章 用户登录
一、 密码hash化
1、 我们访问网站的时候,经常需要注册,注册以后自然就会存留密码。
为了防止意外数据外泄导致用户的密码暴露,故可以通过hash算法将用户密码加密处理。在第四章,我们在User model中就定义了这么一个字段,还没开始使用了。密码hash化其实是一个复杂的过程,不过,这些工作可以交给其他专家,我们只需要在APP应用中调用即可。
有一个此功能的第三方包:Werkzeug。这个包在flask安装的时候就一起安装好了。
加密:
from werkzeug.security import generate_password_hash
hash = generate_password_hash('foobar') #将密码foobar通过hash算法加密
2 、有了加密,自然就会用到解密了。flask自带了一个密码检核的方法。
检核密码:
from werkzeug.security import check_password_hash
check_password_hash(hash, 'foobar') #检核密码
3 、为了方便后期USER表的密码操作,可以在原有的USER类中新增密码hash及密码核对的方法
from werkzeug.security import generate_password_hash, check_password_hash
# ...
class User(db.Model):
# ...
#hash 明文password
def set_password(self, password):
self.password_hash = generate_password_hash(password)
#检核密码
def check_password(self, password):
return check_password_hash(self.password_hash, password)
4、验证下
#实例化用户信息
>>>u = User(username='susan', email='susan@example.com')
>>>u.set_password('mypassword')
>>>u.check_password('anotherpassword')
False
>>>u.check_password('mypassword')
True
二、介绍Flask-Login
我给大家介绍一个flask中非常流行的扩展: Flask-Login。
这个扩展可以管理用户登录状态。例如:一个用户登录应用后跳转到制定的页面。它带有remember me的功能,即使用户在退出浏览器后,还会保留着登录信息。
1、安装此扩展
pip install flask-login
2、初始化Login
类似其他的扩展,需要先初始化,方便其他模块调用。
app/init.py:
# ...
from flask_login import LoginManager
app = Flask(__name__)
# ...
login = LoginManager(app)
# ...
三、为Flask-Login提前准备User模型
在用户登录的时候,我们经常需要获取用户登录的一些属性值来进一步判读下一步操作:
is_authenticated:是否认证过的
is_active:是否活跃账户.
is_anonymous: 是否匿名账户
get_id(): 返回用户的id.
我们可以很容易的实现上边的4个项通用功能。
flask_login包含的一个类UserMixin,这个类包含了很多适用于User模型类的通用功能。
User类继承UserMixin:
# ...
from flask_login import UserMixin
class User(UserMixin, db.Model): #通过继承方式承接那些类属性
# ...
四、用户加载函数
Flask-Login通过用户ID一直监控着登录用户。每次用户登录到一个新页面,Flask-Login就会重新获取用户的ID,随后把用户信息加载到内存中。
再匹配用户信息的时候,就需要先加载用户信息。
由于Flask-Login根本不知道数据库,所以,需要我们在APP应用中定义一个功能:通过用户ID来获取用户信息。
app/models.py
from app import login #引用LoginManager(app)
# ...
@login.user_loader
def load_user(id):
return User.query.get(int(id)) #通过int把id转换成数字
五、 用户登录
在用户注册界面,用户录入完成后,将要验证用户名称、密码信息。如果无误就跳转到首页。对于已经注册过的用户,就不能再导航到注册页。
app/routes.py
# ...
from flask_login import current_user, login_user
from app.models import User
# ...
@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(username=form.username.data).first() #根据用户名查询对应用户的记录行
if user is None or not user.check_password(form.password.data): #如果登录信息不全及密码错误、用户名不对;提交后仍然停留在登录页面
flash('Invalid username or password')
return redirect(url_for('login'))
login_user(user, remember=form.remember_me.data) #login_user(user),其实也是调用user_loads()把用户设置到session中
return redirect(url_for('index'))
return render_template('login.html', title='Sign In', form=form)
六、 注销账户
既然用户注册了,如果用户认为以后都不想再登录了,自然就会涉及到注销。
Flask-Login中有一个很好方法:logout_user() 。
app/routes.py
# ...
from flask_login import logout_user
# ...
@app.route('/logout')
def logout():
logout_user()
return redirect(url_for('index'))
为了方便用户注销,在导航栏中增加注销菜单项:
<div>
Microblog:
<a href="{{ url_for('index') }}">Home</a>
{% if current_user.is_anonymous %} #判断当前用户是否匿名账户
<a href="{{ url_for('login') }}">Login</a>
{% else %}
<a href="{{ url_for('logout') }}">Logout</a>
{% endif %}
</div>
七、要求用户注册后才能访问网页
有时页面或内容,需要会员或注册用户才可以查看,Flask可以很方便的实现此功能。如果没注册,在打开网页的时候回自动跳转到登录界面。
1、首页要告知Flask,那个视图函数是控制登录的。
app/init.py:
# ...
login = LoginManager(app)
login.login_view = 'login' #login是视图函数名称
2、设置注册后访问
app/routes.py:
from flask_login import login_required
@app.route('/')
@app.route('/index')
@login_required #强制注册装饰器
def index():
# ...
3、 登录后跳转回原访问页面
@login_required作用:
跳转到登录页面,同时在页面URL中增添nex访问参数。
例如: URL /login?next=/index
from flask import request #查询客户使用
from werkzeug.urls import url_parse #判断域名使用
@app.route('/login', methods=['GET', 'POST'])
def login():
# ...
if form.validate_on_submit():
user = User.query.filter_by(username=form.username.data).first()
if user is None or not user.check_password(form.password.data):
flash('Invalid username or password')
return redirect(url_for('login'))
login_user(user, remember=form.remember_me.data)
#获取登录页面路由地址中的next参数值
next_page = request.args.get('next')
#url_parse编译路由地址,netloc属性指域名或服务器地址
#当next_page为空或者路由地址是相对地址时,返回index网页,否则返回next_page制定页面(绝对的地址)
if not next_page or url_parse(next_page).netloc != '':
next_page = url_for('index')
return redirect(next_page)
# ...
八、在模板中显示当前用户
app/templates/index.html:
{% extends "base.html" %}
{% block content %}
<h1>Hi, {{ current_user.username }}!</h1> #返回当前用户名称
{% for post in posts %} #循环遍历当前用户的博文
<div><p>{{ post.author.username }} says: <b>{{ post.body }}</b></p></div>
{% endfor %}
{% endblock %}
备注:把以前手工建立的用户信息语句块删除,同时把index视图里边的渲染页面的user参数删除。
@app.route('/')
@app.route('/index')
def index():
# ...
return render_template("index.html", title='Home Page', posts=posts)
九、 用户注册
当在登录界面的时候,如果用户没有登录账号,就需要注册了。
1、 设置注册表单
app/forms.py
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, BooleanField, SubmitField
from wtforms.validators import ValidationError, DataRequired, Email, EqualTo
from app.models import User
# ...
class RegistrationForm(FlaskForm):
username = StringField('Username', validators=[DataRequired()])
#Email()用于判断邮件名格式
email = StringField('Email', validators=[DataRequired(), Email()])
password = PasswordField('Password', validators=[DataRequired()])
password2 = PasswordField(
'Repeat Password', validators=[DataRequired(), EqualTo('password')]) #EqualTo判断等于,注意要添加引号
submit = SubmitField('Register')
def validate_username(self, username):
user = User.query.filter_by(username=username.data).first()
if user is not None:
raise ValidationError('Please use a different username.')
def validate_email(self, email):
user = User.query.filter_by(email=email.data).first()
if user is not None:
raise ValidationError('Please use a different email address.')
2、 设置注册页面html格式
app/templates/register.html:
{% extends "base.html" %}
{% block content %}
<h1>Register</h1>
<form action="" method="post">
{{ form.hidden_tag() }}
<p>
{{ form.username.label }}<br>
{{ form.username(size=32) }}<br>
{% for error in form.username.errors %}
<span style="color: red;">[{{ error }}]</span>
{% endfor %}
</p>
<p>
{{ form.email.label }}<br>
{{ form.email(size=64) }}<br>
{% for error in form.email.errors %}
<span style="color: red;">[{{ error }}]</span>
{% endfor %}
</p>
<p>
{{ form.password.label }}<br>
{{ form.password(size=32) }}<br>
{% for error in form.password.errors %}
<span style="color: red;">[{{ error }}]</span>
{% endfor %}
</p>
<p>
{{ form.password2.label }}<br>
{{ form.password2(size=32) }}<br>
{% for error in form.password2.errors %}
<span style="color: red;">[{{ error }}]</span>
{% endfor %}
</p>
<p>{{ form.submit() }}</p>
</form>
{% endblock %}
3、 在登录页面登录按钮下边增加一个跳转到注册页面的连接
<p>New User? <a href="{{ url_for('register') }}">Click to Register!</a></p>
4、定义注册视图函数
用户在注册后,把用户信息添加到数据库中。
db.session.add(user) :添加用户记录
db.session.commit():执行数据库操作
from app import db
from app.forms import RegistrationForm
# ...
@app.route('/register', methods=['GET', 'POST']) #注意添加方法,否则点击登录按钮的时候会报错:不能使用POST方法。
def register():
if current_user.is_authenticated:
return redirect(url_for('index'))
form = RegistrationForm()
if form.validate_on_submit():
user = User(username=form.username.data, email=form.email.data)
user.set_password(form.password.data)
db.session.add(user)
db.session.commit()
flash('Congratulations, you are now a registered user!')
return redirect(url_for('login'))
return render_template('register.html', title='Register', form=form)