Как заставить пользователя выйти из системы в Django?

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

Я знаком с django.auth и auth. logout, но в качестве аргумента принимает запрос. Есть ли «способ Django» для выхода пользователя из системы, если все, что у меня есть, - это имя пользователя? Или мне нужно накатывать собственный SQL для выхода из системы?


person Sergey Golovchenko    schedule 05.06.2009    source источник
comment
Почему вы хотите выйти из системы не тому пользователю, который вошел в систему? если вы используете сеансы длиной в браузер, те пользователи, которые не вошли в систему, уже вышли из системы.   -  person Rama Vadakattu    schedule 05.06.2009
comment
Допустим, мне нужно выйти из системы после изменения пароля. У меня более 100 тыс. Пользователей и около 140 тыс. Сеансов в таблице сеансов. Как с этим справиться эффективно?   -  person kjagiello    schedule 15.02.2011
comment
@kjagiello Взгляните на серверную часть github.com/QueraTeam/django-qsessions. Используя его, вы можете легко выйти из системы пользователя: user.session_set.all().delete(). Отказ от ответственности: я являюсь автором django-qsessions.   -  person Mohammad Javad Naderi    schedule 10.11.2019


Ответы (10)


Обновлять:

Начиная с Django 1.7, пользователи автоматически выходят из системы при изменении пароля. При каждом запросе текущий хэш пароля сравнивается со значением, сохраненным в их сеансе, и, если не совпадает, пользователь выходит из системы.

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

Оригинал:

Я не думаю, что в 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()
person Harold    schedule 05.06.2009
comment
Спасибо за предложение, это решение для грубой силы, которое я пытался избежать. Однако, если нет других вариантов, мне, возможно, придется пойти с ним, возможно, с небольшим улучшением вместо того, чтобы все сеансы получали те, которые были обновлены в течение последних x минут, надеюсь, это резко улучшит производительность. - person Sergey Golovchenko; 05.06.2009
comment
Нет проблем. Полезным улучшением была бы фильтрация данных сеанса по последнему обновлению. - person Harold; 05.06.2009
comment
Стоит отметить, что Django 1.7 поддерживает сеанс . аннулирование при смене пароля. - person DavidM; 27.10.2014
comment
Для django 1.8.2 последнюю строку нужно изменить (преобразовать оба элемента в str), например: [s.delete () for s в Session.objects.all () if str (s.get_decoded (). Get ('_ auth_user_id ')) == str (user.id)] - person iqmaker; 28.08.2015
comment
URL @ DavidM теперь: docs .djangoproject.com / ru / 3.1 / themes / auth / default / - person Greg Sadetsky; 31.03.2021
comment
Удалит ли это файл cookie сеанса в браузере пользователя? Если нет, не будет ли этот файл cookie вызывать исключение SuspiciousOperation при каждой попытке доступа в Интернет? - person mirek; 06.05.2021

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

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

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

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

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

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

К счастью, система аутентификации Django предоставляет сигнал, называемый 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. реализовать проверку доступа

Лучший способ здесь - использовать промежуточное ПО, как было предложено Даном. Это промежуточное ПО будет обращаться к 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()
    
person Clément    schedule 20.02.2011
comment
Я новичок, поэтому, пожалуйста, извините, если я неправильно понял, но разве это не должно быть: update_session_last_login (sender, user = 'user', request = 'request', ** kwargs) :? - person rix; 25.02.2014
comment
На самом деле, я думаю, что Клеман имел в виду: def update_session_last_login (отправитель, пользователь, запрос, ** kwargs) :. - person Dylan; 21.06.2014

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

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)

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

person Tony Abou-Assaleh    schedule 24.10.2011
comment
Использование указанного поля измененного пароля. Если вы вошли в систему в 2 местах и ​​изменили свой пароль в A, затем войдите в систему A. Флаг измененного пароля будет снят, и вы останетесь в системе в B без необходимости вводить новый пароль. - person Mark; 04.06.2012
comment
@Mark, вы абсолютно правы. Я столкнулся с этой проблемой при использовании WebSockets. Решение, которое я адаптировал, заключалось в том, чтобы управлять хранением соединений самостоятельно, чтобы я мог ими манипулировать. Похоже, здесь потребуется аналогичный подход, то есть другая таблица сеанса, которая сопоставляет пользователей с идентификаторами сеансов. - person Tony Abou-Assaleh; 05.06.2012
comment
Ваше решение было именно тем, что я имел в виду, прежде чем искать другие способы, и, наконец, я сделал это так, когда попал на эту страницу. Всего один комментарий: ваше состояние можно было бы написать так, чтобы (на мой взгляд) было понятнее: if request.user.is_authenticated() and not request.user.is_active - person Yoone; 15.05.2015

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

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

person dan    schedule 09.06.2009

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

Да, имея около 140 тыс. Сеансов для повторения, я понимаю, почему ответ Гарольда может быть не таким быстрым, как вам хотелось бы!

Я бы порекомендовал добавить модель, только два свойства которой являются внешними ключами для объектов 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, когда вы закончите.)

person pythonian4000    schedule 18.02.2011

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

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

Документы для update_session_auth_hash здесь.

person Pankaj Sharma    schedule 28.02.2019

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

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

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

person Tzach    schedule 31.08.2015

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

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

  • django-qsessions (на основе db, cached_db сеансовых бэкэндов django)
  • django-user-sessions (на основе db сеансового бэкэнда django)

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

user.session_set.all().delete()

Заявление об ограничении ответственности: я являюсь автором django-qsessions.

person Mohammad Javad Naderi    schedule 11.04.2018

из сеанса импорта django.contrib.sessions.models

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

[s.delete() for s in Session.objects.all() if s.get_decoded().get('_auth_user_hash') == user.get_session_auth_hash()]
person Ashish Kumar Verma    schedule 03.06.2016

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

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

if request.user.is_active==False:
            return HttpResponse('You are banned on the site for spaming.')
person Pulkit Sharma    schedule 27.08.2017
comment
Этот ответ не отвечает на вопрос. Он не просит средства, чтобы отказать в ответе удаленному пользователю. - person Mohammed Shareef C; 16.06.2020