10、密码扩展,使用Flask-Login认证用户

摘要:
密码扩展1、使用Werkzeug实现密码散列在User模型中加入密码散列app/models.py计算密码散列值的函数通过名为password的只写属性实现,设定这个属性的值时,赋值方法会调用Werkzeug提供的generate_password_hash()函数,并把得到的结果赋值给password_hash字段。如果试图读取password属性的值,则会返回错误,原因很明显,因为生成散列值后

密码扩展

1、使用Werkzeug实现密码散列

在User模型中加入密码散列

app/models.py

计算密码散列值的函数通过名为password的只写属性实现,设定这个属性的值时,赋值方法会调用Werkzeug提供的generate_password_hash()函数,并把得到的结果赋值给password_hash字段。

如果试图读取password属性的值,则会返回错误,原因很明显,因为生成散列值后就无法还原成原来的密码了

from . importdb
from werkzeug.security importgenerate_password_hash, check_password_hash

#定义数据库模型
classRole(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


classUser(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
    defpassword(self):
        raise AttributeError('password is not a readable attribute')

    @password.setter
    defpassword(self, password):
        self.password_hash =generate_password_hash(password)

    defverify_password(self, password):
        returncheck_password_hash(self.password_hash, password)

    def __repr__(self):
        return '<User %r>' %self.username

注:

把password方法变为属性只需要加上@property装饰器即可,此时@property本身又会创建另外一个装饰器@password.setter,负责把password方法变成给属性赋值

u.password = 'cat' 实际转化成 u.password('cat')

u.password 实际转化成 u.password()

所以当读取password值时,会调用 u.password(),会抛出异常

在shell中验证加入的密码散列功能

注:u1,u2即使使用了相同的密码,它们的密码散列值也完全不一样

10、密码扩展,使用Flask-Login认证用户第1张

把上述测试写成单元测试,以便于重复执行,我们在test包中新建一个模块,编写3个新测试,测试最近对User模型所做的修改

tests/test_user_model.py

importunittest
from app.models importUser

#密码散列化测试

classUserModelTestCase(unittest.TestCase):
    deftest_password_setter(self):
        u = User(password = 'cat')
        self.assertTrue(u.password_hash is notNone)

    deftest_no_password_getter(self):
        u = User(password = 'cat')
        with self.assertRaises(AttributeError):
            u.password

    deftest_password_verification(self):
        u = User(password = 'cat')
        self.assertTrue(u.verify_password('cat'))
        self.assertFalse(u.verify_password('dog'))

    deftest_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 importBlueprint

#创建认证蓝本
auth = Blueprint('auth', __name__)

from . import views

蓝本中的路由和视图函数

app/auth/views.py

添加一个/login路由,渲染同名占位模板

render_template()指定的模板文件保存在auth文件夹中,这个文件夹必须在app/templates中创建,因为Flask认为模板的路径是相对于程序模板文件夹而言的。

为避免与main蓝本和后序添加的蓝本发生模板命名冲突,可以把蓝本使用的模板保存在单独的文件夹中

from flask importrender_template
from . importauth

#蓝本中的路由和视图函数
@auth.route('/login')
deflogin():
    return render_template('auth/login.html')

附加蓝本

app/__init__.py

url_prefix是可选参数,使用这个参数后,注册蓝本中定义的所有路由都会加上指定的前缀,本例中,/login路由会注册成/auth/login,在web服务器中,完整的URL就变成了http://locahost:5000/auth/login

from flask importFlask, render_template
from flask_bootstrap importBootstrap
from flask_mail importMail
from flask_moment importMoment
from flask_sqlalchemy importSQLAlchemy
from config importconfig

bootstrap =Bootstrap()
mail =Mail()
moment =Moment()
db =SQLAlchemy()

defcreate_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 importauth as auth_blueprint
    app.register_blueprint(auth_blueprint, url_prefix='/auth')

    from .main importmain as main_blueprint
    app.register_blueprint(main_blueprint)

    #附加路由和自定义的错误页面

    return app

使用Flask-login认证用户

1、安装flask-login

10、密码扩展,使用Flask-Login认证用户第2张

2、准备用于登录的用户模型

要想使用Flask-login扩展,程序的User模型必须实现几个方法,这四个方法可以直接实现。另一种简单的替代方案,是使用Flask-Login提供的UserMixin类,其中包含这些方法的默认实现,并能满足大多数需求

10、密码扩展,使用Flask-Login认证用户第3张

2、修改User,支持用户登录

app/models.py

示例中还添加了email字段,在这个程序中,用户使用电子邮件地址登录,因为对于用户而言,用户更不容易忘记自己的电子邮件地址

from . importdb
from werkzeug.security importgenerate_password_hash, check_password_hash
from flask_login importUserMixin

#定义数据库模型
classRole(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


classUser(UserMixin, 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))
    email = db.Column(db.String(64), unique=True, index=True)

    #在User模型中加入密码散列
@property
    defpassword(self):
        raise AttributeError('password is not a readable attribute')

    @password.setter
    defpassword(self, password):
        self.password_hash =generate_password_hash(password)

    defverify_password(self, password):
        returncheck_password_hash(self.password_hash, password)

    def __repr__(self):
        return '<User %r>' %self.username

3、初始化Flask-Login

app/__init__.py

LoginManager对象的session_protection属性可以设置为None、‘basic’、‘strong’,以提供不同的安全等级防止用户会话遭篡改

设为‘strong’时,Flask-Login会记录客户端IP地址和浏览器的用户代理信息,如果发现异常就登出用户

login_view属性设置登录页面的端点

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
from flask_login import LoginManager
bootstrap = Bootstrap()
mail = Mail()
moment = Moment()
db = SQLAlchemy()
# 初始化Flask-Login
login_manager = LoginManager()
login_manager.session_protection = 'strong'
login_manager.login_view = 'auth.login'

def create_app(config_name):
app = Flask(__name__)
app.config.from_object(config['default'])
config['default'].init_app(app)
login_manager.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

4、加载用户的回调函数

Flask-Login要求程序实现一个回调函数,使用指定的标识符加载这个用户,这个函数定义如下

app/models.py

加载用户的回调函数接受Uicode字符串形式表示的用户标识符。如果能找到用户,这个函数必须返回用户对象,否则应该返回None

#加载用户回调函数
from . importlogin_manager

@login_manager.user_loader
defload_user(user_id):
    return User.query.get(int(user_id))

5、保护路由

如果未认证的用户访问这个路由, Flask-Login 会拦截请求,把用户发往登录页面。

from flask.ext.login importlogin_required

@app.route('/secret')
@login_required
defsecret():
    return 'Only authenticated users are allowed!'

6、添加登录表单
app/auth/forms.py: 登录表单
包含一个用于输入电子邮件地址的文本字段、一个密码字段、一个“记住我”复选框和提交按钮

电子邮件字段用到了 WTForms 提供的 Length() Email() 验证函数。 PasswordField 类表示属性为 type="password" <input> 元素。 BooleanField 类表示复选框

from flask.ext.wtf importForm
from wtforms importStringField, PasswordField, BooleanField, SubmitField
from wtforms.validators importRequired, Length, Email

classLoginForm(Form):
    email = StringField('Email', validators=[Required(), Length(1, 64),Email()])
    password = PasswordField('Password', validators=[Required()])
    remember_me = BooleanField('Keep me logged in')
    submit = SubmitField('Log In')

7、导航条中的 Sign In Sign Out 链接
app/templates/base.html

判断条件中的变量 current_user Flask-Login 定义,且在视图函数和模板中自动可用。这个变量的值是当前登录的用户, 如果用户尚未登录,则是一个匿名用户代理对象。如果是匿名用户, is_authenticated() 方法返回 False。所以这个方法可用来判断当前用户是否已经登录

<ul class="nav navbar-nav navbar-right">{% if current_user.is_authenticated() %}
    <li><a href="{{ url_for('auth.logout') }}">Sign Out</a></li>{% else %}
    <li><a href="{{ url_for('auth.login') }}">Sign In</a></li>{% endif %}
</ul>

8、登入用户
视图函数 login() 的实现如下
app/auth/views.py 登录路由

这个视图函数创建了一个 LoginForm 对象 ,当请求类型是 GET 时,视图函数直接渲染模板,即显示表单。当表单在 POST 请求中提交时,Flask-WTF 中的 validate_on_submit() 函数会验证表单数据,然后尝试登入用户

from flask importrender_template, redirect, request, url_for, flash
from flask.ext.login importlogin_user

from . importauth
from ..models importUser
from .forms importLoginForm

@auth.route('/login', methods=['GET', 'POST'])
deflogin():
    form =LoginForm()
    ifform.validate_on_submit():
        user = User.query.filter_by(email=form.email.data).first()
        if user is not None anduser.verify_password(form.password.data):
            login_user(user, form.remember_me.data)
            return redirect(request.args.get('next') or url_for('main.index'))
        flash('Invalid username or password.')
    return render_template('auth/login.html', form=form)

9、更新登录模板以渲染表单

app/templates/auth/login.html 渲染登录表单

{% extends "base.html" %}
{% import "bootstrap/wtf.html" as wtf %}

{% block title %}Flasky - Login{% endblock %}
{% block page_content %}
<div class="page-header">
    <h1>Login</h1>
</div>
<div class="col-md-4">{{ wtf.quick_form(form) }}
</div>{% endblock %}

10、登出用户
退出路由的实现如下
app/auth/views.py 退出路由

为了登出用户,这个视图函数调用 Flask-Login 中的 logout_user() 函数,删除并重设用户会话。随后会显示一个 Flash 消息,确认这次操作,再重定向到首页,这样登出就完成了

from flask.ext.login importlogout_user, login_required

@auth.route('/logout')
@login_required
deflogout():
    logout_user()
    flash('You have been logged out.')
    return redirect(url_for('main.index'))

11、测试登录
为验证登录功能可用,可以更新首页,使用已登录用户的名字显示一个欢迎消息
app/templates/index.html:为已登录的用户显示一个欢迎消息

Hello,
{% if current_user.is_authenticated() %}
    {{ current_user.username }}
{% else %}
    Stranger
{% endif %}!

在这个模板中再次使用 current_user.is_authenticated() 判断用户是否已经登录

免责声明:文章转载自《10、密码扩展,使用Flask-Login认证用户》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇Vue watch监听基于Emgu CV+百度人脸识别,实现视频动态 人脸抓取与识别下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

websocket实时监控画面

  Ajax轮询是通过特定的的时间间隔(如每1秒),由浏览器对服务器发出HTTP请求,然后由服务器返回最新的数据给客户端的浏览器。这种简单粗暴模式有一个明显的缺点,就是浏览器需要不断的向服务器发出请求,HTTP请求可能包含较长的头部,其中真正有效的数据可能只是很小的一部分,显然这样会浪费很多的带宽等资源(对于很多局域网内的企业应用,这个简单粗暴模式确实解决...

AttributeError: module 'DBBase' has no attribute 'DBBase'

AttributeError: module 'DBBase' has no attribute 'DBBase' pycharm不会将当前文件目录自动加入自己的sourse_path。右键make_directory as-->Sources Root将当前工作的文件夹加入source_path就可以了。 还有一点:models加一个点,表示同级目...

03 flask数据库操作、flask-session、蓝图

ORM ORM 全拼Object-Relation Mapping,中文意为 对象-关系映射。主要实现模型对象到关系数据库数据的映射。 1.优点 : 只需要面向对象编程, 不需要面向数据库编写代码. 对数据库的操作都转化成对类属性和方法的操作. 不用编写各种数据库的sql语句. 实现了数据模型与数据库的解耦, 屏蔽了不同数据库操作上的差异....

Jinja2学习

模板: 知名模板引擎:jinja2(flask默认的模板引擎) Mako template模板路径: 1.在渲染模板的时候,会默认从根目录下的templates目录下查找模板文件 2.也可以自定义模板路径,Flask类的构造函数中定义了模板路径参数,所以可以在Flask初始化的时候指定template_folder参数。模板变量传参: 单个变量可直接放在r...

django面试题必问

1、谈谈你对http协议的认识。 HTTP协议(HyperText Transfer Protocol,超文本传输协议)是用于从WWW服务器传输超文本到本地浏览器的传送协议。它可以使浏览器更加高效,使网络传输减少。它不仅保证计算机正确快速地传输超文本文档,还确定传输文档中的哪一部分,以及哪部分内容首先显示(如文本先于图形)等。HTTP是一个应用层协议,由请...

flask接口入门实现简单的登录注册(一)

和网上的博客app不同,由于需求主要写一些小程序的借口 首先是初始化app __init__.py importos from flask importFlask def create_app(test_config=None): #create and configure the app app = Flask(__name__,...