跨登录持久化会话变量

时间:2011-11-24 11:50:29

标签: django

我想在会话变量中保存有关用户首选项的信息。如果用户在注销时选择首选项然后再登录,我希望保留首选项而无需重新选择它。

Django会话在cookie中维护会话密钥以跟踪用户会话。我理解它的方式,当用户登录时,该密钥会被更改。

a)这是否意味着所有会话变量在登录时被删除或者是否存在任何类型的逾越节

b)如果无法通过登录保存首选项,手动设置Cookie是最佳方式吗?我想象一个场景:

  • 注销时,保持cookie中的首选项
  • 登录时,将首选项复制到会话变量并写入db(通过信号?)
  • 注销时,使用首选项更新cookie(通过信号?)

更新

我设法通过在用户的个人资料对象以及cookie中保存首选项来获得此功能(这些首选项在任何方面都不敏感)。当用户登录时,他们的配置文件设置优先。未登录时,会选择Cookie首选项

5 个答案:

答案 0 :(得分:8)

登录后,Django会调用session.flush()session.cycle_key(),这样可以确保保留旧会话中的任何内容。这是一项保护您免受会话固定漏洞影响的安全措施。因此,在应用此解决方案时,请注意要保留的变量的找到位置。

你想保留一些状态,你必须在登录后恢复它。

Chase Seibert的解决方案是一个很好的开始,由于该代码中的线程安全问题,它非常不安全。你可以在这里找到一个可以安全使用的改进版本:

class persist_session_vars(object):
    """
    Some views, such as login and logout, will reset all session state.
    (via a call to ``request.session.cycle_key()`` or ``session.flush()``).
    That is a security measure to mitigate session fixation vulnerabilities.

    By applying this decorator, some values are retained.
    Be very aware what find of variables you want to persist.
    """

    def __init__(self, vars):
        self.vars = vars

    def __call__(self, view_func):

        @wraps(view_func)
        def inner(request, *args, **kwargs):
            # Backup first
            session_backup = {}
            for var in self.vars:
                try:
                    session_backup[var] = request.session[var]
                except KeyError:
                    pass

            # Call the original view
            response = view_func(request, *args, **kwargs)

            # Restore variables in the new session
            for var, value in session_backup.items():
                request.session[var] = value

            return response

        return inner

现在你可以写:

from django.contrib.auth import views

@persist_session_vars(['some_field'])
def login(request, *args, **kwargs):
    return views.login(request, *args, **kwargs)

对于基于类的视图(django-allauth):

import allauth.account.views as auth_views
from django.utils.decorators import method_decorator

@method_decorator(persist_session_vars(['some_field']), name='dispatch')
class LoginView(auth_views.LoginView):
    pass

并在网址模式中使用该视图:

import allauth.urls
from django.conf.urls import include, url

from . import views

urlpatterns = [
    # Views that overlap the default:
    url(r'^login/$', views.LoginView.as_view(), name='account_login'),

    # default allauth urls
    url(r'', include(allauth.urls)),
]

答案 1 :(得分:2)

当你登录/注销时,如果另一个用户登录(在auth / init .py中的request.session.flush()),Django将刷新所有会话。

您最好将用户设置存储在数据库中,并添加一些中间件来获取该数据并将其存储在您的请求中。

答案 2 :(得分:1)

持续存在的用户数据听起来应该像UserProfile模型

那样存在

答案 3 :(得分:0)

我实际上认为你的初始设计是有道理的。如果要在登录/注销边界中保存一些会话变量,可以执行以下操作。

from functools import wraps

class persist_session_vars(object):
    """ Some views, such as login and logout, will reset all session state.
    However, we occasionally want to persist some of those session variables.
    """

    session_backup = {}

    def __init__(self, vars):
        self.vars = vars

    def __enter__(self):
        for var in self.vars:
            self.session_backup[var] = self.request.session.get(var)

    def __exit__(self, exc_type, exc_value, traceback):
        for var in self.vars:
            self.request.session[var] = self.session_backup.get(var)

    def __call__(self, test_func, *args, **kwargs):

        @wraps(test_func)
        def inner(*args, **kwargs):
            if not args:
                raise Exception('Must decorate a view, ie a function taking request as the first parameter')
            self.request = args[0]
            with self:
                return test_func(*args, **kwargs)

        return inner

你会把这个装饰器扔到你正在调用auth.login / logout的任何视图上。如果您正在委托内置视图,那么您可以轻松地将它们包装起来。

from django.contrib.auth import views

@persist_session_vars(['HTTP_REFERER'])
def login(request, *args, **kwargs):
    return views.login(request, *args, **kwargs)

答案 4 :(得分:0)

您可以创建一个表单Mixin,它允许您将表单值保存到用户的会话中(不要求他们登录)。这对于诸如公共表视图报告上的过滤器/排序选项之类的内容非常有用,您希望在其中保持其过滤器选项在刷新期间保持不变。

filter form screenshot

查看:

def list_states(request):
    if request.method == 'GET':
        form = StateListFilterForm().load_from_session(request.session)
    elif request.method == 'POST':
        form = StateListFilterForm(request.POST)
        form.persist_to_session()
    return render('forms/state_list.html', RequestContext(request, {'state_form': form})

形式:

class PersistableMixin:
    def persist_to_session(form, session):
        for key in form.fields.keys():
            val = getattr(form, 'cleaned_data', form.data).get(key, None)
            if val:  # will not store empty str values
                session[key] = val
        return True

    def load_from_session(form, session):
        for key in form.fields.keys():
            saved_val = session.get(key, '')
            if saved_val:  # will not load empty str values
                form.fields[key].initial = saved_val
        return form


class StateListFilterForm(forms.Form, PersistableMixin):
    states = forms.MultipleChoiceField(required=False, choices=US_STATES)