Как принудительно выйти из системы в django?

В моем приложении django при определенных условиях мне нужно заставить пользователя выйти из системы по имени пользователя. Не обязательно текущий пользователь, который вошел в систему, но другой пользователь. Таким образом, метод запроса в моем представлении не имеет информации о сеансе пользователя, который я хочу выйти из системы.

Я знаком с django.auth и с методом auth.logout, но он принимает запрос в качестве аргумента. Есть ли способ "django-way" для регистрации пользователя, если у меня есть имя пользователя? Или мне нужно свернуть собственный logout sql?

Ответ 1

Я не думаю, что в Django еще есть санкционированный способ сделать это.

Идентификатор пользователя сохраняется в объекте сеанса, но он кодируется. К сожалению, это означает, что вам придется перебирать все сеансы, декодировать и сравнивать...

Два шага:

Сначала удалите объекты сеанса для целевого пользователя. Если они вступают в систему с нескольких компьютеров, у них будет несколько объектов сеанса.

from django.contrib.sessions.models import Session
from django.contrib.auth.models import User

# grab the user in question 
user = User.objects.get(username='johndoe')

[s.delete() for s in Session.objects.all() if s.get_decoded().get('_auth_user_id') == user.id]

Затем, если вам нужно, заблокируйте их.

user.is_active = False
user.save()

Ответ 2

Хотя ответ Гарольда работает в этом конкретном случае, я вижу по крайней мере две важные проблемы:

  • Это решение может использоваться только с базой данных сеансовым движком. В других ситуациях (кеш, файл, cookie) модель Session не будет использоваться.
  • Когда количество сеансов и пользователей в базе данных растет, это становится довольно неэффективным.

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

Затем, когда кто-либо получает доступ к вашему сайту, если зарегистрированная дата ниже даты выхода, вы можете принудительно выйти из системы. Как сказал дан, нет никакой практической разницы между немедленным выводом пользователя или его последующим запросом на ваш сайт.

Теперь рассмотрим возможную реализацию этого решения для django 1.3b1. В три этапа:

1. сохранить в сеансе последнюю дату входа

К счастью, система Django auth предоставляет signal под названием user_logged_in. Вам просто нужно зарегистрировать эти сигналы и сохранить текущую дату в сеансе. В нижней части вашего models.py:

from django.contrib.auth.signals import user_logged_in
from datetime import datetime

def update_session_last_login(sender, user=user, request=request, **kwargs):
    if request:
        request.session['LAST_LOGIN_DATE'] = datetime.now()
user_logged_in.connect(update_session_last_login)

2. запросить отключение пользователя для пользователя

Нам просто нужно добавить поле и метод в модель User. Существует несколько способов достижения этого (профили пользователей, наследование модели и т.д.), каждый с плюсами и минусами.

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

from django.contrib.auth.models import User
from django.db import models
from datetime import datetime

class MyUser(User):
    force_logout_date = models.DateTimeField(null=True, blank=True)

    def force_logout(self):
        self.force_logout_date = datetime.now()
        self.save()

Затем, если вы хотите принудительно выйти из системы для пользователя johndoe, вам просто нужно:

from myapp.models import MyUser
MyUser.objects.get(username='johndoe').force_logout()

3. выполнить проверку доступа

Лучший способ здесь - использовать middleware в качестве предлагаемого. Это промежуточное программное обеспечение будет иметь доступ к request.user, поэтому вам нужно поместить его после 'django.contrib.auth.middleware.AuthenticationMiddleware' в настройку MIDDLEWARE_CLASSES.

from django.contrib.auth import logout

class ForceLogoutMiddleware(object):
    def process_request(self, request):
        if request.user.is_authenticated() and request.user.force_logout_date and \
           request.session['LAST_LOGIN_DATE'] < request.user.force_logout_date:
            logout(request)

Это должно сделать это.


Примечания

  • Помните о влиянии на производительность хранения дополнительного поля для ваших пользователей. Использование наследования модели добавит дополнительный JOIN. Использование профилей пользователей добавит дополнительный запрос. Модификация непосредственно User - лучший способ производительности, но он по-прежнему является волосатой темой.
  • Если вы развертываете это решение на существующем сайте, у вас, вероятно, возникнут проблемы с существующими сеансами, в которых не будет ключа 'LAST_LOGIN_DATE'. Вы можете немного адаптировать код промежуточного кода для решения этого случая:

    from django.contrib.auth import logout
    
    class ForceLogoutMiddleware(object):
        def process_request(self, request):
            if request.user.is_authenticated() and request.user.force_logout_date and \
               ( 'LAST_LOGIN_DATE' not in request.session or \
                 request.session['LAST_LOGIN_DATE'] < request.user.force_logout_date ):
                logout(request)
    
  • В django 1.2.x отсутствует сигнал user_logged_in. Вернитесь к переопределению функции login:

    from django.contrib.auth import login as dj_login
    from datetime import datetime
    
    def login(request, user):
        dj_login(request, user)
        request.session['LAST_LOGIN_DATE'] = datetime.now()
    

Ответ 3

Мне нужно что-то подобное в моем приложении. В моем случае, если пользователь был настроен неактивно, я хотел убедиться, что пользователь уже зарегистрирован, что он будет выходить из системы и не сможет продолжать использовать этот сайт. После прочтения этого сообщения я пришел к следующему решению:

from django.contrib.auth import logout

class ActiveUserMiddleware(object):
    def process_request(self, request):
        if not request.user.is_authenticated():
            return
        if not request.user.is_active:
           logout(request)

Просто добавьте это промежуточное программное обеспечение в свои настройки и отключайтесь. В случае смены паролей вы можете ввести новое поле в модели userprofile, которое заставляет пользователя выйти из системы, проверить значение поля вместо is_active выше, а также отключить поле при входе пользователя в систему. Последнее может сделайте с помощью сигнала Django user_logged_in.

Ответ 4

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

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

Ответ 5

Это ответ на запрос Balon:

Да, с примерно 140k сессиями для итерации я могу понять, почему ответ Гарольда может быть не таким быстрым, как вам может понравиться!

То, что я бы рекомендовал, - это добавить модель, только два свойства которой являются внешними ключами для объектов User и Session. Затем добавьте некоторое промежуточное программное обеспечение, которое будет поддерживать эту модель в актуальном состоянии при текущих сеансах пользователя. Раньше я использовал такую ​​настройку; в моем случае я заимствовал модуль sessionprofile из этой системы единого входа для phpBB (см. исходный код в папке "django/sessionprofile" ), и это (Я думаю) будет соответствовать вашим потребностям.

В результате вы получите некоторую функцию управления где-нибудь в вашем коде, как это (при условии, что те же самые кодовые имена и макет, что и в модуле sessionprofile, приведенном выше):

from sessionprofile.models import SessionProfile
from django.contrib.auth.models import User

# Find all SessionProfile objects corresponding to a given username
sessionProfiles = SessionProfile.objects.filter(user__username__exact='johndoe')

# Delete all corresponding sessions
[sp.session.delete() for sp in sessionProfiles]

(Я думаю, что это также удалит объекты sessionprofile, как из того, что я помню, поведение по умолчанию Django, когда объект, на который ссылается ForeignKey, удаляется, чтобы его каскадировать, а также удалить объект, содержащий ForeignKey, но если нет, то достаточно тривиально удалить содержимое sessionProfiles, когда вы закончите.)

Ответ 6

Как Тони Абу-Ассале, мне также пришлось выходить из системы, у которых были отключены, поэтому я начал с реализации его решения. Через некоторое время я узнал, что промежуточное ПО заставляет запрос БД выполнять все запросы (чтобы проверить, был ли пользователь заблокирован) и, таким образом, вредит производительности на страницах, которые не требуют входа в систему.

У меня есть пользовательский объект и Django >= 1.7, поэтому то, что я закончил, переопределяет его функцию get_session_auth_hash, чтобы аннулировать когда пользователь неактивен. Возможная реализация:

def get_session_auth_hash(self):
    if not self.is_active:
        return "inactive"
    return super(MyCustomUser, self).get_session_auth_hash()

Чтобы это сработало, django.contrib.auth.middleware.SessionAuthenticationMiddleware должно быть в settings.MIDDLEWARE_CLASSES

Ответ 7

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

from django.contrib.auth import update_session_auth_hash
update_session_auth_hash(self.request, user)

Ответ 8

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

Если вам нужно более быстрое решение, вы можете использовать бэкэнд сеанса, который позволяет запрашивать и получать сеансы определенного пользователя. В этих серверных сеансах Session имеет внешний ключ для User, поэтому вам не нужно перебирать все объекты сеанса:

  • django-user- session (на основе серверной части django db)
  • django-qsessions (на основе cached_db сеанса django cached_db)

Используя эти бэкэнды, можно удалить все сеансы пользователя в одной строке кода:

user.session_set.all().delete()

Отказ от ответственности: я автор django-qsessions.

Ответ 9

из django.contrib.sessions.models import Session

удаление сеанса пользователя

[s.delete() for s in Session.objects.all() if s.get_decoded().get('_auth_user_hash') == user.get_session_auth_hash()]

Ответ 10

Даже я столкнулся с этой проблемой. Немногие спамеры из Индии продолжают публиковать сообщения о тех Баба и Мольви для любовных решений.

То, что я делал, во время публикации только что вставил этот код:

if request.user.is_active==False:
            return HttpResponse('You are banned on the site for spaming.')