美文网首页Flask微电影网站开发
【Flask微电影】13.管理员登录、退出、装饰器进行访问控制

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

作者: 吾星喵 | 来源:发表于2018-11-08 21:51 被阅读92次

    个人博客,欢迎查看: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

    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:root@127.0.0.1:3306/movie"  # 定义数据库连接,传入连接,默认端口3306,可不写
    app.config["SQLALCHEMY_DATABASE_URI"] = "mysql+mysqlconnector://root:root@127.0.0.1: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
    # 。。。
    

    最终代码结构如下

    image.png

    添加全局404页面

    app/templates/下创建404.html

    <!doctype html>
    <html lang="zh-cn">
    <head>
        <meta charset="utf-8">
        <title>消失在宇宙星空中的404页面</title>
        <link href="{{ url_for('static', filename='404/404.css') }}" rel="stylesheet" type="text/css">
    </head>
    <body>
    <!-- 代码 开始 -->
    <div class="fullScreen" id="fullScreen">
        <img class="rotating" src="{{ url_for('static', filename='404/spaceman.svg') }}">
        <div class="pagenotfound-text">
            <h1>迷失在太空中!</h1>
            <h2><a href="#">返回首页</a></h2>
        </div>
        <canvas id="canvas2d"></canvas>
    </div>
    <script type="text/javascript" src="{{ url_for('static', filename='404/404.js') }}"></script>
    <!-- 代码 结束 -->
    </body>
    </html>
    

    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页面的表单

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

    {#<input name="user" type="text" class="form-control" placeholder="请输入账号!">#}
    {{ form.account }}
    
    {#<input name="pwd" type="password" class="form-control" placeholder="请输入密码!">#}
    {{ form.pwd }}
    
    {#<a id="btn-sub" type="submit" class="btn btn-primary btn-block btn-flat">登录</a>#}
    {{ form.submit }}
    

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

    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 action="" method="post" id="form-data">
        <div class="form-group has-feedback">
            {#<input name="user" type="text" class="form-control" placeholder="请输入账号!">#}
            {{ form.account }}
            <span class="glyphicon glyphicon-envelope form-control-feedback"></span>
            <div class="col-md-12" id="input_user"></div>
        </div>
        <div class="form-group has-feedback">
            {#<input name="pwd" type="password" class="form-control" placeholder="请输入密码!">#}
            {{ form.pwd }}
            <span class="glyphicon glyphicon-lock form-control-feedback"></span>
            <div class="col-md-12" id="input_pwd"></div>
        </div>
        {{ form.csrf_token }}
        <div class="row">
            <div class="col-xs-8">
            </div>
            <div class="col-xs-4">
                {#<a id="btn-sub" type="submit" class="btn btn-primary btn-block btn-flat">登录</a>#}
                {{ form.submit }}
            </div>
        </div>
    </form>
    
    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 %}
        <div class="col-md-12" id="input_user" style="color: red">{{ err }}</div>
    {% endfor %}
    
    {% for err in form.pwd.errors %}
        <div class="col-md-12" id="input_pwd" style="color: red">{{ err }}</div>
    {% 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('账号不存在')
    

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

    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() %}
        <p class="login-box-msg" style="color: red">{{ msg }}</p>
    {% endfor %}
    

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

    image.png

    完成后login.html表单代码

    <div class="login-box-body">
        {% for msg in get_flashed_messages() %}
            <p class="login-box-msg" style="color: red">{{ msg }}</p>
        {% endfor %}
        <form action="" method="post" id="form-data">
            <div class="form-group has-feedback">
                {#<input name="user" type="text" class="form-control" placeholder="请输入账号!">#}
                {{ form.account }}
                <span class="glyphicon glyphicon-envelope form-control-feedback"></span>
                {% for err in form.account.errors %}
                    <div class="col-md-12" id="input_user" style="color: red">{{ err }}</div>
                {% endfor %}
            </div>
            <div class="form-group has-feedback">
                {#<input name="pwd" type="password" class="form-control" placeholder="请输入密码!">#}
                {{ form.pwd }}
                <span class="glyphicon glyphicon-lock form-control-feedback"></span>
                {% for err in form.pwd.errors %}
                    <div class="col-md-12" id="input_pwd" style="color: red">{{ err }}</div>
                {% endfor %}
            </div>
            {{ form.csrf_token }}
            <div class="row">
                <div class="col-xs-8">
                </div>
                <div class="col-xs-4">
                    {#<a id="btn-sub" type="submit" class="btn btn-primary btn-block btn-flat">登录</a>#}
                    {{ form.submit }}
                </div>
            </div>
        </form>
    </div>
    

    管理员退出

    修改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.管理员登录、退出、装饰器进行访问控制

        本文链接:https://www.haomeiwen.com/subject/ocugxqtx.html