django - 如何确定用户何时在 Django 中出现空闲超时?

标签 django session-timeout

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

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

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

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

最佳答案

更新:

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

首先,深入了解 session 框架:

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

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

    建于 Alex Gaynor's suggestion ,我想我想出了另一种方法,它似乎可以满足您的要求,尽管它的边缘仍然有些粗糙。基本上,我使用第二个长期存在的“审计”cookie 来镜像 session ID,并使用一些中间件来检查该 cookie 的存在。对于任何请求:
  • 如果审计 cookie 和 session cookie 都不存在,这可能是一个新用户
  • 如果审计 cookie 存在,但 session cookie 不存在,这可能是 session 刚刚过期的用户
  • 如果两个 cookie 都存在并且具有相同的值,则这是一个事件 session

  • 这是到目前为止的代码:

    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
    

    审计.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)
    

    设置.py:
    MIDDLEWARE_CLASSES = (
        ...
        'django.contrib.sessions.middleware.SessionMiddleware',
        'sessionaudit.middleware.SessionAuditMiddleware',
        ...
    )
    
    INSTALLED_APPS = (
        ...
        'django.contrib.sessions',
        'audit',
        ...
    )
    

    如果你正在使用它,你应该实现一个自定义的注销 View ,当用户注销时它会显式地删除审计 cookie。另外,我建议使用 django 签名 cookie 中间件(但您可能已经这样做了,不是吗?)

    老的:

    我认为您应该能够使用自定义 session 后端来做到这一点。这是一些(未经测试的)示例代码:
    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 .

    关于django - 如何确定用户何时在 Django 中出现空闲超时?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/644471/

    相关文章:

    java - session 超时配置不起作用?

    python - 具有三个需要多对多字段的表的 Django 应用程序

    android - 在这种情况下,我为 android 登录到 RESTful api 调整了什么?

    python - Django 开发服务器未检测到我的 html 文件中的更改

    django - 你能从 Django 的模板中检查互联网协议(protocol)吗?

    java - 获取http响应的最大延迟是多少?

    django - 如何使用 Django 登录 Google+ API?

    java - session 过期时重定向到登录页面时出现空指针异常?

    php - CakePHP 3 session 更新

    android - 处理 Android 的 session 超时 IBM Mobile First v8.0.2017012919