1、使用Werkzeug实现密码散列
在User模型中加入密码散列
app/models.py
计算密码散列值的函数通过名为password的只写属性实现,设定这个属性的值时,赋值方法会调用Werkzeug提供的generate_password_hash()函数,并把得到的结果赋值给password_hash字段。
如果试图读取password属性的值,则会返回错误,原因很明显,因为生成散列值后就无法还原成原来的密码了
from . import db from werkzeug.security import generate_password_hash, check_password_hash #定义数据库模型 class Role(db.Model): __tablename__ = 'roles' id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(64), unique=True) users = db.relationship('User', backref='role') def __repr__(self): return '<Role %r>' %self.name class User(db.Model): __tablename__ = 'users' id = db.Column(db.Integer, primary_key=True) username = db.Column(db.String(64), unique=True, index=True) role_id = db.Column(db.Integer, db.ForeignKey('roles.id')) password_hash = db.Column(db.String(128)) #在User模型中加入密码散列 @property def password(self): raise AttributeError('password is not a readable attribute') @password.setter def password(self, password): self.password_hash = generate_password_hash(password) def verify_password(self, password): return check_password_hash(self.password_hash, password) def __repr__(self): return '<User %r>' %self.username
在shell中验证加入的密码散列功能
注:u1,u2即使使用了相同的密码,它们的密码散列值也完全不一样
把上述测试写成单元测试,以便于重复执行,我们在test包中新建一个模块,编写3个新测试,测试最近对User模型所做的修改
tests/test_user_model.py
import unittest from app.models import User #密码散列化测试 class UserModelTestCase(unittest.TestCase): def test_password_setter(self): u = User(password = 'cat') self.assertTrue(u.password_hash is not None) def test_no_password_getter(self): u = User(password = 'cat') with self.assertRaises(AttributeError): u.password def test_password_verification(self): u = User(password = 'cat') self.assertTrue(u.verify_password('cat')) self.assertFalse(u.verify_password('dog')) def test_password_salts_are_random(self): u = User(password = 'cat') u2 = User(password = 'cat') self.assertTrue(u.password_hash != u2.password_hash)
2、创建认证蓝本
创建蓝本
app/auth/__init__.py
app/auth/views.py模块引入蓝本,然后使用蓝本的route修饰器定义与认证相关的路由
from flask import Blueprint #创建认证蓝本 auth = Blueprint('auth', __name__) from . import views
蓝本中的路由和视图函数
app/auth/views.py
添加一个/login路由,渲染同名占位模板
render_template()指定的模板文件保存在auth文件夹中,这个文件夹必须在app/templates中创建,因为Flask认为模板的路径是相对于程序模板文件夹而言的。
为避免与main蓝本和后序添加的蓝本发生模板命名冲突,可以把蓝本使用的模板保存在单独的文件夹中
from flask import render_template from . import auth #蓝本中的路由和视图函数 @auth.route('/login') def login(): return render_template('auth/login.html')
附加蓝本
app/__init__.py
url_prefix是可选参数,使用这个参数后,注册蓝本中定义的所有路由都会加上指定的前缀,本例中,/login路由会注册成/auth/login,在web服务器中,完整的URL就变成了http://locahost:5000/auth/login
from flask import Flask, render_template from flask_bootstrap import Bootstrap from flask_mail import Mail from flask_moment import Moment from flask_sqlalchemy import SQLAlchemy from config import config bootstrap = Bootstrap() mail = Mail() moment = Moment() db = SQLAlchemy() def create_app(config_name): app = Flask(__name__) app.config.from_object(config['default']) config['default'].init_app(app) bootstrap.init_app(app) mail.init_app(app) moment.init_app(app) db.init_app(app) #认证函数的附加蓝本 from .auth import auth as auth_blueprint app.register_blueprint(auth_blueprint, url_prefix='/auth') from .main import main as main_blueprint app.register_blueprint(main_blueprint) #附加路由和自定义的错误页面 return app