Разрешение только одного активного сеанса для каждого пользователя в приложении Django

Я хочу ограничить вошедших в систему пользователей только одним активным сеансом, т.е. если пользователь входит в систему с новым идентификатором сеанса, старый сеанс должен быть завершен. Я уже нашел много помощи по SO: здесь и здесь

Я реализовал решение промежуточного программного обеспечения с небольшой дополнительной проверкой...

class OnlyOneUserMiddleware(object):
"""
Middleware to ensure that a logged-in user only has one session active.
Will kick out any previous session. 
"""
def process_request(self, request):
    if request.user.is_authenticated():
        try:
            cur_session_key = request.user.get_profile().session_key
            if cur_session_key and cur_session_key != request.session.session_key:
                # Default handling... kick the old session...
                Session.objects.get(session_key=cur_session_key).delete()
            if not cur_session_key or cur_session_key != request.session.session_key:
                p = request.user.get_profile()
                p.session_key = request.session.session_key
                p.save()
        except ObjectDoesNotExist:
            pass

Пока все хорошо... на сервере Django dev (manage.py runserver) все работает нормально, старая сессия пинается...

... но при использовании Apache (с mod_wsgi) это не работает!

Я пытался найти какую-либо информацию об этом, но пока безуспешно...

Самое близкое, что я нашел, это это, но это своего рода "противоположная" проблема ...

Любая помощь приветствуется.

Изменить: я добавил отладочную печать перед удалением сеанса... вот фрагмент из журнала ошибок Apache:

[Fri Jan 20 09:56:50 2012] [error] old key = f42885ccb7f33b6afcb2c18fca14f44a
[Fri Jan 20 09:56:50 2012] [error] new key = ce4cfb672e6025edb8ffcd0cf2b4b8d1
[Fri Jan 20 09:57:14 2012] [error] old key = f42885ccb7f33b6afcb2c18fca14f44a
[Fri Jan 20 09:57:14 2012] [error] new key = 0815c56241ac21cf4b14b326f0aa7e24

первые две лжи, когда я вошел с первой сессией (Firefox)

последние два - когда я вошел со второй сессией (Chromium)

... получается, что старая запись Session не удаляется... ???

Я использую тот же самый экземпляр PostgreSQL, что и с сервером разработки...

Edit2: оказалось, что мой код содержит ошибки... он не работает, когда новый Session_key не найден в сеансе...

вот исправленный код... try..except теперь в правильном месте

class OnlyOneUserMiddleware(object):
    """
    Middleware to ensure that a logged-in user only has one session active.
    Will kick out any previous session. 
    """
    def process_request(self, request):
        if request.user.is_authenticated():
            cur_session_key = request.user.get_profile().session_key
            if cur_session_key and cur_session_key != request.session.session_key:
                # Default handling... kick the old session...
                try:
                    s = Session.objects.get(session_key=cur_session_key)
                    s.delete()
                except ObjectDoesNotExist:
                    pass
            if not cur_session_key or cur_session_key != request.session.session_key:
                p = request.user.get_profile()
                p.session_key = request.session.session_key
                p.save()

person Mark Johansson    schedule 19.01.2012    source источник
comment
Когда вы говорите, что не работает, что именно не работает? Вы все еще видите старую сессию в БД? Если вы поместите вызов печати/регистрации непосредственно перед удалением Session, вы увидите, что он выполняется под mod_wsgi?   -  person AdamKG    schedule 19.01.2012
comment
@AdamKG: Спасибо, что указали мне правильное направление!   -  person Mark Johansson    schedule 20.01.2012
comment
Если механизм сеанса — cache_db, я думаю, нам также нужно вручную удалить сеансовый ключ из кеша, верно?   -  person Wesley    schedule 26.08.2016
comment
Что делает user.get_profile()?   -  person guival    schedule 02.01.2017


Ответы (3)


Подобных вопросов действительно много, но вот мое решение.

Когда пользователь входит в систему, просматриваются все активные сеансы и удаляются те, у которых одинаковые user.id. Для небольших сайтов это должно подойти.

# __init__.py
# Logs user out from all other sessions on login, django 1.8

from django.contrib.sessions.models import Session
from django.contrib.auth.signals import user_logged_in
from django.db.models import Q
from django.utils import timezone

def limit_sessions(sender, user, request, **kwargs):
    # this will be slow for sites with LOTS of active users

    for session in Session.objects.filter(
        ~Q(session_key = request.session.session_key),
        expire_date__gte = timezone.now()
    ):
        data = session.get_decoded()
        if data.get('_auth_user_id', None) == str(user.id):
            # found duplicate session, expire it
            session.expire_date = timezone.now()
            session.save()

    return

user_logged_in.connect(limit_sessions)
person MarZab    schedule 07.05.2015
comment
Вы сравнивали производительность между двумя способами? Я имею в виду, через сигналы и промежуточное ПО. При использовании промежуточного ПО все запросы должны проходить через файл handle_request. Если использовать сигналы, хотя только событие входа в систему попадает в функцию, но она будет перебирать таблицу сеансов... так что подумайте о производительности, что лучше... - person Wesley; 26.08.2016
comment
я сказал для небольших веб-сайтов :) - вы можете сохранить ключ, как это делает OP, но проверять его только при входе в систему, как я делаю для более эффективного решения - person MarZab; 26.08.2016
comment
Да, я вижу, вы упомянули о небольших сайтах :-) Я просто хочу знать, что лучше для больших сайтов :-) - person Wesley; 27.08.2016

Вы всегда можете использовать этот подход, хотя и не рекомендуется, но он работает.

my_old_sessions = Session.objects.all()
for row in my_old_sessions:
   if row.get_decoded().get("_username") == request.user.username:
      row.delete()

Вы должны реализовать приведенный выше код в своей функции login() прямо перед аутентификацией пользователя.

Это, конечно, работает только в том случае, если у вас есть метод функции login(), который сохраняет имя пользователя USERS в своем сеансе следующим образом:

request.session["_username"] = request.user.username

Если вы используете этот подход, просто не забудьте очистить свою базу данных от всех ваших сеансов перед запуском сервера после внесения этих изменений, потому что это вызовет ошибки KeyLookUp.

person Froweey    schedule 26.01.2012
comment
_username больше не находится в get_decoded() объекта сеанса (Django 1.8). Однако user_id есть, поэтому row.get_decoded().get("_auth_user_id") == request.user.pk будет работать. - person guival; 02.01.2017

Я чувствую, что здесь могут как-то помочь сигналы django.contrib.auth. При входе в систему аннулируйте старые сеансы пользователя.

person Italo Maia    schedule 31.08.2016
comment
Если вы уже работаете с пользовательской базой данных, это будет означать, что все пользователи должны сначала выйти из системы, чтобы это работало. - person guival; 02.01.2017