python - ¿La forma más optimizada de eliminar todas las sesiones para un usuario específico en Django?
session-cookies django-authentication (5)
Esta no es una respuesta directa, pero resuelve su problema y reduce los aciertos de base de datos a cero. Con las versiones recientes de Django puede usar el backend de sesión basado en cookies:
https://docs.djangoproject.com/en/dev/topics/http/sessions/#cookie-session-backend
Estoy ejecutando Django 1.3, utilizando Sessions Middleware y Auth Middleware:
# settings.py
SESSION_ENGINE = django.contrib.sessions.backends.db # Persist sessions to DB
SESSION_COOKIE_AGE = 1209600 # Cookies last 2 weeks
Cada vez que un usuario inicia sesión desde una ubicación diferente (computadora / navegador diferente), se crea una nueva Session()
y se guarda con un session_id
Session()
único. Esto puede resultar en múltiples entradas de base de datos para el mismo usuario. Su inicio de sesión persiste en ese nodo hasta que la cookie se elimina o la sesión caduca.
Cuando un usuario cambia su contraseña, quiero eliminar de la base de datos todas las sesiones no vencidas para ese usuario. De esa manera, después de un cambio de contraseña, se les obliga a volver a iniciar sesión. Esto es por motivos de seguridad, como si le robaron su computadora o si accidentalmente se dejó conectado en un terminal público.
Quiero saber la mejor manera de optimizar esto. Así es como lo he hecho:
# 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()
Una vista muy simplificada:
# 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))
Como puede ver en sessions_helpers.py
, tengo que sacar todas las sesiones que no hayan expirado de la base de datos, Session.objects.filter(expire_date__gte=datetime.datetime.now())
, descodificarlas todas, y luego verificar si están coincide con un usuario o no. Esto será extremadamente costoso para la base de datos si, por ejemplo, hay más de 100,000 sesiones almacenadas allí.
¿Hay una forma más amigable para hacer esto en la base de datos? ¿Hay una configuración de middleware de sesión / autenticación que le permita almacenar el nombre de usuario como una columna en la tabla de sesiones para que pueda ejecutar SQL contra eso, o tendré que modificar las sesiones para hacerlo? Fuera de la caja solo tiene las columnas session_key
, session_data
y expire_date
.
Gracias por cualquier información o ayuda que pueda ofrecer. :)
La forma más eficiente es almacenar el ID de sesión del usuario durante el inicio de sesión. Puede acceder a la ID de sesión utilizando request.session._session_key y almacenarla en un modelo separado que tenga referencia al usuario. Ahora, cuando desee eliminar todas las sesiones del usuario, solo consulte este modelo, que devolverá todas las sesiones activas para el usuario en cuestión. Ahora necesita eliminar solo estas sesiones de la tabla de sesiones. Mucho mejor que tener que buscar todas las sesiones para filtrar solo las sesiones de un usuario en particular.
Otra versión de una función que usa la comprensión de lista que simplemente eliminará todas las sesiones que no hayan expirado de un usuario:
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'')
]
Podría ser útil usar:
- django-password-session para invalidar las sesiones del usuario después de cambiar una contraseña. Desde Django 1.7 esta característica implementada fuera de la caja.
- django-admin borra las sesiones para eliminar las cookies caducadas
Si devuelve un QuerySet desde su función all_unexpired_sessions_for_user
, podría limitar sus visitas a la base de datos a dos:
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()
Esto le da un total de dos visitas a la base de datos. Una vez para recorrer todos los objetos de la Session
, y una vez para eliminar todas las sesiones. Desafortunadamente, no conozco una forma más directa de filtrar las sesiones.