使用flask_login扩展时,我们使用login_user() 将当前登陆用户设置为登陆,并保存,关闭浏览器再打开网页时,能自动加载用户。可是当session_protection=’strong’ 关闭浏览器后再次打开网页用户并没有被加载,而将session_protection设为basic或者不设置session_protection则不会出现这个问题。通过查看flask_login和flask中session部分的源码就知道为什么了。当我们使用login_user()时,需要传入当前登录的用户对象,如果想记住用户方便用户以后访问,可以传入参数remember=True 。在调用这个函数后,flask_login 会将一些信息存到session中,这些信息包括user_id _fresh _id ,请求完成后这些值就会被保存在用户的浏览器中,但是我们不在调用login_user后,设置session.permanent=True,那么刚才存在session 中的信息(即存储在浏览器中cookie的信息)在浏览器关闭后就会失效。无论是current_user 还是 装饰器login_require ,都会在从cookie加载用户时调用_load_user,而此时浏览器中已经没有session对应的cookie,我们看一下内部是如何处理的
def _load_user(self):
'''Loads user from session or remember_me cookie as applicable'''
user_accessed.send(current_app._get_current_object())
# first check SESSION_PROTECTION
config = current_app.config
if config.get('SESSION_PROTECTION', self.session_protection):
deleted = self._session_protection()
if deleted:
return self.reload_user()
def _session_protection(self):
sess = session._get_current_object()
ident = self._session_identifier_generator()
app = current_app._get_current_object()
mode = app.config.get('SESSION_PROTECTION', self.session_protection)
# if the sess is empty, it's an anonymous user or just logged out
# so we can skip this
if sess and ident != sess.get('_id', None):
if mode == 'basic' or sess.permanent:
sess['_fresh'] = False
session_protected.send(app)
return False
elif mode == 'strong':
for k in SESSION_KEYS:
sess.pop(k, None)
sess['remember'] = 'clear'
session_protected.send(app)
return True
return False
由于我们设置了session_protection所以会执行self._session_protection(),在这个函数里如果sess存在并且 ident != sess.get(‘_id’, None) (这里可以确定sess肯定存在,并不是浏览器中没有session对应的cookie,sess就不存在。事实上,只要配置了secret_key 这里的sess就肯定不为空,这是flask 在push 上下文中就决定的) ,我们上面说过,浏览器中的session对应的cookie已经没有了,ident != sess.get(‘_id’, None)肯定为false (ident 是根路浏览器的user_agent和你的ip地址生成的字符串),继续往下执行,因为我们设置的是strong 所有,session 中的所有key都被pop出,也就是session现在为空,继续往回看,要执行return self.reload_user()
ctx = _request_ctx_stack.top
if user is None:
user_id = session.get('user_id')
if user_id is None:
ctx.user = self.anonymous_user()
else:
if self.user_callback is None:
raise Exception(
"No user_loader has been installed for this "
"LoginManager. Refer to"
"https://flask-login.readthedocs.io/"
"en/latest/#how-it-works for more info.")
user = self.user_callback(user_id)
if user is None:
ctx.user = self.anonymous_user()
else:
ctx.user = user
else:
ctx.user = user
首先看上下文中有没有用户,现在肯定没有,然后从session中取user_id ,前面已经都pop完了,肯定也没有,所有将上下文中的用户设置为匿名用户。这就解释了开头提到的现象。可见要想在strong模式下,使用remember功能,必须设置session.permanent=True。如果在调用login_user()时,传入了remember=True,你会发现不设置session.permanent=True ,它也不会丢失,这是因为flask_login 在保存remember时和我们上面提到的user_id _fresh _id 采取了不同的方法,后面几个值是直接存在flask中的session中,然后存到浏览器的cookie中,而remember虽然在开始时也是存在了session中,但是flask_login 定义了一个after_request函数,在请求结束后如果session中有remember,就pop出,然后直接使用set_cookie方法设置cookie ,并且如果你没有设置REMEMBER_COOKIE_DURATION,那么remember会默认一年有效。
我们再来看一下将session_protection分别设置为basic和strong 具体有哪些区别:在调用login_user()时,不设置session.permanent , mode=basic 在cookie中的session被篡改或者用户ip发生变化时 则会从设置在cookie中的remember_token 加载用户但会把用户标记为‘不新鲜’,但是strong模式,则会直接返回匿名用户。
def _session_protection(self):
sess = session._get_current_object()
ident = self._session_identifier_generator()
app = current_app._get_current_object()
mode = app.config.get('SESSION_PROTECTION', self.session_protection)
# if the sess is empty, it's an anonymous user or just logged out
# so we can skip this
if sess and ident != sess.get('_id', None):
if mode == 'basic' or sess.permanent:
sess['_fresh'] = False
session_protected.send(app)
return False
elif mode == 'strong':
for k in SESSION_KEYS:
sess.pop(k, None)
sess['remember'] = 'clear'
session_protected.send(app)
return True
return False