最优化的方法是删除Django中特定用户的所有会话?

Dav*_*her 17 python django django-authentication session-cookies django-sessions

我使用Sessions Middleware和Auth Middleware运行Django 1.3:

# settings.py

SESSION_ENGINE = django.contrib.sessions.backends.db   # Persist sessions to DB
SESSION_COOKIE_AGE = 1209600                           # Cookies last 2 weeks
Run Code Online (Sandbox Code Playgroud)

每次用户从其他位置(不同的计算机/浏览器)登录时,Session()都会创建一个新的并使用唯一的方式保存session_id.这可能导致同一用户的多个数据库条目.他们的登录在该节点上持续存在,直到cookie被删除或会话到期为止.

当用户更改其密码时,我想从数据库中删除该用户的所有未到期会话.密码更改后,他们被迫重新登录.这是出于安全目的,例如,如果您的计算机被盗,或者您不小心将自己登录在公共终端上.

我想知道优化它的最佳方法.这是我如何做到的:

# sessions_helpers.py

from django.contrib.sessions.models import Session
import datetime

def all_unexpired_sessions_for_user(user):
    user_sessions = []
    all_sessions  = Session.objects.filter(expire_date__gte=datetime.datetime.now())
    for session in all_sessions:
        session_data = session.get_decoded()
        if user.pk == session_data.get('_auth_user_id'):
            user_sessions.append(session)
    return user_sessions

def delete_all_unexpired_sessions_for_user(user, session_to_omit=None):
    for session in all_unexpired_sessions_for_user(user):
        if session is not session_to_omit:
            session.delete()
Run Code Online (Sandbox Code Playgroud)

一个非常简化的视图:

# views.py

from django.http import HttpResponse
from django.shortcuts import render_to_response
from myapp.forms import ChangePasswordForm
from sessions_helpers import delete_all_unexpired_sessions_for_user

@never_cache
@login_required
def change_password(request):
    user = request.user

    if request.method == 'POST':
        form = ChangePasswordForm(data=request)

        if form.is_valid():
            user.set_password(form.get('password'))
            user.save()
            request.session.cycle_key()         # Flushes and replaces old key. Prevents replay attacks.
            delete_all_unexpired_sessions_for_user(user=user, session_to_omit=request.session)
            return HttpResponse('Success!')

    else:
        form = ChangePasswordForm()

    return render_to_response('change_password.html', {'form':form}, context_instance=RequestContext(request))
Run Code Online (Sandbox Code Playgroud)

正如您所看到的sessions_helpers.py,我必须从DB中提取所有未到期的会话Session.objects.filter(expire_date__gte=datetime.datetime.now()),解码所有会话,然后检查它是否与用户匹配.如果存在100,000多个会话,那么对数据库来说这将是非常昂贵的.

是否有更加数据库友好的方式来做到这一点?是否有Sessions/Auth Middleware设置允许您将用户名存储为Sessions表中的列,以便我可以针对该列运行SQL,或者我是否必须修改Sessions才能执行此操作?外的开箱它只有session_key,session_dataexpire_date列.

感谢您提供的任何见解或帮助.:)

Jac*_* M. 20

如果从all_unexpired_sessions_for_user函数返回QuerySet ,则可以将数据库命中数限制为两个:

def all_unexpired_sessions_for_user(user):
    user_sessions = []
    all_sessions  = Session.objects.filter(expire_date__gte=datetime.datetime.now())
    for session in all_sessions:
        session_data = session.get_decoded()
        if user.pk == session_data.get('_auth_user_id'):
            user_sessions.append(session.pk)
    return Session.objects.filter(pk__in=user_sessions)

def delete_all_unexpired_sessions_for_user(user, session_to_omit=None):
    session_list = all_unexpired_sessions_for_user(user)
    if session_to_omit is not None:
        session_list.exclude(session_key=session_to_omit.session_key)
    session_list.delete()
Run Code Online (Sandbox Code Playgroud)

这为数据库提供了总共两次点击.一旦遍历所有Session对象,一次删除所有会话.不幸的是,我不知道有更直接的方法来过滤会话本身.

  • 我需要将`user.pk`转换为字符串才能使其工作:`if str(user.pk)== session_data.get('_ auth_user_id')` (2认同)

Ed *_*Tan 8

使用列表理解的函数的另一个版本将直接删除用户的每个未过期会话:

from django.utils import timezone
from django.contrib.sessions.models import Session


def delete_all_unexpired_sessions_for_user(user):
    unexpired_sessions = Session.objects.filter(expire_date__gte=timezone.now())
    [
        session.delete() for session in unexpired_sessions
        if str(user.pk) == session.get_decoded().get('_auth_user_id')
    ]
Run Code Online (Sandbox Code Playgroud)