Persisting session variables across login

后端 未结 5 1188
名媛妹妹
名媛妹妹 2020-12-30 16:01

I want to hold information about a users preferences in a session variable. If the user chooses a preference while logged out and then later logs in, I want the preference t

相关标签:
5条回答
  • 2020-12-30 16:23

    When you login/logout Django will flush all sessions if another user logs in (request.session.flush() in auth/init.py).

    You're better of storing user settings in the database and add some middleware to get that data and store it in your request.

    0 讨论(0)
  • 2020-12-30 16:28

    Upon login, Django calls session.flush() or session.cycle_key(), which makes sure nothing from the old session is kept. This is a security measure that protects you against session fixation vulnerabilities. So, when applying this solution, be aware what find of variables you want to persist.

    If you want to keep some state, you'll have to restore that after the login was issued.

    The solution by Chase Seibert was a great start, it was very insecure due to threadsafety issues in that code. You can find an improved version here, which is safe to use:

    from functools import wraps
    
    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
    

    and now you can write:

    from django.contrib.auth import views
    
    @persist_session_vars(['some_field'])
    def login(request, *args, **kwargs):
        return views.login(request, *args, **kwargs)
    

    And for class based views (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
    

    and use that view in the url patterns:

    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)),
    ]
    
    0 讨论(0)
  • 2020-12-30 16:31

    I actually think your initial design made sense. If you want to save some session variables across the login/logout boundary, you can do something like this.

    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
    

    You would throw this decorator on whatever view you're calling auth.login/logout from. If you're delegating to the built-in view for those, you can easily wrap them.

    from django.contrib.auth import views
    
    @persist_session_vars(['HTTP_REFERER'])
    def login(request, *args, **kwargs):
        return views.login(request, *args, **kwargs)
    
    0 讨论(0)
  • 2020-12-30 16:33

    You can create a form Mixin that allows you to persist form values to the user's session (which doesn't require them being logged in). This is useful for things such as filter/sorting options on a public table-view report, where you want to keep their filter options persistent across refreshes.

    View:

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

    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)
    
    0 讨论(0)
  • 2020-12-30 16:39

    user data that persists sounds like it should live in something like a UserProfile model

    0 讨论(0)
提交回复
热议问题