Як примусити вихід користувача в Django?


78

У моєму додатку Django за певних умов я хочу мати змогу примусити користувачів виходити за допомогою імені користувача. Не обов'язково поточний користувач, який увійшов у систему, але інший користувач. Отже, метод запиту, на мій погляд, не містить інформації про сеанс про користувача, з якого я хочу вийти.

Я знайомий з django.auth та з auth. метод виходу, але він приймає запит як аргумент. Чи існує «шлях Django» для виходу користувача з системи, якщо все, що у мене є, це ім’я користувача? Або мені доводиться прокручувати власний SQL-вихід?


Чому ви хочете вийти з облікового запису іншого користувача, ніж той, хто ввійшов у систему? якщо ви використовуєте сеанси тривалості браузера, ті користувачі, які не ввійшли в систему, вже вийшли з системи.
Рама Вадакатту,

3
Скажімо, мені потрібно вийти з користувача, коли пароль було змінено. У мене більше 100 тис. Користувачів і близько 140 тис. Сесій у таблиці сеансів. Як з цим впоратися ефективно?
kjagiello

1
@kjagiello Погляньте на github.com/QueraTeam/django-qsessions backend. З його допомогою ви можете легко вийти з системи користувача: user.session_set.all().delete(). Застереження: Я автор django-qsessions.
Мохаммад Джавад Надері

Відповіді:


83

Я не думаю, що ще існує санкціонований спосіб зробити це в Джанго.

Ідентифікатор користувача зберігається в об'єкті сеансу, але він кодується. На жаль, це означає, що вам доведеться перебирати всі сеанси, декодувати та порівнювати ...

Два кроки:

Спочатку видаліть об’єкти сеансу для цільового користувача. Якщо вони увійдуть з кількох комп’ютерів, вони матимуть кілька об’єктів сеансу.

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

1
Дякую за пропозицію, це шов є досить грубим рішенням, якого я намагався уникнути. Однак, якщо немає інших варіантів, мені, можливо, доведеться піти з цим, можливо, з невеликим вдосконаленням, замість того, щоб отримати "всі" сесії, а ті, які були оновлені протягом останніх "х" хвилин, сподіваємось, це суттєво покращило б ефективність.
Сергій Головченко

Без проблем. Фільтрування даних сеансу за останнім оновленням було б вартим покращенням.
Гарольд,

4
Варто зазначити, що Django 1.7 підтримує недійсність сеансу при зміні пароля .
DavidM

7
Для django 1.8.2 останній рядок потребує певних змін (перетворення обох елементів у str), як: [s.delete () for s у Session.objects.all (), якщо str (s.get_decoded (). Get ('_ auth_user_id ')) == str (user.id)]
iqmaker

60

Хоча відповідь Гарольда працює в цьому конкретному випадку, я бачу принаймні дві важливі проблеми:

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

Я новачок, тому, будь ласка, вибачте мене, якщо я неправильно зрозумів, але чи не так: update_session_last_login (sender, user = 'user', request = 'request', ** kwargs) :?
rix

Насправді, я думаю, Clément мав на увазі: def update_session_last_login (відправник, користувач, запит, ** kwargs) :.
Ділан,

48

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

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 вище, а також скасувати поле, коли користувач входить. Останній може виконати за допомогою сигналу user_logged_in від Django .


1
Використовуючи згадане поле зміненого пароля. Якщо ви ввійшли в систему з двох місцеположень і змінили пароль на A, то увійдіть на A. Змінений прапорець буде скасовано, і ви залишатиметеся в системі B без необхідності вводити новий пароль.
Марк

1
@Mark, ви абсолютно праві. Я зіткнувся з цією проблемою під час використання WebSockets. Рішенням було те, що я був адаптований для того, щоб керувати зберіганням з'єднань самостійно, щоб я міг ними керувати. Здається, тут потрібен подібний підхід, тобто інша таблиця сеансів, яка відображає користувачів до ідентифікаторів сеансу.
Тоні Абу-Ассале

5
Твоє рішення було саме тим, що я мав на увазі перед пошуком інших шляхів, і нарешті я зробив це таким чином після того, як потрапив на цю сторінку. Лише один коментар: ваш стан можна if request.user.is_authenticated() and not request.user.is_active
написати

7

Можливо, трохи проміжного програмного забезпечення, яке посилається на список користувачів, яких змусили вийти. Наступного разу, коли користувач спробує щось зробити, тоді вийдіть з них, перенаправіть їх тощо.

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


6

Це відповідь на запит Балона:

Так, із приблизно 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 за замовчуванням, коли об'єкт, на який посилається a ForeignKey, видаляється, полягає в його каскадному видаленні, а також видалення об'єкта, що містить ForeignKey, але якщо ні, то це досить тривіально, щоб видалити вміст, sessionProfilesколи ви закінчите.)


3

Ви також можете використовувати для цього пряму функцію django, яка оновить і вийде з системи всі інші сеанси для користувача, крім поточної.

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

Документи для update_session_auth_hash тут .


2

Як Тоні Абу-Ассале, мені також потрібно було вийти з користувачів, які були неактивними, тому я почав із впровадження його рішення. Через деякий час я з'ясував, що проміжне програмне забезпечення примушує запит БД до всіх запитів (щоб перевірити, чи не було заблоковано користувача), і, отже, шкодить продуктивності сторінок, які не потребують входу.

У мене є користувальницький користувальницький об'єкт і 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


0

Як заявляли інші, ви можете переглядати всі сеанси в БД, декодувати всі та видаляти ті, що належать цьому користувачеві. Але це повільно, особливо якщо ваш сайт має великий трафік і є багато сесій.

Якщо вам потрібне швидше рішення, ви можете використовувати серверну середу, яка дозволяє запитувати та отримувати сеанси певного користувача. У цих фонових сеансах Session має зовнішній ключ для користувача, тому вам не потрібно переглядати всі об’єкти сеансу:

  • django-qsessions (на основі django db, cached_dbсеансові резервні копії)
  • django-user-db session (на основі бекенда сеансу django )

За допомогою цих серверних систем видалення всіх сеансів користувача може здійснюватися в одному рядку коду:

user.session_set.all().delete()

Застереження: Я є автором django-qsessions.


-1

з 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()]

-5

Навіть я стикався з цим питанням. Небагато спамерів з Індії продовжують писати про тих Бабу та Молві, щоб знайти рішення для любові.

Те, що я зробив, на момент публікації щойно вставило цей код:

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

Ця відповідь не відповідає на питання. Він не просить засобів, щоб заборонити відповідь видаленому користувачеві
Mohammed Shareef C
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.