如何确定 Django 中用户何时出现空闲超时?
-
22-07-2019 - |
题
我想审核用户在我的 Django 应用程序中遇到空闲超时的情况。换句话说,如果用户的会话 cookie 的过期日期超过了 settings.py 中找到的 SESSION_COOKIE_AGE,用户将被重定向到登录页面。当这种情况发生时,也应该进行审计。我所说的“审核”是指应将记录写入我的 person.audit 表中。
目前,我已经配置了一些中间件来捕获这些事件。不幸的是,当用户重定向到登录页面时,Django 会生成一个新的 cookie,因此我无法确定用户是否通过空闲超时或其他事件进入登录页面。
据我所知,我需要使用“django_session”表。但是,该表中的记录无法与该用户关联,因为发生重定向时 cookie 中的 sessionid 值会被重置。
我想我不是第一个遇到这种困境的人。有谁知道如何解决该问题?
解决方案
更新:
经过一番测试后,我意识到下面的代码并不能回答您的问题。虽然它有效,并且信号处理程序被调用, prev_session_data
如果存在,将不包含任何有用的信息。
首先,深入了解会话框架:
- 当新访问者请求应用程序 URL 时,会为他们生成一个新会话 - 此时,他们仍然是匿名的(
request.user
是 AnonymousUser 的一个实例)。 - 如果他们请求需要身份验证的视图,他们将被重定向到登录视图。
- 当请求登录视图时,它会在用户会话中设置一个测试值(
SessionStore._session
);这会自动设置accessed
和modified
当前会话的标志。 - 在上述请求的响应阶段,
SessionMiddleware
保存当前会话,有效地创建一个新的Session
实例中的django_session
表(如果您使用默认的数据库支持的会话,由django.contrib.sessions.backends.db
)。新会话的 id 保存在settings.SESSION_COOKIE_NAME
曲奇饼。 - 当用户输入用户名和密码并提交表单时,他们就通过了身份验证。如果身份验证成功,则
login
方法来自django.contrib.auth
叫做。login
检查当前会话是否包含用户 ID;如果是,并且该 ID 与登录用户的 ID 相同,SessionStore.cycle_key
调用以创建新的会话密钥,同时保留会话数据。否则,SessionStore.flush
被调用,以删除所有数据并生成新会话。这两种方法都应该删除以前的会话(对于匿名用户),并调用SessionStore.create
创建一个新会话。 - 此时,用户已通过身份验证,并且他们有一个新会话。他们的 ID 以及用于对他们进行身份验证的后端保存在会话中。会话中间件将这些数据保存到数据库中,并将它们的新会话 ID 保存在
settings.SESSION_COOKIE_NAME
.
所以你看,之前的解决方案最大的问题是时间 create
被调用(步骤 5),前一个会话的 ID 早已消失。作为 其他人指出, 发生这种情况是因为一旦会话cookie过期,它就会被浏览器默默地删除。
建立在 亚历克斯·盖纳的建议, ,我想我已经想出了另一种方法,它似乎可以满足您的要求,尽管它的边缘仍然有点粗糙。基本上,我使用第二个长期存在的“审核”cookie 来镜像会话 ID,并使用一些中间件来检查该 cookie 是否存在。对于任何请求:
- 如果审核 cookie 和会话 cookie 都不存在,则这可能是一个新用户
- 如果审核 cookie 存在,但会话 cookie 不存在,则这可能是会话刚刚过期的用户
- 如果两个 cookie 都存在并且具有相同的值,则这是一个活动会话
这是到目前为止的代码:
sessionaudit.middleware.py:
from django.conf import settings
from django.db.models import signals
from django.utils.http import cookie_date
import time
session_expired = signals.Signal(providing_args=['previous_session_key'])
AUDIT_COOKIE_NAME = 'sessionaudit'
class SessionAuditMiddleware(object):
def process_request(self, request):
# The 'print' statements are helpful if you're using the development server
session_key = request.COOKIES.get(settings.SESSION_COOKIE_NAME, None)
audit_cookie = request.COOKIES.get(AUDIT_COOKIE_NAME, None)
if audit_cookie is None and session_key is None:
print "** Got new user **"
elif audit_cookie and session_key is None:
print "** User session expired, Session ID: %s **" % audit_cookie
session_expired.send(self.__class__, previous_session_key=audit_cookie)
elif audit_cookie == session_key:
print "** User session active, Session ID: %s **" % audit_cookie
def process_response(self, request, response):
if request.session.session_key:
audit_cookie = request.COOKIES.get(AUDIT_COOKIE_NAME, None)
if audit_cookie != request.session.session_key:
# New Session ID - update audit cookie:
max_age = 60 * 60 * 24 * 365 # 1 year
expires_time = time.time() + max_age
expires = cookie_date(expires_time)
response.set_cookie(
AUDIT_COOKIE_NAME,
request.session.session_key,
max_age=max_age,
expires=expires,
domain=settings.SESSION_COOKIE_DOMAIN,
path=settings.SESSION_COOKIE_PATH,
secure=settings.SESSION_COOKIE_SECURE or None
)
return response
审计模型.py:
from django.contrib.sessions.models import Session
from sessionaudit.middleware import session_expired
def audit_session_expire(sender, **kwargs):
try:
prev_session = Session.objects.get(session_key=kwargs['previous_session_key'])
prev_session_data = prev_session.get_decoded()
user_id = prev_session_data.get('_auth_user_id')
except Session.DoesNotExist:
pass
session_expired.connect(audit_session_expire)
设置.py:
MIDDLEWARE_CLASSES = (
...
'django.contrib.sessions.middleware.SessionMiddleware',
'sessionaudit.middleware.SessionAuditMiddleware',
...
)
INSTALLED_APPS = (
...
'django.contrib.sessions',
'audit',
...
)
如果您使用此功能,则应该实现自定义注销视图,该视图会在用户注销时显式删除审核 cookie。另外,我建议使用 djangosigned-cookies 中间件(但你可能已经这样做了,不是吗?)
老的:
我认为您应该能够使用自定义会话后端来完成此操作。这是一些(未经测试的)示例代码:
from django.contrib.sessions.backends.db import SessionStore as DBStore
from django.db.models import signals
session_created = signals.Signal(providing_args=['previous_session_key', 'new_session_key'])
class SessionStore(DBStore):
"""
Override the default database session store.
The `create` method is called by the framework to:
* Create a new session, if we have a new user
* Generate a new session, if the current user's session has expired
What we want to do is override this method, so we can send a signal
whenever it is called.
"""
def create(self):
# Save the current session ID:
prev_session_id = self.session_key
# Call the superclass 'create' to create a new session:
super(SessionStore, self).create()
# We should have a new session - raise 'session_created' signal:
session_created.send(self.__class__, previous_session_key=prev_session_id, new_session_key=self.session_key)
将上面的代码保存为“customdb.py”并将其添加到您的 django 项目中。在 settings.py 中,将“SESSION_ENGINE”设置或替换为上述文件的路径,例如:
SESSION_ENGINE = 'yourproject.customdb'
然后在你的 中间件,或 models.py,为“session_created”信号提供一个处理程序,如下所示:
from django.contrib.sessions.models import Session
from yourproject.customdb import session_created
def audit_session_expire(sender, **kwargs):
# remember that 'previous_session_key' can be None if we have a new user
try:
prev_session = Session.objects.get(kwargs['previous_session_key'])
prev_session_data = prev_session.get_decoded()
user_id = prev_session_data['_auth_user_id']
# do something with the user_id
except Session.DoesNotExist:
# new user; do something else...
session_created.connect(audit_session_expire)
不要忘记包含包含以下内容的应用程序 models.py
在 INSTALLED_APPS
.
其他提示
SESSION_COOKIE_AGE = 1500#25分钟
假如把它放在你的设置,并应采取照顾和过期的会话。
我不知道Django的,但你能,简单地创建一个非持久性cookie,它存储了最后访问时间到您网站上的一个页面(您更新在每个页面加载cookie的)
然后,你的登录页面上,你可以检查,如果你的用户的Cookie,但没有会话,那么,你知道该用户的会话可能已经超时。既然你上次访问到网站上的网页,还可以计算,基于会话的持续时间,时间是否已超时。