为 Flask 添加用户登录

keinYe
keinYe 完成于

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

二、建立 user_loader 的回调函数,该函数通过 user_id 来获取 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,它提供了 is_authenticated、is_active、is_anonymous、get_id 等方法的默认实现。

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

五、建立登录视图,并在登录验证完成是调用 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 的用户登录实现我们通过 flask_httpauth 来完成。通过 flask_httpauth 可以通过 token 来登录,而不需要每次都在 http 请求中携带用户名和密码。

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

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 之间的认证打通,这些功能留待以后进一步学习吧!

Comments

comments powered by Disqus