Подтвердить что ты не робот

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

Я запускаю Django 1.3, используя Session Middleware и Auth Middleware:

# settings.py

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

Каждый раз, когда пользователь регистрируется в другом месте (другой компьютер/браузер), создается новый Session() и сохраняется с уникальным session_id. Это может привести к появлению нескольких записей базы данных для одного и того же пользователя. Их логин сохраняется на этом node до тех пор, пока файл 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 + сеансов.

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

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

4b9b3361

Ответ 1

Если вы вернете 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 и один раз, чтобы удалить все сеансы. К сожалению, я не знаю более прямого способа фильтрации самих сеансов.

Ответ 2

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

Ответ 3

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

  • django-password-session, чтобы аннулировать сеансы пользователя после изменения пароля. Начиная с Django 1.7 эта функция реализована из коробки.
  • django-admin clearsessions для удаления истекших файлов cookie

Ответ 5

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

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