文档章节

网站后端.Flask.实战-社交博客开发-flask-login认证用户?

满满李
 满满李
发布于 2016/06/26 10:57
字数 2396
阅读 575
收藏 2

用户模型

1.用户登录后认证状态需要被记录,这样浏览不同的页面才能记住这个状态,flask-login专门用来管理用户认证系统中的认证状态,且不依赖特定的认证机制

2.用户模型必须实现is_authenticated/is_active/is_anonymous/get_id四个方法才可以集成扩展,flask-login为我们提供了UserMixin类,包含了这些方法的默认实现,只需用户模型继承此类即可

is_authenticated()

如果用户已登录,必须返回True,否则返回False

is_active()

如果允许用户登录,必须返回True,否则返回false,如果禁用账户,可返回False

is_anonymous()

对普通用户必须返回False

get_id()

必须返回用户对象的唯一标识符,常为主键id字段,使用Unicode编码字符串

FlaskWeb/app/models.py

#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
#
# Authors: limanman
# OsChina: http://my.oschina.net/pydevops/
# Purpose:
#
"""
from . import db
from flask_login import UserMixin
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, nullable=False, index=True)
    users = db.relationship('User', backref='role', lazy='dynamic')

class User(UserMixin, db.Model):
    __tablename__ == 'users'
    id = db.Column(db.Integer, primary_key=True)
    email = db.Column(db.String(64), unique=True, nullable=False, index=True)
    username = db.Column(db.String(64), unique=True, nullable=False, index=True)
    password_hash = db.Column(db.String(128), nullable=False)
    role_id = db.Column(db.Integer, db.ForeignKey('roles.id'))

    @property
    def password(self):
        raise AttributeError(u'password 不允许读取.')

    @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)

说明:User模型继承UserMixin,于是用户对象将拥有is_authenticated/is_active/is_anonymous/get_id属性方法,特别是基于上下文的全局变量current_user,可以通过这四个属性判断用户当前状态

FlaskWeb/app/__init__.py

#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
#
# Authors: limanman
# OsChina: http://my.oschina.net/pydevops/
# Purpose:
#
"""
from flask import Flask
from config import config
from flask_mail import Mail
from flask_moment import Moment
from flask_login import LoginManager
from flask_bootstrap import Bootstrap
from flask_sqlalchemy import SQLAlchemy


mail = Mail()
db = SQLAlchemy()
moment = Moment()
bootstrap = Bootstrap()
loginmanager = LoginManager()
loginmanager.login_view = 'auth.login'
loginmanager.session_protection = 'strong'
loginmanager.login_message = u'你需要先登录才能继续本操作'

def create_app(env='default'):
    env_config = config.get(env)
    app = Flask(__name__)
    app.config.from_object(env_config)

    db.init_app(app)
    mail.init_app(app)
    moment.init_app(app)
    bootstrap.init_app(app)
    loginmanager.init_app(app)

    from .main import main as main_blueprint
    from .auth import auth as auth_blueprint
    app.register_blueprint(main_blueprint)
    app.register_blueprint(auth_blueprint, url_prefix='/auth')

    return app

说明:集成flask-login时可设置loginmanager.session_protection为None/basic/strong,防止用户会话被篡改,此设置会记录客户端的IP地址和浏览器的用户代理信息,如果发现异动则强制登出用户,loginmanager.login_view设置登录视图端点,需要验证登录才可以访问其它路由时非常有用,loginmanager.login_message为默认需要验证登录时flash的默认消息.

FlaskWeb/app/models.py

#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
#
# Authors: limanman
# OsChina: http://my.oschina.net/pydevops/
# Purpose:
#
"""
from . import db, loginmanager
from flask_login import UserMixin
from werkzeug.security import generate_password_hash, check_password_hash


@loginmanager.user_loader
def load_user(user_id):
    return User.query.get(int(user_id))

class Role(db.Model):
    __tablename__ == 'roles'
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(64), unique=True, nullable=False, index=True)
    users = db.relationship('User', backref='role', lazy='dynamic')

class User(UserMixin, db.Model):
    __tablename__ == 'users'
    id = db.Column(db.Integer, primary_key=True)
    email = db.Column(db.String(64), unique=True, nullable=False, index=True)
    username = db.Column(db.String(64), unique=True, nullable=False, index=True)
    password_hash = db.Column(db.String(128), nullable=False)
    role_id = db.Column(db.Integer, db.ForeignKey('roles.id'))

    @property
    def password(self):
        raise AttributeError(u'password 不允许读取.')

    @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)

说明:需要在modles.py数据库模型文件中给loginmanager定义一个回调函数,当用户登录时会向session['user_id']插入当前用户对象的主键id,当访问其它页面时,如果session['user_id']依然存在,则尝试通过回调函数和用户id来还原全局用户对象current_user,否则尝试从本地cookie加载之前用户登录信息重新尝试登录,此回调函数要求用户对象存在就返回用户对象,否则返回None

执行:python manager.py db drop,python manager.py db init,重置下数据库中的数据,并在视图模块儿中导入数据模型中User对象,这样回调函数才会被设置,否则可能会由于你本地cookie存在session['user_id']值会出现No user_loader has been installed for this错误,其实是因为回调函数没有被设置,却给回调函数传递给一个session['id']让程序还原current_user对象,这不是痴人说梦吗?

保护路由

1.为了保护路由只让认证用户访问,flask-login提供了login_required和fresh_login_required装饰器,前者包含了cookie自动登录,后者必须手动登录

FlaskWeb/app/auth/views.py

#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
#
# Authors: limanman
# OsChina: http://my.oschina.net/pydevops/
# Purpose:
#
"""
from . import auth
from ..models import User
from flask import render_template
from flask_login import login_required, fresh_login_required


@auth.route('/')
@fresh_login_required
def index():
    pass

@auth.route('/login', methods=['GET', 'POST'])
def login():
    return render_template('auth/login.html')

说明:如果未认证的用户访问这个路由,flask-login会拦截请求,把用户发往登录页面

添加登录表单

1.登录表单包含一个用于电子邮件地址的文本字段,一个密码字段,一个"记住密码"复选框,表单使用flask-wtf生成,使用flask-bootstrap的wtf宏前端展现

FlaskWeb/app/auth/forms.py

#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
#
# Authors: limanman
# OsChina: http://my.oschina.net/pydevops/
# Purpose:
#
"""
from ..models import User
from flask_wtf import Form
from wtforms import ValidationError
from wtforms import StringField, PasswordField, BooleanField, SubmitField
from wtforms.validators import Length, DataRequired, Email, Regexp

class LoginForm(Form):
    email = StringField(u'邮箱', validators=[
        DataRequired(u'请填写此字段'),
        Length(6, 64, u'长度必须在6-64之间'),
        Email(u'邮箱地址格式有误'),
    ])
    password = PasswordField(u'密码', validators=[
        DataRequired(u'请填写此字段'),
        Length(6, 128, u'长度必须在6-64之间'),
        Regexp(r'^[a-zA-Z0-9_][a-zA-Z0-9_]*$', 0, u'密码只能包含字母数字下划线')
    ])
    remeber_me = BooleanField(u'是否记住密码?', default=True)
    submit = SubmitField(u'登录')

    def validate_email(self, field):
        user = User.query.filter_by(email=field.data).first()
        if not user:
            raise ValidationError(u'邮箱地址还未注册')

说明:针对每个表单字段我们都设置了对应的验证器,需要说明的是在表单类中以validate_开头的,以表单字段结尾的,如上validate_email会自动被加载作为验证器,它接受一个字段类作为参数

FlaskWeb/app/templates/base.html

{%- extends 'bootstrap/base.html' -%}
{%- import 'bootstrap/wtf.html' as wtf -%}
{%- import 'bootstrap/utils.html' as utils -%}
{%- import 'bootstrap/fixes.html' as fixes -%}
{%- block html_attribs -%}
    {{ super() }}
    lang="zh-cn"
{%- endblock -%}
{%- block meta -%}
    {{ super() }}
    charset="utf-8"
{%- endblock -%}
{%- block title -%}
    {{ title|default('Flasky', true) }}
{%- endblock -%}
{%- block head -%}
    {{ super() }}
    {{ fixes.ie8() }}
    <link rel="shortcut icon"
          type="image/x-icon" href="{{ url_for('static', filename='favicon.ico') }}">
    <link rel="icon"
          type="image/x-icon" href="{{ url_for('static', filename='favicon.ico') }}">
{%- endblock -%}
{%- block navbar -%}
    <div class="navbar navbar-inverse" role="navigation">
        <div class="container">
            <div class="navbar-header">
                {# 说明: 先不支持响应式 #}
                <a class="navbar-brand" href="/">Flasky</a>
            </div>
            <div>
                <ul class="nav navbar-nav">
                    <li class="active"><a href="/">Home</a></li>
                </ul>
            </div>
            <div class="navbar-right">
                <ul class="nav navbar-nav">
                    {%- if current_user.is_authenticated -%}
                        <li><a href="#current_user_profile">{{ current_user.username }}</a></li>
                        <li><a href="#auth_logout">登出</a></li>
                    {%- else -%}
                        <li><a href="{{ url_for('auth.login') }}">登录</a></li>
                        <li><a href="#auth_register">注册</a></li>
                    {%- endif -%}
                </ul>
            </div>
        </div>
    </div>
{%- endblock -%}
{%- block content -%}
    <div class="container">
        {%- block page_content -%}{%- endblock -%}
    </div>
{%- endblock -%}
{%- block styles -%}
    {{ super() }}
{%- endblock -%}
{%- block scripts -%}
    <script type="text/javascript"
            src="{{ url_for('main.static',filename='js/fixes/respond.min.js') }}"></script>
    <script type="text/javascript"
            src="{{ url_for('main.static',filename='js/fixes/html5shiv.min.js') }}"></script>
    {{ super() }}
    {{ moment.include_moment(local_js=url_for('main.static', filename='js/moment-with-locales.min.js')) }}
{%- endblock -%}

说明:之前未使用独立模版目录和静态资源目录由于可以公用base.html基础模版,flask-login为上下文注入了一个全局对象current_user,可以在模版中任意引用,可以利用此特性添加一个动态的注册/登录/登录/用户信息导航栏,用来验证登录,更新首页

FlaskWeb/app/templates/auth/login.html

{%- extends 'base.html' -%}
{%- block page_content -%}
    <div class="page-header">
        {{ utils.flashed_messages(container=false, dismissible=true) }}
    </div>
    <div class="page-content">
        {{ wtf.quick_form(form, method='POST', role='form') }}
    </div>
{%- endblock -%}

说明:为了方便,直接使用flask-bootstrap的utils和wtf宏实现消息闪现和快捷表单创建,如果想个性化表单可单独设置每个表单字段,设置其id/class等额外属性控制其样式和行为.

登入用户

FlaskWeb/app/auth/views.py

#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
#
# Authors: limanman
# OsChina: http://my.oschina.net/pydevops/
# Purpose:
#
"""
from . import auth
from ..models import User
from .forms import LoginForm
from flask import render_template, flash, url_for, redirect
from flask_login import login_required, fresh_login_required, login_user

@auth.route('/')
@fresh_login_required
def index():
    pass

@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 and user.verify_password(form.password.data):
            flash(u'已成功登录', 'success')
            login_user(user, form.remeber_me.data)
            return redirect(url_for('main.index'))
        flash(u'用户名或密码错误', 'danger')
        return redirect(url_for('auth.login'))
    return render_template('auth/login.html', form=form)

说明:当GET请求时,视图函数直接渲染表单,POST请求时,form.validate_on_submit()验证表单,表单验证通过,通过输入的emial从数据库加载用户对象,如果存在并且密码通过验证则调用flask-login的login_user(user, form.remeber_me.data),第一个参数为要登录的用户对象,第二个参数为是否记住密码的Bool值,如果为False,关闭浏览器后用户会话就过期,下次用户访问时要重新登录,否则会在用户浏览器中写一个长期有效的cookie(session['user_id']),使用这个cookie可以复现用户会话

登出用户

1.flask-login提供现成的logout_user()函数,它会删除并重设用户会话,至此此用户将改变状态为未授权访问状态,加上fresh_login_required的修饰,如果没有手动重定向到其它页面会重新调用login_view至登录界面

Flasky/app/main/views.py

#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
#
# Authors: limanman
# OsChina: http://my.oschina.net/pydevops/
# Purpose:
#
"""
from . import auth
from ..models import User
from .forms import LoginForm
from flask import render_template, redirect, request, flash, url_for
from flask_login import login_user, fresh_login_required, login_required, logout_user

@auth.route('/', methods=['GET', 'POST'])
@login_required
def refuse():
    pass

@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 and user.verify_password(form.password.data):
            login_user(user, form.remeber_me.data)
            return redirect(url_for('main.index'))
        flash(u'用户名或密码错误', 'danger')
        return redirect(url_for('auth.login'))
    return render_template('auth/login.html', form=form)

@auth.route('/logout', methods=['GET', 'POST'])
@fresh_login_required
def logout():
    logout_user()
    flash(u'您已经成功登出', 'success')
    return redirect(url_for('main.index'))

说明:由于所有的视图函数必须返回response对象,fresh_login_required尝试从cookie中获取session['user_id']如果本地缓存被你强制清除则自动重写响应跳转到loginmanager.login_view定义的默认登录视图,地址栏中地址将变为http://127.0.0.1/auth/login?next=/auth/logout

© 著作权归作者所有

共有 人打赏支持
满满李
粉丝 20
博文 156
码字总数 101605
作品 0
杭州
运维
网站后端.Flask.实战-社交博客开发-认证实现?

1.flask-login扩展负责管理已登录用户的会话 2.Werkzeug负责计算密码散列值并进行核对 3.itsdangerous生成并核对指定时间有效的加密安全token 说明:要实现对用户跟踪,必须让程序在用户登录后...

满满李
2016/06/25
60
0
981764793/PyFly

PyFly 项目介绍 Flask + Layui Fly Template实现的一个社区项目,使用flask-admin实现了简单的后台管理功能,数据库使用Mongodb,前台实现功能:用户注册、登录、邮件激活、发帖、回帖、点赞...

981764793
04/28
0
0
Flask插件与资源整理

flask-script 支持命令行选相 使用示例 flask-bootstrap 集成Bootstrap flask-moment 本地化日期和时间 flask-wtf网络表单类防止跨站伪请求CSRF flask-sqlalchemy数据库框架ORM或ODM转换 数据...

晴难自控
2017/11/19
0
0
社区模板的 python 后端实现 - PyFly

项目介绍 PyFly 是 Flask + Layui Fly Template实现的一个社区项目,使用flask-admin实现了简单的后台管理功能,数据库使用Mongodb,前台实现功能:用户注册、登录、邮件激活、发帖、回帖、...

981764793
04/28
0
0
《Flask Web开发:基于Python的Web应用开发实战》.PDF

简介 本书不仅适合初级Web开发人员学习阅读,更是Python程序员用来学习高级Web开发技术的优秀参考书。 • 学习Flask应用的基本结构,编写示例应用; • 使用必备的组件,包括模板、数据库、W...

jackmk
2017/08/12
0
0

没有更多内容

加载失败,请刷新页面

加载更多

区块链10年了,还未落地,它失败了吗?

导读 几乎每个人,甚至是对通证持怀疑态度的人,都对区块链的技术有积极的看法,因为它有可能改变世界。然而,区块链技术问世已经10年了,我们仍然没有真正的用上区块链技术。 几乎每个人,甚...

问题终结者
14分钟前
0
0
20180921 su与sudo命令、限制root用户通过ssh远程登录

su 命令 用户切换。 su # 切换到root用户su username # 切换到username用户# su 后面加-时,会初始化当前用户的各种环境su - username # 指定用户执行某些命令 su - -c "touch /tm...

野雪球
28分钟前
1
0
Windows 下双 Python 开发环境配置

Windows 下双 Python 开发环境配置作者:老农民(刘启华)QQ: 46715422Email: 46715422@qq.com微信: 46715422 本人曾经在 Windows 下被两个版本环境折腾够呛,现在总结两个 Python...

新疆老农民
昨天
2
0
CentOS7全局安装composer

1. 下载composer-setup.php到当前目录 php -r "copy('https://install.phpcomposer.com/installer', 'composer-setup.php');" 2. 安装 php composer-setup.php 3. 将composer设置成全局 mv c......

月夜中徘徊
昨天
2
0
20180920上课截图

小丑鱼00
昨天
1
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部