【Flask微电影】13.管理员登录、退出、装饰器进行访问控制

个人博客,欢迎查看:https://blog.starmeow.cn/

Github地址:https://github.com/xyliurui/FlaskMovie

管理员登录

  • app/__init__.py中创建db对象(将以前的app/models.py的db对象移动过去)
  • app/models.py中导入db对象
  • app/admin/forms.py中定义表单验证功能,需要出啊关键表单:LoginForm
  • app/templates/admin/login.html中使用表单字段、信息验证、消息闪现
  • app/admin/views.py中login视图处理登录请求,将登陆信息保存会话
  • app/admin.views.py中增加登录装饰器,然后对其他视图进行访问控制

优化代码结构

models.py中的db对象移动到app/__init__.py

【Flask微电影】13.管理员登录、退出、装饰器进行访问控制_第1张图片
image.png

app/__init__.py中修改

from flask import Flask, render_template
from flask_sqlalchemy import SQLAlchemy

app = Flask(__name__)  # 创建app对象
app.debug = True  # 开启调试模式
# app.config["SQLALCHEMY_DATABASE_URI"] = "mysql+pymysql://root:[email protected]:3306/movie"  # 定义数据库连接,传入连接,默认端口3306,可不写
app.config["SQLALCHEMY_DATABASE_URI"] = "mysql+mysqlconnector://root:[email protected]:3306/movie"
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = True

# 定义db对象,实例化SQLAlchemy,传入app对象
db = SQLAlchemy(app)

from app.home import home as home_blueprint
from app.admin import admin as admin_blueprint

# 注册蓝图
app.register_blueprint(home_blueprint)
app.register_blueprint(admin_blueprint, url_prefix="/admin")

app/models.py中导入db对象,原来的app配置就不需要了

from app import db
# 。。。

最终代码结构如下

【Flask微电影】13.管理员登录、退出、装饰器进行访问控制_第2张图片
image.png

添加全局404页面

app/templates/下创建404.html




    
    消失在宇宙星空中的404页面
    



迷失在太空中!

返回首页

app/__init__.py中添加404视图

# 添加全局404页面
@app.errorhandler(404)
def page_not_found(error):
    return render_template('404.html'), 404

创建管理员登录表单forms.py

修改app/admin/forms.py

后台登陆需要验证账号,密码

安装flask表单库

> pip install flask-wtf

安装的版本为 WTForms-2.2.1 flask-wtf-0.14.2

创建登录表单类

app/admin/forms.py

from flask_wtf import FlaskForm  # 表单基类
from wtforms import StringField, PasswordField, SubmitField
from wtforms.validators import DataRequired


class LoginFrom(FlaskForm):
    """管理员登录表单"""
    account = StringField(
        label='账号',
        validators=[
            DataRequired('请输入账号!')
        ],
        description='账号',
        render_kw={
            'class': "form-control",
            'placeholder': "请输入账号",
            'required': "required"
        }
    )

    pwd = PasswordField(
        label='密码',
        validators=[
            DataRequired('请输入密码!')
        ],
        description='密码',
        render_kw={
            'class': "form-control",
            'placeholder': "请输入密码",
            'required': "required"
        }
    )
    submit = SubmitField(
        label='登录',
        render_kw={
            'class': "btn btn-primary btn-block btn-flat"
        }
    )

后台login()引入表单

from app.admin.forms import LoginFrom

@admin.route("/login/")
def login():
    form = LoginFrom()
    return render_template('admin/login.html', form=form)

修改后台login.html页面的表单

上方为以前的进行注释,下方为表单

{##}
{{ form.account }}

{##}
{{ form.pwd }}

{#登录#}
{{ form.submit }}

访问 http://127.0.0.1:5000/admin/login/ 提示缺少CSRF

【Flask微电影】13.管理员登录、退出、装饰器进行访问控制_第3张图片
image.png

需要进行跨站伪装登录验证,通过查阅资料说明后,需要在html的form中添加{{ form.csrf_token }}字段

https://flask-wtf.readthedocs.io/en/latest/csrf.html

CSRF保护需要一个密钥来安全地对令牌进行签名。默认情况下,这将使用Flask应用程序的SECRET_KEY。如果想使用单独的令牌,可以设置WTF_CSRF_SECRET_KEY

这儿直接修改app/__init__.py给app添加SECRET_KEY

添加SECRET_KEY

先在终端模拟一个随机的字段,或者自己随便定义就行

import uuid
uuid.uuid4().hex
'b1b7ed6af47d4031acbdeb420658ba84'

修改app/__init__.py

# 。。。补充配置
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = True
app.config['SECRET_KEY'] = 'b1b7ed6af47d4031acbdeb420658ba84'
# 定义db对象,实例化SQLAlchemy,传入app对象
db = SQLAlchemy(app)
# 。。。

login中添加csrf_token

在表单中添加{{ form.csrf_token }}

{##} {{ form.account }}
{##} {{ form.pwd }}
{{ form.csrf_token }}
{#登录#} {{ form.submit }}
【Flask微电影】13.管理员登录、退出、装饰器进行访问控制_第4张图片
image.png

视图中处理登录提交的表单

有需要处理post提交的数据,需要指定处理的模式,包含getpost

@admin.route("/login/", methods=['GET', 'POST'])
def login():
    form = LoginFrom()
    if form.validate_on_submit():
        # 提交的时候验证表单
        data = form.data  # 获取表单的数据
        print(data)
    return render_template('admin/login.html', form=form)

可以得到值:{'account': 'user', 'pwd': 'password', 'submit': True, 'csrf_token': 'ImFkMzA0OTZiMWYxZGVkMjVhNmEyZmIzMDAwNGIwMjg2MjljZGY4ZGYi.DqnJxg.MpNuyswOQp-HdRgeJ26Q3X7aVAg'}

在forms.py中进行提交数据验证

验证用户输入的账号,然后通过查询Admin数据库,如果查到的集合数量为0,则表明账号不存在,则向前端抛出ValidationError

前端通过遍历account.errors来获取里面的错误信息,同样,密码错误信息也做同样的操作。

{% for err in form.account.errors %}
    
{{ err }}
{% endfor %} {% for err in form.pwd.errors %}
{{ err }}
{% endfor %}
from wtforms.validators import DataRequired, ValidationError
from app.models import Admin


class LoginFrom(FlaskForm):
    """管理员登录表单"""
    account = StringField(
        label='账号',
        validators=[
            DataRequired('请输入账号!')
        ],
        description='账号',
        render_kw={
            'class': "form-control",
            'placeholder': "请输入账号",
            'required': "required"
        }
    )

    pwd = PasswordField(
        label='密码',
        validators=[
            DataRequired('请输入密码!')
        ],
        description='密码',
        render_kw={
            'class': "form-control",
            'placeholder': "请输入密码",
            'required': "required"
        }
    )
    submit = SubmitField(
        label='登录',
        render_kw={
            'class': "btn btn-primary btn-block btn-flat"
        }
    )

    def validate_account(self, field):
        """从Admin数据库中,检测账号是否存在,如果不存在则在account.errors中添加错误信息"""
        account = field.data
        admin_num = Admin.query.filter_by(name=account).count()
        if admin_num == 0:
            raise ValidationError('账号不存在')

当输入的用户不存在时,会提示账号不存在

【Flask微电影】13.管理员登录、退出、装饰器进行访问控制_第5张图片
image.png

在Admin的模型中验证密码是否正确

修改app/views.py中的Admin模型,添加密码验证模块

# 定义管理员模型
class Admin(db.Model):
    # 。。。

    def check_pwd(self, input_pwd):
        """验证密码是否正确,直接将hash密码和输入的密码进行比较,如果相同则,返回True"""
        from werkzeug.security import check_password_hash
        return check_password_hash(self.pwd, input_pwd)

修改视图中逻辑密码错误提示

当用户提交表单后,从Admin数据库中查询到该登录管理员,然后检查从前端获取的密码是否正确

  • 如果密码正确,就需要把账号保存在session中,然后跳转到url参数的next,或者是跳转到后台的首页
  • 如果密码错误,就进行错误信息的返回,使用flash消息闪现,前端使用get_flashed_messages()来获取错误信息
@admin.route("/login/", methods=['GET', 'POST'])
def login():
    form = LoginFrom()
    if form.validate_on_submit():
        # 提交的时候验证表单
        data = form.data  # 获取表单的数据
        # print(data)
        login_admin = Admin.query.filter_by(name=data['account']).first()
        if not login_admin.check_pwd(data['pwd']):
            # 判断密码错误,然后将错误信息返回,使用flash用于消息闪现
            flash('密码错误!')
            return redirect(url_for('admin.login'))
        # 如果密码正确,session中添加账号记录,然后跳转到request中的next,或者是跳转到后台的首页
        session['login_admin'] = data['account']
        return redirect(request.args.get('next') or url_for('admin.index'))
    return render_template('admin/login.html', form=form)

模板login.html中获取flash的内容

修改login.html,使用get_flashed_messages()来获取flash错误信息

{% for msg in get_flashed_messages() %}
    
{% endfor %}

当用户输入一个数据库中存在的账号,但密码错误时,会弹出密码错误的提示

【Flask微电影】13.管理员登录、退出、装饰器进行访问控制_第6张图片
image.png

完成后login.html表单代码


管理员退出

修改app/views.py中的logout函数,当用户点击退出后,则删除该登录账号

@admin.route("/logout/")
def logout():
    session.pop('login_admin', None)  # 删除session中的登录账号
    return redirect(url_for("admin.login"))

使用装饰器进行访问控制

对于后台的很多视图,是不允许不登陆就能访问的,这时候需要使用装饰器来限制对这些视图的访问权限

使用url_for('admin.login', next=request.url)可以指定next下一跳的地址

app/views.py中添加

from functools import wraps


def admin_login_require(func):
    @wraps(func)
    def decorated_function(*args, **kwargs):
        if session.get('login_admin', None) is None:
            # 如果session中未找到该键,则用户需要登录
            return redirect(url_for('admin.login', next=request.url))
        return func(*args, **kwargs)
    return decorated_function

然后将后台视图中除了login()以外的所有所图都加上登录要求@admin_login_require,类似如下

@admin.route("/")
@admin_login_require
def index():
    return render_template('admin/index.html')

例如当在用户直接访问 http://127.0.0.1:5000/admin/ 的时候,如果没有登录,则会直接跳转到 http://127.0.0.1:5000/admin/login/?next=http%3A%2F%2F127.0.0.1%3A5000%2Fadmin%2F 这个url的登录页面,输入正确的帐密后,成功返回 http://127.0.0.1:5000/admin/ 页面

你可能感兴趣的:(【Flask微电影】13.管理员登录、退出、装饰器进行访问控制)