Я пишу приложение, в котором я буду получать доступ к базе данных из django и из автономного приложения. Оба должны выполнить проверку сеанса, и сеанс должен быть одинаковым для них обоих. Django имеет встроенную аутентификацию / проверку сеанса, которую я использую, теперь мне нужно выяснить, как повторно использовать тот же сеанс для моего автономного приложения.
У меня вопрос: как мне найти session_key для конкретного пользователя?
Судя по всему, нет ничего, что связывало бы auth_user и django_session





Это несколько сложно сделать, потому что не каждый сеанс обязательно связан с аутентифицированным пользователем; Фреймворк сеансов Django также поддерживает анонимные сеансы, и любой, кто посещает ваш сайт, будет иметь сеанс, независимо от того, вошел ли он в систему.
Это еще больше усложняется тем фактом, что сериализуется сам объект сеанса - поскольку Django не имеет возможности узнать, какие именно данные вы хотите сохранить, он просто сериализует словарь данных сеанса в строку (используя стандартный Python "pickle" модуль) и вставляет его в вашу базу данных.
Если у вас есть ключ сеанса (который будет отправлен браузером пользователя как значение cookie "sessionid"), самый простой способ получить данные - просто запросить таблицу сеанса для сеанса с этим ключом, который возвращает сеанс объект. Затем вы можете вызвать метод этого объекта get_decoded (), чтобы получить словарь данных сеанса. Если вы не используете Django, вы можете посмотреть исходный код (django / contrib / sessions / models.py), чтобы увидеть, как десериализуются данные сеанса.
Однако, если у вас есть идентификатор пользователя, вам нужно будет пройти через все объекты сеанса, десериализуя каждый из них и ища тот, который имеет ключ с именем «_auth_user_id» и для которого значением этого ключа является идентификатор пользователя. .
Изменение таблицы django_session для добавления явного user_id может значительно облегчить жизнь. Предполагая, что вы это сделаете (или что-то подобное), вот четыре подхода к выбору вещей по своему вкусу:
Разветвите код django.contrib.session. Я знаю, знаю, это ужасно предлагать. Но это всего 500 строк, включая все бэкенды без тестов. Взломать довольно просто. Это лучший маршрут только в том случае, если вы собираетесь серьезно переставить вещи.
Если вы не хотите форкнуть, вы можете попробовать подключиться к сигналу Session.post_save и сыграть там.
Или вы можете MonkeyPatch contrib.session.models.Session.save(). Просто оберните существующий метод (или создайте новый), выделите / синтезируйте любые значения, которые вам нужны, сохраните их в своих новых полях, а затем super(Session, self).save().
Еще один способ сделать это - добавить 2 (да, два) класса промежуточного программного обеспечения - один до и один после SessionMiddleware в вашем файле settings.py. Это связано со способом обработки промежуточного программного обеспечения. Тот, который указан после SessionMiddleware, получит во входящем запросе запрос с уже присоединенным к нему сеансом. Один из перечисленных выше может выполнять любую обработку ответа и / или изменять / повторно сохранять сеанс.
Мы использовали вариант этого последнего метода для создания псевдосеансов для пауков поисковых систем, чтобы предоставить им особый доступ к материалам, которые обычно доступны только участникам. Мы также обнаруживаем входящие ссылки, в которых поле REFERER находится в соответствующей поисковой системе, и даем пользователю полный доступ к этой статье.
Обновлять:
Мой ответ теперь довольно древний, хотя в основном он верен. См. Гораздо более свежий ответ @ Gavin_Ballard (29 сентября 2014 г.) ниже, чтобы узнать о еще одном подходе к этой проблеме.
Это может нарушить тесты, если вы сделаете это без разветвления кода django. Если приложение сеансов находится в INSTALLED_APPS, таблица сеансов будет создана дважды, что приведет к прерыванию тестов. Я не уверен, безопасно ли удалять приложение сеанса django, если вы предоставили свою собственную таблицу django_session.
Теперь доступны пользовательские сеансы Django, которые предоставляют вам эту функциональность: github.com/Bouke/django-user-sessions
@ sww314 - Я быстро просмотрел связанный проект GitHub, и это выглядит как очень разумный подход к проблеме. Спасибо!
Питер Роуэлл, спасибо за ответ. Это была огромная помощь. Вот что я сделал, чтобы он заработал. Пришлось изменить только один файл в djang.contrib.sessions.
В django / contrib / sessions / models.py добавьте user_id в таблицу (добавьте в таблицу БД вручную или удалите таблицу и запустите manage.py syncdb).
class Session(models.Model):
...
user_id = models.IntegerField(_('user_id'), null=True)
...
def save(self, *args, **kwargs):
user_id = self.get_decoded().get('_auth_user_id')
if ( user_id != None ):
self.user_id = user_id
# Call the "real" save() method.
super(Session, self).save(*args, **kwargs)
Теперь в вашем представлении, где вы выполняете вход (если вы используете базовый вход в django, вам придется его переопределить)
# On login, destroy all prev sessions
# This disallows multiple logins from different browsers
dbSessions = Session.objects.filter( user_id = request.user.id )
for index, dbSession in enumerate( dbSessions ):
if ( dbSession.session_key != request.session.session_key ):
dbSession.delete()
Это сработало для меня.
Я нашел этот фрагмент кода
from django.contrib.sessions.models import Session
from django.contrib.auth.models import User
session_key = '8cae76c505f15432b48c8292a7dd0e54'
session = Session.objects.get(session_key=session_key)
uid = session.get_decoded().get('_auth_user_id')
user = User.objects.get(pk=uid)
print user.username, user.get_full_name(), user.email
здесь http://scottbarnham.com/blog/2008/12/04/get-user-from-session-key-in-django/
Еще не проверял, но выглядит довольно прямолинейно.
Прямая, но обратная операция. Получение ключа из идентификатора пользователя (без изменения моделей) требует повторения каждого сеанса.
Голосование за поддержку, потому что поиск Google по запросу "сеанс десериализации django" ведет сюда.
Я столкнулся с этой проблемой, когда хотел выгнать спамера. Кажется, что сделать их учетную запись «неактивной» недостаточно, потому что они все еще могут войти в свой предыдущий сеанс. Итак - как удалить сеанс для определенного пользователя или как сознательно истечь сеанс для определенного пользователя?
Ответ заключается в том, чтобы использовать поле last_login для отслеживания времени, когда сеанс был отключен, что говорит вам, что expire_date - две недели спустя, что позволяет вам выполнить полезный фильтр для таблицы сеансов:
from django.contrib.sessions.models import Session
from django.contrib.auth.models import User
from datetime import datetime
from dateutil.relativedelta import relativedelta
baduser = User.objects.get(username = "whoever")
two_weeks = relativedelta(weeks=2)
two_hours = relativedelta(hours=2)
expiry = baduser.last_login + two_weeks
sessions = Session.objects.filter(
expire_date__gt=expiry - two_hours,
expire_date__lt=expiry + two_hours
)
print sessions.count() # hopefully a manageable number
for s in sessions:
if s.get_decoded().get('_auth_user_id') == baduser.id:
print(s)
s.delete()
Хакерский, но эффективный, мне это нравится! К сожалению, я думаю, что это не работает, когда серверная часть сеанса - это redis, а не БД.
Этот ответ публикуется через пять лет после исходного вопроса, но эта ветка SO является одним из лучших результатов Google при поиске решения этой проблемы (и это все еще то, что не поддерживается Django из коробки).
У меня есть альтернативное решение для случая использования, когда вас интересуют только зарегистрированные пользовательские сеансы, в котором используется дополнительная модель UserSession для сопоставления пользователей с их сеансами, примерно так:
from django.conf import settings
from django.db import models
from django.contrib.sessions.models import Session
class UserSession(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL)
session = models.ForeignKey(Session)
Затем вы можете просто сохранить новый экземпляр UserSession каждый раз, когда пользователь входит в систему:
from django.contrib.auth.signals import user_logged_in
def user_logged_in_handler(sender, request, user, **kwargs):
UserSession.objects.get_or_create(user = user, session_id = request.session.session_key)
user_logged_in.connect(user_logged_in_handler)
И, наконец, когда вы хотите перечислить (и, возможно, очистить) сеансы для конкретного пользователя:
from .models import UserSession
def delete_user_sessions(user):
user_sessions = UserSession.objects.filter(user = user)
for user_session in user_sessions:
user_session.session.delete()
Это все гайки и болты, если вы хотите более подробно, у меня есть Сообщение блога, покрывающий это.
Проблема, с которой я столкнулся, заключается в том, что сеанс не обязательно сохраняется при срабатывании user_logged_in, и поэтому возникает ошибка ограничения FK. Безопасно ли сначала запускать request.session.save() в user_logged_in_handler()? В качестве альтернативы я мог бы изменить поле FK на текст, но это означает, что мне, вероятно, потребуется дополнительная очистка.
Дополнительный вопрос: что, если бы я хотел отслеживать логины пользователей? Удалив сеанс, я потеряю это. Как я могу аннулировать сеанс, не удаляя его?
@DanH Я не видел этой проблемы; вместо того, чтобы звонить request.session.save(), я бы выбрал второй вариант и не использовал бы FK. Единственная дополнительная очистка, которую вам нужно сделать, - это вызвать user_session.delete() после user_session.session.delete(). (В этом примере вам нужно будет добавить метод session() к вашей модели UserSession, чтобы найти правильный сеанс.)
@alfetopito: Вы можете просто изменить отношение FK модели UserSession, чтобы не было делецией CASCADE. Затем вы будете вести учет входов в систему в таблице UserSession, по-прежнему делая недействительными сами сеансы.
@GavinBallard спасибо! В итоге я использовал что-то похожее на то, что вы сказали. Я сохранил session_key в CharField, а не как ForeignKey, в основном, чтобы избежать исключений целостности БД, поскольку объект сеанса сохраняется только после возврата из представления. Кроме того, таким образом я могу удалить объект сеанса без каскадного эффекта.
Я столкнулся с той же проблемой FK, что и @DanH, что кажется логичным решением. Удаление внешнего ключа кажется недействительным в остальном элегантное решение.
Если я чего-то не упускаю, еще одна проблема с этим подходом заключается в том, что если у вас очень короткие сеансы (скажем, 30 минут или один час), он не поймает тех пользователей, сеансы которых истекли (поскольку у них не было возможности выйти). Короткие сеансы необходимы, если у вас есть веб-сайт, который позволяет пользователю видеть, какие другие пользователи вошли в систему.
@ Роберт: Я не совсем понимаю, что вы имеете в виду. Даже если срок сеанса истек, он все равно будет сопоставлен пользователю через модель UserSession. Если у вас есть что-то для очистки истекших пользовательских сеансов (например, задание cron), тогда да, отображение UserSession также будет удалено - но это обычно желательное поведение. Основная цель этого - удалить все оставшиеся допустимые сеансы для пользователя.
/ admin / дает следующую ошибку ... user_logged_in_handler () принимает ровно 3 аргумента (2 задано) в параметрах, которые я заменил user на CustomUser, поскольку у меня есть расширенные модели пользователей с помощью AbstractUser.
и почему бы не привязать сеанс к пользователю в промежуточном программном обеспечении, а не к запросу, как во встроенной версии?