在Django中如何确定用户何时有空闲超时? [英] How do I determine when a user has an idle timeout in Django?

查看:169
本文介绍了在Django中如何确定用户何时有空闲超时?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I would like to audit when a user has experienced an idle timeout in my Django application. In other words, if the user's session cookie's expiration date exceeds the SESSION_COOKIE_AGE found in settings.py, the user is redirected to the login page. When that occurs, an audit should also occur. By "audit", I mean a record should be written to my person.audit table.

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

Currently, I have configured some middleware to capture these events. Unfortunately, Django generates a new cookie when the user is redirected to the login page, so I cannot determine if the user was taken to the login page via an idle timeout or some other event.

从我可以看出,我需要使用django_session表。但是,该表中的记录不能与该用户关联,因为重定向发生时,cookie中的sessionid值会重置。

From what I can tell, I would need to work with the "django_session" table. However, the records in this table cannot be associated with that user because the sessionid value in the cookie is reset when the redirect occurs.

我猜我不是第一个遇到这个困境。有没有人有洞察如何解决问题?

I'm guessing I'm not the first to encounter this dilemma. Does anyone have insight into how to resolve the problem?

推荐答案

更新:



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

Update:

After a bit of testing, I realize that the code below doesn't answer your question. Although it works, and the signal handler gets called, prev_session_data if it exists, won't contain any useful information.

首先,在会议框架内窥视:

First, an inside peek at the sessions framework:


  1. 当新的访问者请求应用程序URL时,会为它们生成一个新的会话 - 在这一点上,他们仍然是匿名的( request.user 是AnonymousUser的一个实例)。

  2. 如果他们要求查看这需要身份验证,它们被重定向到登录视图。

  3. 当请求登录视图时,它将在用户会话中设置测试值( SessionStore._session );这将自动设置当前会话中访问的修改标志。

  4. 在上述请求的响应阶段, SessionMiddleware 保存当前会话,有效地创建一个新的 Session django_session 表(如果您使用默认的数据库支持会话,由 django.contrib.sessions.backends.db )。新会话的ID保存在 settings.SESSION_COOKIE_NAME cookie中。

  5. 当用户输入用户名和密码并提交形式,他们被认证。如果身份验证成功,则调用 django.contrib.auth 中的登录方法。 登录检查当前会话是否包含用户ID;如果是,并且ID与登录用户的ID相同,则调用 SessionStore.cycle_key 来创建一个新的会话密钥,同时保留会话数据。否则,调用 SessionStore.flush ,以删除所有数据并生成新会话。这两种方法都应该删除上一个会话(对于匿名用户),并调用 SessionStore.create 来创建一个新的会话。

  6. 此时,用户被认证,并且他们有一个新的会话。他们的ID保存在会话中,以及用于验证它们的后端。会话中间件将此数据保存到数据库,并将其新会话ID保存在 settings.SESSION_COOKIE_NAME 中。

  1. When a new visitor requests an application URL, a new session is generated for them - at this point, they're still anonymous (request.user is an instance of AnonymousUser).
  2. If they request a view that requires authentication, they're redirected to the login view.
  3. When the login view is requested, it sets a test value in the user's session (SessionStore._session); this automatically sets the accessed and modified flags on the current session.
  4. During the response phase of the above request, the SessionMiddleware saves the current session, effectively creating a new Session instance in the django_session table (if you're using the default database-backed sessions, provided by django.contrib.sessions.backends.db). The id of the new session is saved in the settings.SESSION_COOKIE_NAME cookie.
  5. When the user types in their username and password and submits the form, they are authenticated. If authentication succeeds, the login method from django.contrib.auth is called. login checks if the current session contains a user ID; if it does, and the ID is the same as the ID of the logged in user, SessionStore.cycle_key is called to create a new session key, while retaining the session data. Otherwise, SessionStore.flush is called, to remove all data and generate a new session. Both these methods should delete the previous session (for the anonymous user), and call SessionStore.create to create a new session.
  6. At this point, the user is authenticated, and they have a new session. Their ID is saved in the session, along with the backend used to authenticate them. The session middleware saves this data to the database, and saves their new session ID in settings.SESSION_COOKIE_NAME.

所以你看,以前的解决方案的大问题是在创建被调用的时候(步骤5.),上一个会话的ID是长的不见了。作为其他人已经指出,这是因为一旦会话cookie过期,它被浏览器静默删除。

So you see, the big problem with the previous solution is by the time create gets called (step 5.), the previous session's ID is long gone. As others have pointed out, this happens because once the session cookie expires, it is silently deleted by the browser.

建立在 Alex Gaynor的建议,我想我已经提出了另一种方法,这似乎在做你想要的,虽然它还是一点点在边缘周围粗糙。基本上,我使用第二个长寿命的审核cookie来镜像会话ID,还有一些中间件来检查该cookie的存在。对于任何请求:

Building on Alex Gaynor's suggestion, I think I've come up with another approach, that seems to do what you're asking, though it's still a little rough around the edges. Basically, I use a second long-lived "audit" cookie, to mirror the session ID, and some middleware to check for the presence of that cookie. For any request:


  • 如果审核cookie和会话cookie都不存在,这可能是一个新用户

  • 如果审核cookie存在,但是会话cookie不存在,这可能是一个用户,其会话刚刚过期

  • 如果两个cookie都存在,并且具有相同的值,则是一个活跃的会话

以下是到目前为止的代码:

Here's the code so far:

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中间件(但你可能已经在做,不是吗?)

If you're using this, you should implement a custom logout view, that explicitly deletes the audit cookie when the user logs out. Also, I'd suggest using the django signed-cookies middleware (but you're probably already doing that, aren't you?)

我认为您应该可以使用自定义会话后端来执行此操作。以下是一些(未经测试)的示例代码:

I think you should be able to do this using a custom session backend. Here's some (untested) sample code:

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,例如:

Save the code above as 'customdb.py' and add that to your django project. In your settings.py, set or replace 'SESSION_ENGINE' with the path to the above file, e.g.:

SESSION_ENGINE = 'yourproject.customdb'

然后在您的中间件或 models.py中,提供session_created信号的处理程序,如下所示:

Then in your middleware, or models.py, provide a handler for the 'session_created' signal, like so:

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)

Don不要在 INSTALLED_APPS 中包含包含 models.py 的应用程序。

Don't forget to include the app containing the models.py in INSTALLED_APPS.

这篇关于在Django中如何确定用户何时有空闲超时?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆