【问题标题】:How do I determine when a user has an idle timeout in Django?如何确定用户何时在 Django 中有空闲超时?
【发布时间】:2009-03-13 20:17:12
【问题描述】:

我想在用户在我的 Django 应用程序中遇到空闲超时时进行审核。换句话说,如果用户的会话 cookie 的过期日期超过了 settings.py 中的 SESSION_COOKIE_AGE,用户将被重定向到登录页面。当这种情况发生时,也应该进行审计。通过“审计”,我的意思是应该将记录写入我的 person.audit 表。

目前,我已经配置了一些中间件来捕获这些事件。不幸的是,当用户被重定向到登录页面时,Django 会生成一个新的 cookie,因此我无法确定用户是否通过空闲超时或其他事件被带到登录页面。

据我所知,我需要使用“django_session”表。但是,此表中的记录无法与该用户关联,因为重定向发生时 cookie 中的 sessionid 值被重置。

我猜我不是第一个遇到这种困境的人。有没有人知道如何解决这个问题?

【问题讨论】:

  • 您想要审核它是什么意思?你的意思是登录? tbh 我不太明白你想要做什么,你能详细说明一下吗?
  • 用户可以通过以下两种方式之一退出系统:他们可以单击注销链接,或者他们的会话可以过期(60 分钟后)。我需要记录(a.k.a.,“审计”)这两种情况。

标签: django session-timeout


【解决方案1】:

更新:

经过一番测试,我意识到下面的代码不能回答您的问题。虽然它有效,并且信号处理程序被调用,但prev_session_data 如果存在,则不会包含任何有用的信息。

首先,深入了解会话框架:

  1. 当新访问者请求应用程序 URL 时,会为他们生成一个新会话 - 此时,他们仍然是匿名的(request.user 是 AnonymousUser 的一个实例)。
  2. 如果他们请求需要身份验证的视图,则会被重定向到登录视图。
  3. 当请求登录视图时,它会在用户会话中设置一个测试值(SessionStore._session);这会自动在当前会话中设置 accessedmodified 标志。
  4. 在上述请求的响应阶段,SessionMiddleware 保存当前会话,有效地在django_session 表中创建一个新的Session 实例(如果您使用的是默认数据库支持的会话,由django.contrib.sessions.backends.db)。新会话的 id 保存在 settings.SESSION_COOKIE_NAME cookie 中。
  5. 当用户输入他们的用户名和密码并提交表单时,他们就通过了身份验证。如果认证成功,则调用来自django.contrib.authlogin 方法。 login 检查当前会话是否包含用户 ID;如果是,并且 ID 与登录用户的 ID 相同,则调用 SessionStore.cycle_key 创建新的会话密钥,同时保留会话数据。否则,调用SessionStore.flush 以删除所有数据并生成新会话。这两种方法都应该删除之前的会话(对于匿名用户),并调用SessionStore.create 来创建一个新会话。
  6. 此时,用户已通过身份验证,并且他们有一个新会话。他们的 ID 与用于验证他们的后端一起保存在会话中。会话中间件将此数据保存到数据库中,并将其新会话 ID 保存在 settings.SESSION_COOKIE_NAME 中。

所以你看,上一个解决方案的大问题是当create 被调用时(第5 步),上一个会话的ID 早已不复存在。就像others have pointed out,这是因为一旦会话cookie过期,它就会被浏览器静默删除。

Alex Gaynor's suggestion 的基础上,我想我想出了另一种方法,这似乎可以满足您的要求,尽管边缘仍然有些粗糙。基本上,我使用第二个长期存在的“审计”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'

然后在您的 middleware 或 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 中。

【讨论】:

  • 我还是一头雾水。我在哪里注册 audit_session_expire() 的接收者?
  • audit_session_expire 是自定义 session_created 信号的处理程序。我已经更新了答案以显示它是如何注册的。
  • 我已按照您的指示进行操作,但从未看到调用“audit_session_expire()”方法的信号。我已将它包含在我的 models.py 文件中(在“审计”应用程序中)并更新了 INSTALLED_APPS。即使它正在工作,如果用户单击“注销”,这段代码不会触发吗?
  • 您使用的是哪个版本的 django?我在 1.1-alpha 中对此进行了测试,但这在 1.0.2 中也应该可以工作,因为 SessionStore API 没有改变。您能否更新您的问题并发布您的代码以便我查看?
  • 我更正了代码不工作的问题——这是一个 PYTHONPATH 问题。不幸的是,如果用户显式注销或基于过期会话注销,则会触发此代码。我想独立于用户自己注销来跟踪后者。
【解决方案2】:

SESSION_COOKIE_AGE = 1500 # 25 分钟

把它放在你的设置中,它应该会处理好这个问题并让会话过期。

【讨论】:

    【解决方案3】:

    我不了解 Django,但是您可以简单地创建一个非持久性 cookie,它存储对您网站上页面的最后访问时间(您在每次页面加载时更新 cookie)

    然后,在您的登录页面上,您可以检查您的用户是否有您的 cookie,但没有会话,那么,您知道用户的会话可能已经超时。由于您有最后一次访问您网站上某个页面的时间,因此您还可以根据会话的持续时间计算它是否已超时。

    【讨论】:

    • 我是一个asp.net程序员,也遇到过这种问题。 (嗯,它是网络开发,所以很相似)这就是我尝试在 asp.net 中解决这个问题的方法,所以我建议这个答案。
    猜你喜欢
    • 2017-07-29
    • 1970-01-01
    • 2010-10-25
    • 2013-06-18
    • 1970-01-01
    • 2010-11-03
    • 1970-01-01
    • 2011-01-31
    • 1970-01-01
    相关资源
    最近更新 更多