为 Flask 应用添加用户登录

Flask 是什么?我想打开这篇文章的你应该不陌生,但是我还引用维基百科上的内容做个简短的介绍。

Flask 是一个使用 Python 编写的轻量级 Web 应用框架。基于 Werkzeug WSGI 工具箱和 Jinja2 模板引擎。Flask 使用 BSD 授权。 Flask 被称为 “microframework”,因为它使用简单的核心,用 extension 增加其他功能。Flask 没有默认使用的数据库、窗体验证工具。然而,Flask 保留了扩增的弹性,可以用 Flask-extension 加入这些功能:ORM、窗体验证工具、文件上传、各种开放式身份验证技术。

简单来说 Flask 是一个使用 Python 语言的 Web 服务框架,但是 Flask 仅实现了部分功能,大多数功能通过扩展来实现,使用者可以用自己最熟悉的模块来实现自己的功能。

当然今天这篇文章不是来介绍 Flask 的,而是如何在 Flask 中增加用户管理「用户登录」的功能。Flask 是一个 Web 框架,在服务端需要实现的用户登录主要有两种方式,一个是通过网页登录,另一个是通过 API 登录。这里将带你实现这两种方式的用户登录。

网页中的用户登录实现

在 Flask 中网页的用户登录,主要通过 Flask-Login 扩展来完成, 通过 Flask-Login 可以实现以下功能:

  • 存储会话中活动用户的 ID,并允许你随意登入登出。
  • 让你限制已登入(或已登出)用户访问视图。
  • 实现棘手的“记住我”功能。
  • 保护用户会话免遭 Cookie 盗用。
  • 随后可能会与 Flask-Principal 或其它认证扩展集成。

在使用 Flask-Login 之前需要用 pip 来安装它,对 Flask-Login 的使用主要分为以下几个步骤。

一、建立 LoginManager 的实例,并使用 Flask 初始化 Flask-Login。

loginmanager = LoginManager()
loginmanager.init_app(app)

二、建立 userloader 的回调函数,该函数通过 userid 来获取 user 对象。

@login_manager.user_loader
    def load_user(user_id):
        """Loads the user. Required by the `login` extension."""
        user_instance = User.query.filter_by(id=user_id).first()
        if user_instance:
            return user_instance
        else:
            return None

三、建立用户类。用户类继承自 UserMixin,它提供了 isauthenticated、isactive、isanonymous、getid 等方法的默认实现。

四、建立用户登录页面,需要包含输入框及提交按钮。

五、建立登录视图,并在登录验证完成时调用 login_user 函数。

class Login(MethodView):
    def get(self):
        return render_template('user/login.html')

    def post(self):
        username = request.form['username']
        password = request.form['password']
        remember = True if request.form.get('remember') else False
        logger.info(remember)
        user = User.query.filter_by(name=username).first()
        if user and user.check_password(password):
            login_user(user, remember=remember)
            next = request.args.get('next')
            return redirect(next or url_for('brand.bar'))
        return render_template('user/login.html')

程序实现到现在,用户登录的整个过程已经完成,可以通过用户名和密码来实现用户的验证,但是你会发现所有的 url 你还是可以在没有登录的状态下访问,那么如何使需要登录的 url 处于保护状态呢?答案是使用 login_required 标签。

class BrandBar(MethodView):
    decorators = [login_required]

    def get(self):
        brands = [name[0] for name in db.session.query(Brands.name).all()]
        # brands = db.session.query(Brands).all()
        return render_template('brand_bar.html', brands=brands)

当你访问一个需要登录才能访问的视图时,希望能够自动跳转到登录页面,此时还需要设置一个默认的登录视图,设置方法如下:

login_manager.login_view = 'user.login'

现在整个登录过程才算完整,当你访问 BrandBar 视图时,未登录时会自动跳转到登录页面,完整登录后会自动跳转到 BrandBar 视图。

API 中的用户登录实现

REST API 是通过 API 来访问服务端数据,服务端返回的数据通常是 JSON 格式,API 的用户登录实现我们通过 flaskhttpauth 来完成。通过 flaskhttpauth 可以通过 token 来登录,而不需要每次都在 http 请求中携带用户名和密码。

在使用 flaskhttpauth 前需要先安装「采用统一的 pip 来安装 python 模块」和初始化 flaskhttpauth。

from flask_httpauth import HTTPBasicAuth

auth = HTTPBasicAuth()

设置 verify_password 回调函数。

@auth.verify_password
def verify_password(username_or_token, password):
    # first try to authenticate by token
    user = User.verify_auth_token(username_or_token, current_app)
    logger.info('username:{} password:{}'.format(username_or_token, password))
    if not user:
        # try to authenticate with username/password
        user = User.query.filter_by(name = username_or_token).first()
        if not user or not user.check_password(password):
            return False
    g.current_user = user
    return True

verify_password 回调函数同时接收用户名/密码、token 两种方式来完成登录。在用户类中需要实现通过密码和 token 进行验证的方法。

class User(db.Model, UserMixin, CRUDMixin):

    ....

    def check_password(self, password):
        """Check passwords. If passwords match it returns true, else false."""

        if self.password is None:
            return False
        return check_password_hash(self.password, password)

    def generate_auth_token(self, app, expiration = 600):
        s = Serializer(app.config['SECRET_KEY'], expires_in = expiration)
        return s.dumps({ 'id': self.id })

    @staticmethod
    def verify_auth_token(token, app):
        s = Serializer(app.config['SECRET_KEY'])
        try:
            data = s.loads(token)
        # except SignatureExpired:
        #     return None # valid token, but expired
        except BadSignature:
            return None # invalid token
        user = User.query.get(data['id'])
        return user

是在登录完成后通过 API 来获取 token ,以后访问 API 可以直接携带 token 无需使用用户名和密码进行登录。

通过以下命令实现通过用户名和密码来获取认证 token

curl -u test:test -i -X GET http://127.0.0.1:5000/api/v0.1/user/token

获取 token 后,即可通过 token 来访问

curl -X GET -H "Authorization: Bearer secret-token-1" http://127.0.0.1:5000/api/v0.1/user/token

需要经过认证才能访问的 API 请至于 auth.login_required 的保护之中。

后记

本次实现网页端和 API 端简单的用户认证功能,还需许多功能需要进一步发现,比如 flask_httpauth 的其他认证方式,网页端和 API 之间的认证打通,这些功能留待以后进一步学习吧!

请关注我吧!

原文发布于微信公众号 - keinYe(keinYe_zh)

原文发表时间:2019-09-09

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

扫码关注云+社区

领取腾讯云代金券