在我们使用 Flask 构建一个系统时,用户登录注册是一个必不可少的过程,我们可以自己实现一个登录的功能,但是由于涉及的面很多,所以我们更多情况下还是寻求已有的模块来进行使用。

Flask 中比较常用的就是 Flask-Login 了,这里就以 Flask-Login 为例,给大家介绍一下如何使用Flask-Login 进行登录注销,以及帮助大家解答一些可能比较常见的问题。

使用入门

首先,我们先概述一下我们这里的例子,我们这个例子有三个 url,分别是:

1
2
3
/auth/login     用于登录
/auth/logout    用于注销
/test               用于测试,需要登录才能访问

好,这就是我们的前提概述了,下面我们就开始介绍。

安装必要的库

毫无疑问,我们要使用 Flask-Login ,那就必须安装它,安装使用 pip 还是很简单的,此外,因为我们登录涉及到登录表单,所以,还需要安装 wtform ,所以最后总共需要安装的有:

1
2
3
4
pip install Flask==0.10.1
pip install Flask-Login==0.3.2
pip install Flask-WTF==0.12
pip install WTForms==2.1

编写 web 框架

首先,在开始登录之前,我们先把整个 web 的框架搭建出来,也就是,我们要能够先在不登录的情况下访问到上面提到的三个url,这个架构比较简单了,我就直接放在一个叫做 app.py 的文件中了。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#!/usr/bin/env python
# encoding: utf-8
from flask import Flask, Blueprint

app = Flask(__name__)

# url redirect
auth = Blueprint('auth', __name__)

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

@auth.route('/logout', methods=['GET', 'POST'])
def logout():
    return "logout page"    

# test method
@app.route('/test')
def test():
    return "yes , you are allowed"

app.register_blueprint(auth, url_prefix='/auth')
app.run(debug=True)

现在,我们可以尝试一下运行一下这个框架,使用

1
python app.py

运行即可,然后打开浏览器,分别访问一下:

http://localhost:5000/test
http://localhost:5000/auth/login
http://localhost:5000/auth/logout

看一下是否都正常。

设置登录才能查看

现在框架已经设置完毕,那么我们就可以尝试一下设置登录需求的,也就是说我们将 test 和 auth/logout 这两个 page 设置成登录之后才能查看。因为这个功能已经和 login 有关系了,所以这时我们就需要使用到 Flask-Login 了。

我们可以这样来改变代码:

    #!/usr/bin/env python
    # encoding: utf-8
    from flask import Flask, Blueprint
    from flask.ext.login import LoginManager, login_required

    app = Flask(__name__)

    # 以下这段是新增加的============
    app.secret_key = 's3cr3t'
    login_manager = LoginManager()
    login_manager.session_protection = 'strong'
    login_manager.login_view = 'auth.login'
    login_manager.init_app(app)

    @login_manager.user_loader
    def load_user(user_id):
        return None
    # 以上这段是新增加的============

    auth = Blueprint('auth', __name__)

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

    @auth.route('/logout', methods=['GET', 'POST'])
    @login_required
    def logout():
        return "logout page"

    # test method
    @app.route('/test')
    @login_required
    def test():
        return "yes , you are allowed"

    app.register_blueprint(auth, url_prefix='/auth')
    app.run(debug=True)

其实我们就增加了两项代码,一项是初始化 LoginManager 的,另外一项就是给 test 和 auth.logout 添加了 login_required 的装饰器,表示要登录了才能访问。

你也许会有疑问:@login_manager.user_loader 这个装饰器是干嘛用的。这个在后面的 Question 中有详细得介绍,在这里我们只需要知道这个函数需要返回指定 id 的用户,如果没有就返回 None。这里因为设置框架所以就默认返回 None。

用户授权

到此,我们发现访问 test 是不能访问的,会被重定向到 login 的那个 page。那我们看一下我们现在的代码,我们发现 login_required 有了,那么就差 login 了,好,接下来就写 login,所以我们就来看看 Flask-Login 的文档,找找,我们会发现一个叫做:login_user 的函数,看看它的原型:

1
flask.ext.login.login_user(user, remember=False, force=False, fresh=True)

这里需要一个 user 的对象,所以我们就先构建一个 Model,其实,这个 Model 还是有一点讲究的,所以我们最好是继承自 Flask-LoginUserMixin , 然后需要实现几个方法, Model 为:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# user models
    class User(UserMixin):
        def is_authenticated(self):
            return True

        def is_actice(self):
            return True

        def is_anonymous(self):
            return False

        def get_id(self):
            return "1"

这里给所有的函数都返回了默认值,默认对应的情况是这个用户已经登录,并且是有效的。

然后在 login 的 view 里面 login_user,logout 的 view 里面 logout_user,这样整个登录过程就连接起来了,最后的代码是这样的:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#!/usr/bin/env python
    # encoding: utf-8
    from flask import Flask, Blueprint
    from flask.ext.login import (LoginManager, login_required, login_user,
                                 logout_user, UserMixin)

    app = Flask(__name__)


    # user models
    class User(UserMixin):
        def is_authenticated(self):
            return True

        def is_actice(self):
            return True

        def is_anonymous(self):
            return False

        def get_id(self):
            return "1"

    # flask-login
    app.secret_key = 's3cr3t'
    login_manager = LoginManager()
    login_manager.session_protection = 'strong'
    login_manager.login_view = 'auth.login'
    login_manager.init_app(app)

    @login_manager.user_loader
    def load_user(user_id):
        user = User()
        return user

    auth = Blueprint('auth', __name__)

    @auth.route('/login', methods=['GET', 'POST'])
    def login():
        user = User()
        login_user(user)
        return "login page"

    @auth.route('/logout', methods=['GET', 'POST'])
    @login_required
    def logout():
        logout_user()
        return "logout page"

    # test method
    @app.route('/test')
    @login_required
    def test():
        return "yes , you are allowed"

    app.register_blueprint(auth, url_prefix='/auth')
    app.run(debug=True)

Summary

到此,这就是一个比较精简的 Flask-Login 教程了,通过这个框架大家可以自行扩展,达到更丰富的功能,后续会连续这个 Login 功能继续讲解一下权限控制。

Question

未登录访问鉴权页面如何处理?

如果未登录访问了一个作了 login_required 限制的 view,那么 Flask-Login 会默认 flash一条消息,并且将重定向到 log in view,如果你没有指定 log in view,那么 Flask-Login 将会抛出一个 401 错误。

如何指定 log in view

指定 log in view 只需要直接设置 login_manager 即可:

1
login_manager.login_view = "auth.login"

如何自定义 flash 消息

如果需要自定义 flash 的消息,那么还是简单设置 login_manager,

1
login_manager.login_message = u"请登录!"

还可以设置 flash 消息的级别,一般设置成 info 或者 error:

1
login_manager.login_message_category = "info"

自定义未登录处理函数

如果你不想使用默认的规则,那么你也可以自定义未登录情况的处理函数,只需要使用 login_manager 的 unauthorized_handler 装饰器即可。

1
2
3
4
@login_manager.unauthorized_handler
    def unauthorized():
        # do stuff
        return render_template("some template")

匿名用户是怎么处理的?有哪些属性?

Flask-Login 中,如果一个匿名用户访问站点,那么 current_user 对象会被设置成一个 AnonymousUserMixin 的对象,AnonymousUserMixin 对象有以下方法和属性:

自定义匿名用户 Model

如果你有需求自定义匿名用户的 Model,那么你可以通过设置 login_manageranonymous_user 属性来实现,而赋值的对象只需是可调用对象(class 和 function都行)即可。

1
login_manager.anonymous_user = MyAnonymousUser

Flask-Login 如何加载用户的

当一个请求过来的时候,如果 ctx.user 没有值,那么 Flask-Login 就会使用 session 中 session['user_id'] 作为参数,调用 login_manager 中使用 user_loader 装饰器设置的 callback 函数加载用户,需要注意的是,如果指定的 user_id 无效,不应该抛出异常,而是应该返回 None。

1
2
3
@login_manager.user_loader
    def load_user(user_id):
        return User.get(user_id)

session['user_id'] 其实是在调用 login_in 函数之后自动设置的。

如何控制 Flask-Login 的 session 过期时间

Flask-Login 中,如果你不特殊处理的话,session 是在你关闭浏览器之后就失效的。也就是说每次重新打开页面都是需要重新登录的。

如果你需要自己控制 session 的过期时间的话,

  1. 首先需要设置 login_manager 的 session类型为永久的,
  2. 然后再设置 session 的过期时间

    1
    2
    session.permanent = True
    app.permanent_session_lifetime = timedelta(minutes=5)
    

同时,还需要注意的是 cookie 的默认有效期其实是 一年 的,所以,我们最好也设置一下:

1
login_manager.remember_cookie_duration=timedelta(days=1)

如何在同域名下的多个系统共享登录状态

这个需求可能在公司里面会比较常见,也就是说我们一个公司域名下面会有好多个子系统,但是这些子系统都是不同部门开发的,那么,我们如何在这不同系统间共享登录状态?也就是说,只要在某一个系统登录了,在使用其他系统的时候也共享着登录的状态,不需要再次登录,除非登录失效。

这个问题因为写这篇文章已经花了3个多小时,如果要继续对这个文章进行更深的探讨的话,可能需要另外开一篇文章,这里给个思路,暂时先挖一个坑,大家有兴趣可以参照

Server-side Sessions with Redis

这个说明尝试,也差不多是类似的解决方法。