Оптимальный способ удалить все сеансы для конкретного пользователя в 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-пароль-сессия чтобы аннулировать сеансы пользователя после изменения пароля. Начиная с Django 1.7 эта функция реализована из коробки.
- django-администратор clearsessions удалить устаревшие файлы cookies
Это не прямой ответ, но он решает вашу проблему и уменьшает количество попаданий в БД до нуля. В последних версиях Django вы можете использовать бэкэнд сеанса на основе cookie:
https://docs.djangoproject.com/en/dev/topics/http/sessions/#cookie-session-backend