想必大家对 Flask-Login 这个 Flask 扩展肯定不会陌生,毕竟作为一个应用,用户登录之后,他们的认证状态是需要被记录下来的,浏览其他的页面也是需要使用这个状态的,但是这一过程是怎么发挥作用的呢?让我们从源码的层次上简单认识一下.
Flask-Login 使用的准备 使用 flask-login 的之前,我们必须要实现下述四个方法: 但是为什么要实现这个方法呢?别着急我们在下面会释疑. 除此之外我们还需要写下述的这样的代码:
1 2 3 4 5 6 @login_manager.user_loader def load_user (username ): if query_user(username) is not None : curr_user = User() curr_user.id = username return curr_user
上面的为用户的回调函数接收字符串表示的唯一用户标识符,如果能找到该用户,则返回该用户对象否则返回None.但是user_loader
究竟是什么? 源码:
1 2 3 4 5 class LoginManager (object ): ... def user_loader (self, callback ): self.user_callback = callback return callback
似乎没什么作用呢?别着急,接着往下看,我们通过具体使用去理解源码
login_user() 但凡时候 Flask-Login 应该都用过 login_user()
,所以让我们看看使用这个函数发生了什么.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 def login_user (user, remember=False , force=False , fresh=True ): if not force and not user.is_active: return False user_id = user.get_id() session['user_id' ] = user_id session['_fresh' ] = fresh session['_id' ] = current_app.login_manager._session_identifier_generator() if remember: session['remember' ] = 'set' _request_ctx_stack.top.user = user user_logged_in.send(current_app._get_current_object(), user=_get_user()) return True
主要内容是将用户信息放入session中,并为改session生成标识符.
@login_required 我们通过 @login_required 去限制用户页面访问,这一过程是怎么实现的?
1 2 3 4 5 6 7 8 9 10 11 def login_required (func ): @wraps(func ) def decorated_view (*args, **kwargs ): if request.method in EXEMPT_METHODS: // EXEMPT_METHODS = set (['OPTIONS' ]) config.py 第 49 行,通常该请求是获取服务器支持的HTTP请求方法 return func(*args, **kwargs) elif current_app.login_manager._login_disabled: return func(*args, **kwargs) elif not current_user.is_authenticated: return current_app.login_manager.unauthorized() return func(*args, **kwargs) return decorated_view
unauthorized() 在上述@login.required
中我们说明了,unauthorized()
在需要登录的情况下采取的行动,现在简单的看一下代码:
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 def unauthorized (self ): user_unauthorized.send(current_app._get_current_object()) if self.unauthorized_callback: return self.unauthorized_callback() if request.blueprint in self.blueprint_login_views: login_view = self.blueprint_login_views[request.blueprint] else : login_view = self.login_view if not login_view: abort(401 ) if self.login_message: if self.localize_callback is not None : flash(self.localize_callback(self.login_message), category=self.login_message_category) else : flash(self.login_message, category=self.login_message_category) config = current_app.config if config.get('USE_SESSION_FOR_NEXT' , USE_SESSION_FOR_NEXT): login_url = expand_login_view(login_view) session['next' ] = make_next_param(login_url, request.url) redirect_url = make_login_url(login_view) else : redirect_url = make_login_url(login_view, next_url=request.url) return redirect(redirect_url)
current_user 在登录之后,我们在需要使用当前登录对象的时候,都会使用 current_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 current_user = LocalProxy(lambda : _get_user()) // 当前用户的代理,这里重点关注 _get_user() def _get_user (): if has_request_context() and not hasattr (_request_ctx_stack.top, 'user' ): current_app.login_manager._load_user() return getattr (_request_ctx_stack.top, 'user' , None ) def _load_user (self ): user_accessed.send(current_app._get_current_object()) config = current_app.config if config.get('SESSION_PROTECTION' , self.session_protection): // session 保护,可以取值为 None (禁用),basic(在 basic 模式下或会话是永久的,如果该标识未匹配,会话会简单地被标记为非活 跃的,且任何需要活跃登入的东西会强制用户重新验证,前提你已经使用了活跃登入机制),strong(在 strong 模式下的非永久会话,如果该标识未匹配,整个会话或者记住的令牌如果存在将会被删除) deleted = self._session_protection() if deleted: return self.reload_user() is_missing_user_id = 'user_id' not in session if is_missing_user_id: cookie_name = config.get('REMEMBER_COOKIE_NAME' , COOKIE_NAME) header_name = config.get('AUTH_HEADER_NAME' , AUTH_HEADER_NAME) has_cookie = (cookie_name in request.cookies and session.get('remember' ) != 'clear' ) // 登出的时候 has_cookie = False if has_cookie: return self._load_from_cookie(request.cookies[cookie_name]) elif self.request_callback: //定义在 request_loader() 中, 正常情况下我们并不使用,仅当我们不想使用 cookie 的情况下登录用户才会考虑 request_loader 回调. return self._load_from_request(request) elif header_name in request.headers: return self._load_from_header(request.headers[header_name]) return self.reload_user()
logout_user() 登录要验证账户密码什么的,显的比较复杂,但是登出就很简答了,你不需要传递任何参数,只需调用这个函数,那让我们看看这个函数发生了什么:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 def logout_user (): user = _get_user() if 'user_id' in session: session.pop('user_id' ) if '_fresh' in session: session.pop('_fresh' ) cookie_name = current_app.config.get('REMEMBER_COOKIE_NAME' , COOKIE_NAME) if cookie_name in request.cookies: session['remember' ] = 'clear' user_logged_out.send(current_app._get_current_object(), user=user) current_app.login_manager.reload_user() return True
该函数将session中的user相关信息全部清空,然后重新加载用户,正常逻辑下由于重新加载用户后如果当前页面需要登录才能访问的话,则会跳转到登录视图.
fresh_login_required 当用户登入,他们的会话被标记成“新鲜的”,就是说在这个会话只中用户实际上登录过。当会话销毁用户使用“记住我”的 cookie 重新登入,会话被标记成“非新鲜的”。fresh_login_required 除了验证用户登录,也将确保他们的登录是“新鲜的”。如果不是“新鲜的”,它会把用户送到可以重输入验证条件的页面,主要用户修改个人信息的敏感操作.
1 2 3 4 5 6 7 8 9 10 11 12 13 def fresh_login_required (func ): @wraps(func ) def decorated_view (*args, **kwargs ): if request.method in EXEMPT_METHODS: return func(*args, **kwargs) elif current_app.login_manager._login_disabled: return func(*args, **kwargs) elif not current_user.is_authenticated: return current_app.login_manager.unauthorized() elif not login_fresh(): // 获取session 中的 refresh 值 return current_app.login_manager.needs_refresh() // 1. 出现 LoginMange.needs_refress_message 2. 跳转到 LoginManger.refresh_view,如果没有设置则会提示 401 错误 return func(*args, **kwargs) return decorated_view
confirm_login 将会话重新标记为”新鲜”
1 2 3 4 def confirm_login (): session['_fresh' ] = True session['_id' ] = current_app.login_manager._session_identifier_generator() // 重新生成会话id user_login_confirmed.send(current_app._get_current_object())
reload_user() 接着 logout_user()
中调用 reload_user()
.很显然这个函数的功能就是重新加载用户,具体实现让我们看下源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 def reload_user (self, user=None ): ctx = _request_ctx_stack.top // _request_ctx_stack 一个保存对象的栈,可以返回看 login_user() 源码的倒数第三行,登录的时候将 user 保存在 _request_ctx_stack.top.user 中 if user is None : user_id = session.get('user_id' ) // logout_user() 中 session 已经将 user_id 移除 if user_id is None : ctx.user = self.anonymous_user() //见下文 else : if self.user_callback is None : //这里解释了为什么我们需要定义 @login_manager.user_loader raise Exception( "No user_loader has been installed for this " "LoginManager. Add one with the " "'LoginManager.user_loader' decorator." ) user = self.user_callback(user_id) // 这里我们最初定义的user_loader发挥作用,通过调用self.user_callback()获取用户 if user is None : ctx.user = self.anonymous_user() else : ctx.user = user else : ctx.user = user
anoymous_user 源码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 self.anonymous_user = AnonymousUserMixin // 匿名类 class AnonymousUserMixin (object ): @property def is_authenticated (self ): return False @property def is_active (self ): return False @property def is_anonymous (self ): return True def get_id (self ): return