目前为止,虽然程序的功能大部分已经实现,但还缺少一个非常重要的部分——用户认证保护。页面上的编辑和删除按钮是公开的,所有人都可以看到。假如我们现在把程序部署到网络上,那么任何人都可以执行编辑和删除条目的操作,这显然是不合理的。
这一章我们会为程序添加用户认证功能,这会把用户分成两类,一类是管理员,通过用户名和密码登入程序,可以执行数据相关的操作;另一个是访客,只能浏览页面。在此之前,我们先来看看密码应该如何安全的存储到数据库中。
安全存储密码
把密码明文存储在数据库中是极其危险的,假如攻击者窃取了你的数据库,那么用户的账号和密码就会被直接泄露。更保险的方式是对每个密码进行计算生成独一无二的密码散列值,这样即使攻击者拿到了散列值,也几乎无法逆向获取到密码。
Flask 的依赖 Werkzeug 内置了用于生成和验证密码散列值的函数,werkzeug.security.generate_password_hash()
用来为给定的密码生成密码散列值,而 werkzeug.security.check_password_hash()
则用来检查给定的散列值和密码是否对应。使用示例如下所示:
>>> from werkzeug.security import generate_password_hash, check_password_hash
>>> pw_hash = generate_password_hash('dog') # 为密码 dog 生成密码散列值
>>> pw_hash # 查看密码散列值
'pbkdf2:sha256:50000$mm9UPTRI$ee68ebc71434a4405a28d34ae3f170757fb424663dc0ca15198cb881edc0978f'
>>> check_password_hash(pw_hash, 'dog') # 检查散列值是否对应密码 dog
True
>>> check_password_hash(pw_hash, 'cat') # 检查散列值是否对应密码 cat
False
我们在存储用户信息的 User
模型类添加 username
字段和 password_hash
字段,分别用来存储登录所需的用户名和密码散列值,同时添加两个方法来实现设置密码和验证密码的功能:
from werkzeug.security import generate_password_hash, check_password_hash
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(20))
username = db.Column(db.String(20)) # 用户名
password_hash = db.Column(db.String(128)) # 密码散列值
def set_password(self, password): # 用来设置密码的方法,接受密码作为参数
self.password_hash = generate_password_hash(password) # 将生成的密码保持到对应字段
def validate_password(self, password): # 用于验证密码的方法,接受密码作为参数
return check_password_hash(self.password_hash, password) # 返回布尔值
因为模型(表结构)发生变化,我们需要重新生成数据库(这会清空数据):
$ flask initdb --drop
生成管理员账户
因为程序只允许一个人使用,没有必要编写一个注册页面。我们可以编写一个命令来创建管理员账户,下面是实现这个功能的 admin()
函数:
import click
@app.cli.command()
@click.option('--username', prompt=True, help='The username used to login.')
@click.option('--password', prompt=True, hide_input=True, confirmation_prompt=True, help='The password used to login.')
def admin(username, password):
"""Create user."""
db.create_all()
user = User.query.first()
if user is not None:
click.echo('