2012-01-19 2 views
21

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

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

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 runningerver) работает нормально, он запускает старую сессию ...

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

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

Ближайший я нашел this, но это своего рода проблема «противоположного» ...

Любая помощь будет очень признательна.

Edit: я добавил отладочную печать перед удалением сессии ... вот отрывок из error.log Апача:

[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)

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

Я бегу по сравнению с точно такой же PostgreSQL, например, как я сделал с devserver ...

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() 
+4

Когда вы говорите «не работает», что именно не работает? Вы все еще видите старую сессию в БД? Если вы поместили вызов печати/регистрации непосредственно перед удалением 'Session', вы видите, что это выполнено в' mod_wsgi'? – AdamKG

+0

@AdamKG: Спасибо, что указал мне в правильном направлении! –

+0

Если сеансовым движком является cache_db, я думаю, нам также нужно вручную удалить ключ сеанса из кеша, не так ли? – Wesley

ответ

1

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

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

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

Это, конечно, работает, только если у вас есть логин() метод функция, которая хранит ПОЛЬЗОВАТЕЛЕЙ имя пользователя в его сессии, как следующим образом:

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

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

+0

_username больше не находится в объекте get_decoded()() Django 1.8). Однако user_id есть, поэтому 'row.get_decoded(). Get (" _ auth_user_id ") == request.user.pk' будет работать. – guival

1

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

Когда пользователь входит в систему, пройдите все активные сеансы и удалите их с тем же 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) 
+0

Вы сравнили производительность между двумя способами? Я имею в виду, путь через сигналы и промежуточное ПО. Если используется промежуточное ПО, все запросы должны проходить через handle_request. Если использовать сигналы, хотя только функция входа в систему попала в функцию, но она будет перебирать таблицу сеансов ... так что подумайте о производительности, что лучше ... – Wesley

+0

Я действительно сказал «для небольших сайтов» :) - вы можете захотеть хранить ключ, как OP, но только проверять его при входе в систему, как я делаю для более эффективного решения. – MarZab

+0

Да, я вижу, что вы упомянули для небольших сайтов :-) Я просто хочу знать, что лучше для более крупных сайтов :-) – Wesley

0

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

+0

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