Flask-Login

Flask-Login 为 Flask 提供对用户 session 的管理。它能够处理登录,注销和长时间记住用户 session 等常用任务。

它会:

  • 在 session 中存储活动用户的 ID,并让你轻松实现用户的登录和注销。

  • 让你可以限制视图只对已登录用户可用。

  • 处理通常会很麻烦的“记住我”功能。

  • 帮助保护用户 session 不被 cookie 窃贼偷窃。

  • 可与 Flask-Principal 或者其他权限认证扩展集成使用。

但是,它不会:

  • 强迫你使用特定的数据库或者其他存储方式。你对如何加载用户拥有完全的自主权。

  • 限定你使用用户名和密码,OpenIDs 或者任何其他的验证方法。

  • 处理“已登录或未登录”之外的权限。

  • 处理用户注册或者账号恢复。

安装

使用 pip 安装扩展:

$ pip install flask-login

配置应用程序

对于一个应用来说,使用 Flask-Login 最重要的部分是 LoginManager 类。你应该在代码中的某个位置为应用程序创建一个类实例,像这样:

login_manager = LoginManager()

login manager 包含让你的应用和 Flask-Login 一起工作的代码,比如如何通过 ID 加载用户,在用户需要登录时将用户跳转到何处等等。

实际的应用对象被创建之后,你能够通过以下代码配置应用的登录功能(译注:即注册扩展到应用实例):

login_manager.init_app(app)

默认情况下,Flask-Login 使用 session 进行身份验证。这意味着你必须在应用程序中设置密钥,否则 Flask 会向你显示一条错误消息要求你这样做。 请参阅 Flask documentation on sessions 以了解如何设置密钥。

注意: 确保 使用"How to generate good secret keys"部分中的给定命令来生成你自己的密钥。不要 使用示例中的密钥。

如何开始工作

你需要提供一个 user_loader 回调函数。这个回调函数用于通过 session 中存储的用户 ID 重新加载用户对象。它应该接收用户的 unicode ID,并返回相应的用户对象。例如:

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

如果 ID 无效,函数应该返回 None (而不是唤起异常) 。 (这样 ID 将从 session 中被手动移除且程序可以继续执行。)

定义用户类

你用来表示用户的类需要实现以下属性和方法:

is_authenticated

如果用户已通过认证这个属性应该返回 True ,即用户已经提供有效的身份凭证。(只有通过认证的用户才会满足 login_required 条件。)

is_active

如果这是一个活跃的用户这个属性应该返回 True - 也就是说用户除了通过验证以外,还激活了账号,且账号没有被暂停或者处于任何应用程序用来封禁账号的状态。不活跃用户将不能登录(除非被强制登录)。(译注:关于强制登录 API 中会有介绍)

is_anonymous

如果这是一个匿名用户这个属性应该返回 True。 (实际存在的用户应该返回 False。)

get_id()

这个方法必须返回一个唯一标识用户的 unicode,并且返回的 unicode 能够在 user_loader 回调函数中用来加载用户。注意返回的值 必须unicode - 如果 ID 本来是 int 类型或者其他类型,你需要将它转换为 unicode

为了让实现用户类更轻松,你可以从 UserMixin 类继承用户类,它提供了以上所有属性和方法的默认实现。(但这不是必需的,你可以自己实现)

登录示例

用户通过认证后,使用 login_user 函数将他们登录。

例如:

@app.route('/login', methods=['GET', 'POST'])
def login():
    # 这里我们使用一个类,来表示和验证客户端表单数据
    # 例如,WTForms 库可以用来为我们处理这些工作,
    # 我们使用自定义的 LoginForm 来验证表单数据。
    form = LoginForm()
    if form.validate_on_submit():
        # 登录并且验证用户
        # user 应该是你 `User` 类的一个实例
        login_user(user)

        flask.flash('Logged in successfully.')

        next = flask.request.args.get('next')
        # is_safe_url 用来检查url是否可以安全的重定向。
        # 有关示例,参见 http://flask.pocoo.org/snippets/62/ 。
        if not is_safe_url(next):
            return flask.abort(400)

        return flask.redirect(next or flask.url_for('index'))
    return flask.render_template('login.html', form=form)

注意: 你必须验证 next 参数的值。如果你没有,你的应用程序将容易受到开放重定向攻击。有关 is_safe_url 的实现示例,请参见 this Flask Snippet

就这么简单。之后你可以使用 current_user 代理对象访问已登录的用户,current_user 可以在每个模板中直接使用(译注:传入了模板上下文):

{% if current_user.is_authenticated %}
  Hi {{ current_user.name }}!
{% endif %}

需要用户登录才能访问的视图可以使用 login_required 装饰器进行装饰:

@app.route("/settings")
@login_required
def settings():
    pass

当用户需要注销时:

@app.route("/logout")
@login_required
def logout():
    logout_user()
    return redirect(somewhere)

用户将会被注销,并且任何保存他们 session 的 cookie 都会被清理。(译注:从 session 中删除用户 id 等字段)

自定义登录流程

默认情况下,当一个未登录的用户试图访问一个 login_required 的视图时,Flask-Login 将会闪现一条信息并将用户重定向到登录视图。(如果没有设置登录视图,将会报401错误)

登录视图的名称(译注:URL 或端点)应该被设置为 LoginManager.login_view。例如:

login_manager.login_view = "users.login"

默认的闪现信息内容是 Please log in to access this page. 。可以通过设置 LoginManager.login_message 属性来自定义内容:

login_manager.login_message = u"Bonvolu ensaluti por uzi tiun paĝon."

可以通过设置 LoginManager.login_message_category 属性来自定义信息类型:

login_manager.login_message_category = "info"

重定向到登录视图后,(当前 URL)查询字符串中会有一个 next 变量,变量中保存着用户试图访问的页面地址。如果 USE_SESSION_FOR_NEXT 配置参数为 True,试图访问的页面地址将会保存在 session 的 next 键中。

如果你想更进一步的自定义流程,用 LoginManager.unauthorized_handler 来装饰处理函数:

@login_manager.unauthorized_handler
def unauthorized():
    # do stuff
    return a_response

使用 Autherization 首部字段登录

警告

这个方法将被弃用;请使用下面的 request_loader 作为替代。

有些场景你想使用 Authorization 首部字段来支持 Basic Auth 登录,比如用于 api 请求。这时你需要提供一个 header_loader 回调函数来支持通过请求的首部字段登录。这个回调函数应该和你的 user_loader 回调函数基本相同,但是它接收一个首部字段值而不是用户 id。例如:

@login_manager.header_loader
def load_user_from_header(header_val):
    header_val = header_val.replace('Basic ', '', 1)
    try:
        header_val = base64.b64decode(header_val)
    except TypeError:
        pass
    return User.query.filter_by(api_key=header_val).first()

默认情况下 Authorization 首部字段的值会被传递给 header_loader 回调函数。你可以通过 AUTH_HEADER_NAME 配置变量来更改使用的首部字段。

使用 Request Loader 自定义登录

有些场景你想在不使用 cookies 的情况下登录用户,比如使用请求首部或者作为查询参数传递的 api key。在这些情况下,你应该使用 request_loader 回调函数。这个回调函数和 user_loader 回调函数基本相同,但是它接收 Flask 请求而不是用户 id。

例如,为了支持通过 URL 参数和使用 Authorization 首部字段的 Basic Auth 进行登录:

@login_manager.request_loader
def load_user_from_request(request):

    # 首先,尝试通过 api_key URL 参数进行登录
    api_key = request.args.get('api_key')
    if api_key:
        user = User.query.filter_by(api_key=api_key).first()
        if user:
            return user

    # 然后, 尝试通过 Basic Auth 进行登录
    api_key = request.headers.get('Authorization')
    if api_key:
        api_key = api_key.replace('Basic ', '', 1)
        try:
            api_key = base64.b64decode(api_key)
        except TypeError:
            pass
        user = User.query.filter_by(api_key=api_key).first()
        if user:
            return user

    # 最后,如果两种方法都不能登录用户则返回 None
    return None

匿名用户

默认情况下,当一个用户没有登录时, current_user 被设置为一个 AnonymousUserMixin 对象。它有下列属性和方法:

  • is_activeis_authenticated 返回 False

  • is_anonymous 返回 True

  • get_id() 返回 None

如果你有自定义匿名用户的需求(例如,他们需要有一个权限字段),你可以使用以下方式提供一个创建匿名用户的可调用对象(类或者工厂函数)给 LoginManager:

login_manager.anonymous_user = MyAnonymousUser

记住我

默认情况下,当用户关闭浏览器时,Flask Session 会被删除,用户将被注销。“记住我” 防止用户关闭他们的浏览器时被意外注销。这 不是 用户注销后在登录表单中会记住或自动填写用户的用户名或密码的意思(译注:即不是浏览器提供的自动填充功能)。

“记住我”功能的实现可能会很麻烦。但是 Flask-Login 使该过程变得简单明了--你只需要在调用 login_user 时传入 remember=True 即可。一个 cookie 将会保存到用户的电脑,然后 Flask-Login 将会在用户 ID 不在 session 中时自动地从该 cookie 中恢复用户 ID。cookie 的过期时长可以通过 REMEMBER_COOKIE_DURATION 配置或者直接将时长传入 login_user 来设置。这个 cookie 是防篡改的,所以如果用户篡改了它(如使用别的用户ID来代替自己的),Flask-Login 将不会使用这个 cookie。

这一层级的功能是自动执行的。但是,你能够(如果你的应用将处理任何的敏感数据,则是应该)提供额外的设置来增加记住我 cookie 的安全性。

可选令牌值

使用用户 ID 作为”记住我“的令牌值意味着你必须更改用户 ID 来使他们的登录 session 无效。一种改进的方式是使用一个另外的用户 id 而不是用户的主 ID。例如:

@login_manager.user_loader
def load_user(user_id):
    return User.query.filter_by(alternative_id=user_id).first()

然后用户类的 get_id 方法也应该返回另外的 id 而不是用户的主 ID:

def get_id(self):
    return unicode(self.alternative_id)

这样,当用户更改他们的密码时,你可以将用户另外的 id 更改为一个新的随机生成值,这样会确保他们原来的验证 session 不再有效。注意这个另外的 id 依然需要唯一标识用户... 可以把它当成第二个用户 ID。

“新鲜”登录

当一个用户登录时,它的登录 session 会被标记为“新鲜”(译注:在 session 中添加 _fresh 字段),表明他们实际是在该 session 中通过了身份认证。当他们的 session 被销毁然后通过“记住我” cookie 登录回来时,session 会被标记为“不新鲜”。login_required 不会区分新鲜状态,对大多数页面来说这样没有问题。然而,类似于更改个人信息这样的敏感操作应该需要“新鲜”登录。(而像修改密码这样的操作不管怎样应该总是需要重新输入原密码。)

fresh_login_required 除了验证用户已经登录,还将确保他们的登录为“新鲜”状态。如果不是,它会将他们重定向到一个可以重新输入凭证的页面。你可以就像自定义 login_required 的方式一样,通过设置 LoginManager.refresh_viewneeds_refresh_message,以及 needs_refresh_message_category 自定义这类行为:

login_manager.refresh_view = "accounts.reauthenticate"
login_manager.needs_refresh_message = (
    u"To protect your account, please reauthenticate to access this page."
)
login_manager.needs_refresh_message_category = "info"

或者提供你自己的回调函数来刷新“新鲜”状态:

@login_manager.needs_refresh_handler
def refresh():
    # do stuff
    return a_response

调用 confirm_login 函数,会把 session 重新标记为“新鲜”。

Session 的保护

虽然上述功能有助于保护你的“记住我”令牌不被 cookie 窃贼偷窃,但是 session cookie依然容易受到攻击。Flask-Login 包含了 session 保护功能来防止用户的 session 被偷窃。

你可以在 LoginManager 和应用程序配置参数中设置 session 保护。如果启用了 session 保护,它将运行在 basic 或者 strong 模式。设置方式是在 LoginManager 设置 session_protection 属性的值为 "basic" 或者 "strong":

login_manager.session_protection = "strong"

停用 session 保护:

login_manager.session_protection = None

默认情况下, session 保护被启动为 "basic" 模式。在应用程序配置中将 SESSION_PROTECTION 配置参数设置为 None"basic""strong" 可以禁用它或更改工作模式。

当 session 保护被启用时,每个请求都会为用户的计算机生成一个标识符(主要是对IP地址和用户代理的加密 hash)。 如果 session 中没有相关联的标识符,则会将生成的标识符存储在 session 中。 如果它有一个标识符,并且与当前请求生成的标识符相匹配,则该请求正常进行。(译注:同一IP地址和用户代理生成的哈希值总是相同的)

如果标识符在 basic 模式下不匹配,或者当 session 是永久的,session 会被直接标记成”不新鲜“, 任何需要“”新鲜“登录的请求都会强制要求用户重新认证。(当然,你必须已经在适当的地方启用了”新鲜“登录这才会有作用)

如果非永久 session 中的标识符在 strong 模式下不匹配,整个 session (以及可能存在的记住我令牌)会被删除。

本地化

默认情况下,当用户需要登录时 LoginManager 使用 flash 来显示消息。这些消息是英文的。如果你需要进行本地化,请将 localize_callbackLoginManager 属性设置为一个在发送到 flash 之前对这些消息进行调用的函数,如 gettext。这个函数将会对这些消息调用,调用的返回值将代替消息发送到 flash

API 文档

下列文档是从 Flask-Login 源码中自动生成的。

登录配置

class flask_login.LoginManager(app=None, add_context_processor=True)[源代码]

这个对象用来保存登录需要的设置。LoginManager 的实例 限定于特定的程序实例, 所以你可以在代码的主体部分创建它,然后在工厂函数中绑定到程序实例。

setup_app(app, add_context_processor=True)[源代码]

这个方法已经被弃用。请使用 LoginManager.init_app() 作为代替。

unauthorized()[源代码]

这个方法会在用户被要求登录的时候调用。如果你使用 LoginManager.unauthorized_handler() 注册了回调函数, 被调用的会是这个回调函数(译注:首先调用 unauthorized() , 然后跳过后续代码直接返回并调用该回调函数)。否则,它将执行下列行为:

  • 向用户闪现消息 LoginManager.login_message

  • 如果应用使用了蓝图将通过 blueprint_login_views 找到当前蓝图的登录视图。 如果应用没有使用蓝图或者没有指定当前蓝图的登录视图, 将使用 login_view 的值。

  • 重定向用户到登录视图。(用户试图访问的页面地址将会被传递到查询字符串的 next 变量中, 所以如果验证通过你会重定向到该页面而不是返回首页。 作为另一选择,如果设置了 USE_SESSION_FOR_NEXT 配置, 该页面地址将会被添加到 session 的 next 键中。)

如果 LoginManager.login_view 未定义,该方法将直接唤起 HTTP 401(Unauthorized)错误。

该方法应该返回自一个视图或者 before/after_request 函数, 否则重定向不会生效。(译注:这样才会有有效的 next 值。)

needs_refresh()[源代码]

当用户已经登录但因为登录 session ”不新鲜“而需要重新认证时,该方法将被调用。 如果你使用 needs_refresh_handler 注册了回调函数, 该回调函数将被调用(译注:过程同上)。否则它将执行下列行为:

如果 LoginManager.refresh_view 未定义,该方法将直接唤起 HTTP 401(Unauthorized)错误。

该方法应该返回自一个视图或者 before/after_request 函数,否则重定向不会生效。

基本配置

user_loader(callback)[源代码]

用来设置从 session 中重载用户的回调函数。 被设置的函数应该接收一个用户 ID(unicode)并返回一个用户对象, 如果用户不存在的话返回 None

参数

callback (callable) -- 用来取回用户对象的回调函数。

header_loader(callback)[源代码]

该函数已被废弃,请使用 LoginManager.request_loader() 作为代替。

用来设置通过请求头的值加载用户的回调函数。 被设置的函数应该接收一个认证令牌并返回一个用户对象, 如果用户不存在的话返回 None

参数

callback (callable) -- 用来取回用户对象的回调函数。

anonymous_user

一个创建匿名用户的类或者工厂函数,在未登录时使用。

unauthorized 配置

login_view

当用户需要登录时要重定向到的视图的名称。(可以是一个绝对 URL,如果你的认证设施在应用程序的外部。)

login_message

当用户被重定向到登录页面时闪现的信息。

unauthorized_handler(callback)[源代码]

unauthorized 方法设置一个回调函数, 这个回调函数另外还会被 login_required 所使用。 它不接收参数,并且应该返回一个会被发送给用户的响应而不是普通的视图。

参数

callback (callable) -- 用于未认证用户的回调函数。

needs_refresh 配置

refresh_view

当用户需要重新认证时要重定向到的视图的名称。

needs_refresh_message

当用户被重定向到重新认证页面时闪现的信息。

needs_refresh_handler(callback)[源代码]

needs_refresh 方法设置一个回调函数, 这个回调函数另外还会被 fresh_login_required 所使用。 它不接收参数,并且应该返回一个会被发送给用户的响应而不是普通的视图。

参数

callback (callable) -- 用于未认证用户的回调函数。

登录机制

flask_login.current_user

当前用户的代理对象。

flask_login.login_fresh()[源代码]

如果当前登录是“新鲜”的,返回 True

flask_login.login_user(user, remember=False, duration=None, force=False, fresh=True)[源代码]

登录用户。你应该在这个方法中传入实际的用户对象。 如果用户的 is_active 属性为 False,他们将不会被登录,除非 force 参数为 True

如果登录成功将返回 True ,如果登录失败则返回 False (即用户的账号为不活跃状态)。

参数
  • user (object) -- 要登录的用户对象。

  • remember (bool) -- session 过期后是否记住用户。默认值为 False

  • duration (datetime.timedelta) -- 记住我 cookie 的过期时长。如果值为 None 将使用配置中设置的值. 默认值为 None

  • force (bool) -- 如果用户处于不活跃状态,设置这个参数为 True 将强制登录用户。默认值为 False

  • fresh (bool) -- 将该参数设置为 False,将会在登录用户时标记 session 为”不新鲜“。默认值为 True

flask_login.logout_user()[源代码]

注销用户。(不需要传入实际的用户对象。)若存在记住我 cookie,该 cookie 将会被清除。

flask_login.confirm_login()[源代码]

将当前 session 设置为”新鲜“状态。当从 cookie 重新恢复时, session 会变得不新鲜。

视图保护

flask_login.login_required(func)[源代码]

如果你用这个函数装饰一个视图,它将确保当前用户在调用实际的视图之前已经通过认证并登录。 (如果他们没有,它将会调用 LoginManager.unauthorized 回调函数)。例如:

@app.route('/post')
@login_required
def post():
    pass

如果只在特定场景你需要要求用户已经登录,你可以这样做:

if not current_user.is_authenticated:
    return current_app.login_manager.unauthorized()

...这基本上也是这个函数装饰视图时会额外执行的代码。

我们可以很方便在进行单元测试时全局地关闭认证功能。 如果应用程序的配置变量 LOGIN_DISABLED 被设置为 True,这个装饰器将会被忽略。

注解

根据 W3 guidelines for CORS preflight requests, OPTIONS 类型的 HTTP 请求不会进行登录检查。

参数

func (function) -- 要装饰的视图函数。

flask_login.fresh_login_required(func)[源代码]

如果你用这个函数装饰视图,它将确保当前用户的登录是”新鲜“的 - 即他们 的 session 不是从“记住我” cookie 中恢复的。 像改变密码或者邮箱这样的敏感操作应该用这个来保护,以提防 cookie 窃贼的攻击。

如果用户没有通过认证,LoginManager.unauthorized() 像平常一样会被调用。 如果他们已经通过认证,但 session 是”不新鲜“的, 它将调用 LoginManager.needs_refresh()。(在这种情况下,你将需要提供 一个 LoginManager.refresh_view。)

关于配置变量,该装饰器和 login_required() 装饰器有同样的行为。

注解

根据 W3 guidelines for CORS preflight requestsOPTIONS 类型的 HTTP 请求不会进行登录检查。

参数

func (function) -- 要装饰的视图函数。

用户对象辅助

class flask_login.UserMixin[源代码]

提供 Flask-Login 期望用户对象所拥有方法的默认实现。

class flask_login.AnonymousUserMixin[源代码]

用来代表匿名用户的默认对象。

实用工具

flask_login.login_url(login_view, next_url=None, next_field='next')[源代码]

创建用于重定向到登录页面的 URL。如果只提供了 login_view 参数, 函数将仅返回该视图的 URL。如果提供了 next_url 参数,则会添加一个 next=URL 的参数到查询字符串,以便登录视图可以重定向到提供的这个 URL。 Flask-Login 默认的未认证处理器(unauthorized handler) 在重定向到登录 url 时使用这个函数。 将 FORCE_HOST_FOR_REDIRECTS 配置设置为一个主机地址, 即可强制在 URL 中使用主机名。 这可以防止在请求中有 Host 或 X-Forwarded-For 首部字段时重定向到外部站点。

参数
  • login_view (str) -- 登录视图的名称(也可以是登录视图的实际 URL)

  • next_url (str) -- 提供给登录视图来重定向的URL。

  • next_field (str) -- 存储下一个 URL 的字段名称。(默认为 next

信号

查看 Flask documentation on signals 了解如何在你的代码中使用信号。

flask_login.user_logged_in

在用户登录后发送。除了程序实例(信号的发送者)以外,还会传入被登录的 用户对象

flask_login.user_logged_out

在用户注销后发送。除了程序实例(信号的发送者)以外,还会传入被注销的 用户对象

flask_login.user_login_confirmed

在用户的登录被确认后发送,会将登录标记为新鲜。(普通的登录不会调用这个函数)不接收除了程序实例以外的参数。

flask_login.user_unauthorized

LoginManagerunauthorized 方法被调用时发送。不接收除了程序实例以外的参数。

flask_login.user_needs_refresh

LoginManagerneeds_refresh 方法被调用时发送。不接收除了程序实例以外的参数。

flask_login.session_protected

当 session 保护发挥作用时调用,这时一个 session 会被标记为”不新鲜“或被删除。不接收除了程序实例以外的参数。