Оптимальный способ удалить все сеансы для конкретного пользователя в Django?

я запускаю Django 1.3, используя промежуточное ПО сеансов и промежуточное ПО Auth:

# settings.py

SESSION_ENGINE = django.contrib.sessions.backends.db   # Persist sessions to DB
SESSION_COOKIE_AGE = 1209600                           # Cookies last 2 weeks

каждый раз, когда пользователь входит в систему из другого места (другого компьютера/браузера), новый 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()

очень упрощенный вид:

# 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))

как вы можете видеть в sessions_helpers.py, Я должен вытащить каждый неистекший сеанс из БД, Session.objects.filter(expire_date__gte=datetime.datetime.now()), декодировать все из них, а затем проверить, если он соответствует пользователю или нет. Это будет чрезвычайно дорогостоящим для базы данных, если там хранятся, скажем, сеансы 100,000+.

есть ли более удобный для базы данных способ сделать это? Есть ли параметр промежуточного программного обеспечения Sessions/Auth, который позволит вам сохранить имя пользователя в виде столбца в таблице сеансов, чтобы я мог запустить SQL против этого, или мне придется изменить сеансы для этого? Из коробки он имеет только session_key, session_data, и expire_date столбцы.

Спасибо за информацию или помощь вы можете предложить. :)

5 ответов


Если вы возвращаете QuerySet из вашего all_unexpired_sessions_for_user функция, вы можете ограничить свои хиты базы данных двумя:

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()

Это дает вам в общей сложности два попадания в базу данных. Один раз, чтобы охватить все Session предметы, и один раз, чтобы удалить все сессии. К сожалению, я не знаю более прямого способа фильтровать сами сеансы.


наиболее эффективным способом является сохранение идентификатора сеанса пользователя во время входа в систему. Вы можете получить доступ к идентификатору сеанса с помощью запроса.сессия._session_key и сохранить его в отдельной модели, которая имеет ссылку на пользователя. Теперь, когда вы хотите удалить все сеансы пользователя, просто запросите эту модель, которая вернет все Активные сеансы для данного пользователя. Теперь вам нужно удалить только эти сеансы из таблицы сеансов. Гораздо лучше, чем искать все сеансы, чтобы отфильтровать просто сеансы для конкретного пользователя.


другая версия функции, использующая понимание списка, которая просто удалит каждый неистекший сеанс пользователя:

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')
    ]

Это может быть полезно использовать:


Это не прямой ответ, но он решает вашу проблему и уменьшает количество попаданий в БД до нуля. В последних версиях Django вы можете использовать бэкэнд сеанса на основе cookie:

https://docs.djangoproject.com/en/dev/topics/http/sessions/#cookie-session-backend