zoukankan      html  css  js  c++  java
  • 10、密码扩展,使用Flask-Login认证用户

    密码扩展

    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

    注:

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

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

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

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

    在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

    使用Flask-login认证用户

    1、安装flask-login

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

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

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

    app/models.py

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

    from . import db
    from werkzeug.security import generate_password_hash, check_password_hash
    from flask_login import UserMixin
    
    #定义数据库模型
    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(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
        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

    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 . import login_manager
    
    @login_manager.user_loader
    def load_user(user_id):
        return User.query.get(int(user_id))

    5、保护路由

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

    from flask.ext.login import login_required
    
    @app.route('/secret')
    @login_required
    def secret():
        return 'Only authenticated users are allowed!'

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

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

    from flask.ext.wtf import Form
    from wtforms import StringField, PasswordField, BooleanField, SubmitField
    from wtforms.validators import Required, Length, Email
    
    class LoginForm(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 import render_template, redirect, request, url_for, flash
    from flask.ext.login import login_user
    
    from . import auth
    from ..models import User
    from .forms import LoginForm
    
    @auth.route('/login', methods=['GET', 'POST'])
    def login():
        form = LoginForm()
        if form.validate_on_submit():
            user = User.query.filter_by(email=form.email.data).first()
            if user is not None and user.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 import logout_user, login_required
    
    @auth.route('/logout')
    @login_required
    def logout():
        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() 判断用户是否已经登录 

     

     

     

     

  • 相关阅读:
    java高级程序设计(第十周)
    java高级程序设计(第五周)
    java高级程序设计(第四周)
    期末设计(第十四周)
    期末设计(第十三周)
    期末设计(计划进度表)
    Java学习笔记(六)
    Java学习笔记(六)
    Java学习笔记(五)
    Java学习笔记(四)
  • 原文地址:https://www.cnblogs.com/lw-monster/p/11785676.html
Copyright © 2011-2022 走看看