我使用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
每次用户从其他位置(不同的计算机/浏览器)登录时,都会创建一个新的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 Middleware设置允许您将用户名存储为Sessions表中的列,以便我可以针对该列运行SQL,或者我是否必须修改Sessions才能执行此操作?开箱即用它只有session_key
,session_data
和expire_date
列。
感谢您提供的任何见解或帮助。 :)
答案 0 :(得分: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()
这为数据库提供了两次点击。一次遍历所有Session
个对象,一次删除所有会话。不幸的是,我不知道有更直接的方法来过滤会话本身。
答案 1 :(得分:2)
最有效的方法是在登录期间存储用户的会话ID。您可以使用request.session._session_key访问会话ID,并将其存储在引用该用户的单独模型中。现在,当您想要删除用户的所有会话时,只需查询此模型,该模型将返回相关用户的所有活动会话。现在,您只需要从会话表中删除这些会话。比查找所有会话以过滤掉特定用户的会话要好得多。
答案 2 :(得分:1)
使用列表理解的另一个版本的函数,它将直接删除用户的每个未到期会话:
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')
]
答案 3 :(得分:0)
使用可能会有所帮助:
答案 4 :(得分:0)
这不是一个直接的答案,但它可以解决您的问题,并将数据库命中减少到零。使用最新版本的Django,您可以使用基于cookie的会话后端:
https://docs.djangoproject.com/en/dev/topics/http/sessions/#cookie-session-backend