我想审核用户在我的Django应用程序中遇到空闲超时的时间.换句话说,如果用户的会话cookie的到期日期超过了settings.py中的SESSION_COOKIE_AGE,则会将用户重定向到登录页面.发生这种情况时,也应进行审计.通过"审计",我的意思是应该将记录写入我的person.audit表.
目前,我已经配置了一些中间件来捕获这些事件.不幸的是,当用户被重定向到登录页面时,Django会生成一个新的cookie,所以我无法确定是否通过空闲超时或其他事件将用户带到了登录页面.
据我所知,我需要使用"django_session"表.但是,此表中的记录无法与该用户关联,因为在重定向发生时会重置cookie中的sessionid值.
我猜我不是第一个遇到这种困境的人.有没有人深入了解如何解决问题?
经过一些测试后,我意识到下面的代码没有回答你的问题.虽然它有效,但信号处理程序被调用,prev_session_data
如果它存在,将不包含任何有用的信息.
首先,对会话框架进行内窥探:
当新访问者请求应用程序URL时,会为他们生成一个新会话 - 此时,他们仍然是匿名的(request.user
是AnonymousUser的一个实例).
如果他们请求需要身份验证的视图,则会将其重定向到登录视图.
当请求登录视图时,它在用户的session(SessionStore._session
)中设置测试值; 这会自动设置当前会话的accessed
和modified
标志.
在上述请求的响应阶段,SessionMiddleware
保存当前会话,有效地Session
在django_session
表中创建新实例(如果您使用默认的数据库支持的会话,由提供django.contrib.sessions.backends.db
).新会话的ID保存在settings.SESSION_COOKIE_NAME
cookie中.
当用户键入其用户名和密码并提交表单时,将对其进行身份验证.如果验证成功,则调用login
方法from django.contrib.auth
.login
检查当前会话是否包含用户ID; 如果是,并且ID与登录用户的ID相同,SessionStore.cycle_key
则在调用会话数据的同时调用以创建新的会话密钥.否则,SessionStore.flush
调用,删除所有数据并生成新会话.这两种方法都应删除上一个会话(对于匿名用户),并调用SessionStore.create
以创建新会话.
此时,用户已通过身份验证,并且他们有一个新会话.他们的ID与会话一起保存在会话中,后端用于对其进行身份验证.会话中间件将此数据保存到数据库,并保存其新的会话ID settings.SESSION_COOKIE_NAME
.
所以你看,以前解决方案的一个大问题是create
被调用的时间(步骤5),前一个会话的ID已经很久了.正如其他人所指出的那样,这是因为一旦会话cookie过期,它就会被浏览器静默删除.
基于Alex Gaynor的建议,我想我已经提出了另一种方法,这似乎就是你所要求的,尽管它仍然有点粗糙.基本上,我使用第二个长期存在的"审计"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
audit.models.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)
settings.py:
MIDDLEWARE_CLASSES = ( ... 'django.contrib.sessions.middleware.SessionMiddleware', 'sessionaudit.middleware.SessionAuditMiddleware', ... ) INSTALLED_APPS = ( ... 'django.contrib.sessions', 'audit', ... )
如果您正在使用此功能,则应实现自定义注销视图,该视图会在用户注销时显式删除审核cookie.另外,我建议使用django signed-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
in 的应用程序INSTALLED_APPS
.