Django request Session не связывается с авторизованной моделью - PullRequest
2 голосов
/ 17 апреля 2019

Несмотря на вызов функции authenticate и login для ввода правильной комбинации имени пользователя и пароля, при переходе в другое представление сеанс не может связаться с моделью пользователя.Вместо этого модель является «AnonymousUser», и они не могут прогрессировать, потому что требуется, чтобы они были действительным пользователем входа в систему.

У меня была система, которая работала хорошо в течение достаточно долгого времени (как я полагаю, все говорят, прежде чем у них возникнут проблемы ...) Чтобы избежать необходимости добавлять декоратор require_login к каждому представлению, у меня естьсоздал промежуточное программное обеспечение, которое проверяет, что пользователь является логином, и должно перенаправить его, если это не так, на страницу входа в систему.

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

вид аутентификации

import django.http
from django.shortcuts import render, redirect
from django.contrib.auth import authenticate, login, logout, update_session_auth_hash, models as admin_models

from django.conf import settings

from .models import LoginAttempts

import logging
logging.basicConfig(level=logging.DEBUG)
log = logging.getLogger(__name__)

# Define the alert to be given to the users if they have passed invalid user credentials
INVALID_LOGIN = {
    ...
}

LOCKED_ACCOUNT = {
    ...
}

def login_form(request):
    """ Return the login page """

    if request.POST: # Handle the form submission

        # Extract the user information + attempt to authenticate the user
        username, password = request.POST.get("username"), request.POST.get("password")
        log.debug("user '{}' attempting to log in".format(username))

        # Collect the user account corresponding to the username passed
        accountQuery = admin_models.User.objects.filter(username=username)
        if accountQuery.exists() and password is not None:
            userModel = accountQuery.first()
            if userModel.loginattempts.isLocked:
                log.debug("'{}'s' account has been locked due to repeated failed attempts".format(username))
                request.session["alerts"].append(LOCKED_ACCOUNT)
                return render(request, "login.html")
        else:
            log.debug("'{}'s username doesn't exist or no password provided".format(username))
            request.session["alerts"].append(INVALID_LOGIN)
            return render(request, "login.html")

        # Authenticate the user/password combination
        user = authenticate(request, username=username, password=password)

        if user is not None:  # The user has been authenticated, log them in and redirect to the index page
            log.debug("User {} has been verified - logging user in".format(username))
            login(request, user)
            userModel.loginattempts.attempts = 0
            userModel.save()
            return django.http.HttpResponseRedirect("/")
        else:
            log.debug("User {} failed to authenticate".format(username))
            request.session["alerts"].append(INVALID_LOGIN)
            userModel.loginattempts.attempts += 1
            if userModel.loginattempts.attempts >= 10: userModel.loginattempts.isLocked = True
            userModel.save()

    return render(request, "login.html")

Промежуточное программное обеспечение

class RequireLogin:
    """ Require that the user be logging in to view the pages - avoiding the requirement
    to declare "@login_required" on all views
    """

    def __init__(self, get_response: callable):
        self.get_response = get_response  # Function passes the request through and fulfils and collects the generates response

    def __call__(self, request):
        if request.path_info != settings.LOGIN_URL and not request.user.is_authenticated:
            return HttpResponseRedirect(settings.LOGIN_URL)

        return self.get_response(request)

Журналы

Если в операторе if промежуточного программного обеспечения мы добавляем операторы print для печати первого условия (которое, я знаю, должно быть истинным...) запрос, пользовательская модель запроса, пользовательская модель запроса is_authenticated.мы получаем следующую реакцию от сервера:

terminal

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

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

database

Чтобы ответить Даниэлю, вот список промежуточного программного обеспечения в их порядке:

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
    "ace_main.middleware.RequireLogin",
    "ace_main.middleware.RestrictedAccessZones",  # For certain areas - extra restriction - don't worry about this
    "ace_main.middleware.RemoveAlerts",  # Alert structure dequeue alerts already shown to the user
    "ace_main.middleware.UserLogging",  # track the user around the site 
]

1 Ответ

1 голос
/ 18 апреля 2019

Я бы рекомендовал не "полностью переопределять процесс аутентификации" ... Лучше:

  • использовать django.contrib.auth.forms.AuthenticationForm (или пользовательскую форму, которая его наследует)
  • на ваш взгляд просто используйте if AuthenticationForm(data=self.request.POST, files=self.request.FILES).is_valid(): return django.http.HttpResponseRedirect(...). Или вы можете напрямую использовать django.contrib.auth.views.LoginView вместо пользовательской функции просмотра.
  • Для реализации isLocked проверки, отслеживания loginAttempts и т. Д .: создайте (пользовательский бэкэнд аутентификации) [https://docs.djangoproject.com/en/2.2/topics/auth/customizing/#writing-an-authentication-backend]:

from django.contrib import messages
from django.contrib.auth.backends import ModelBackend


class SophisticatedModelBackend(ModelBackend):
    def authenticate(self, request, username=None, password=None, **kwargs):
        user = super(self, SophisticatedModelBackend).authenticate(request, username=username, password=password, **kwargs)
        if user:
            user = self.check_user_locked(request, user, **kwargs)
        else:
            messages.add_message(request, messages.ERROR, INVALID_LOGIN)
        user = self.postprocess_login_attempt(request, user, username, **kwargs)
        return user

    def check_user_locked(self, request, user, **kwargs):
        if user.loginattempts.isLocked:
            # I'd also recommend to also use django.contrib.messages instead of changing request.session
            messages.add_message(request, messages.ERROR, LOCKED_ACCOUNT)
            return None
        return user

    def postprocess_login_attempt(self, request, user, username, **kwargs):
        if user:
            user.loginattempts.attempts = 0
            user.save()
        else:
            userModel = admin_models.User.objects.filter(username=username).first()
            if userModel:
                userModel.loginattempts.attempts += 1
                if userModel.loginattempts.attempts >= 10: userModel.loginattempts.isLocked = True
                userModel.save()

        return user

  • Не забудьте установить или обновить settings.AUTHENTICATION_BACKENDS

P.S. Все вышеперечисленное больше похоже на «лучшую практику». Если вы просто хотите, чтобы ваш текущий код работал, то вы можете попробовать это:

 -            login(request, user)
 -            login(request, user, 'django.contrib.auth.backends.ModelBackend')

Почему это должно работать: AuthenticationMiddleware звонки auth.get_user, что делает:

    try:
        user_id = _get_user_session_key(request)
        backend_path = request.session[BACKEND_SESSION_KEY]
    except KeyError:
        pass

И request.session[BACKEND_SESSION_KEY] устанавливается во время auth.login на третий аргумент, который в вашем случае равен None. Все это можно увидеть только в исходном коде django, поэтому лучше использовать значение по умолчанию auth.LoginView или auth.AuthenticationForm ->, чтобы не пропустить ничего критического.

...