尽管为有效的用户名和密码组合调用身份验证和登录,但当移动到另一个 View 时, session 无法链接到用户的用户模型。该模型改为“AnonymousUser”,他们无法进步,因为要求他们是有效的登录用户。
我有一个运行良好的系统已经有一段时间了(我想每个人在遇到问题之前都会这么说......)为了避免必须向每个 View 添加“require_login”装饰器,我制作了一个中间件检查用户是否已登录,如果未登录,应将他们重定向到登录页面。
此设置已经运行了一段时间,但是,最近对用户模型的更改表明,迁移前存在的用户不再能够登录。令人困惑的是,如果他们有自己的密码通过 django 管理员更新,他们可以正常访问网站
认证 View
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语句中我们添加打印语句来打印第一个条件(我知道必须为真...)请求,请求用户模型,请求用户模型is_authenticated。我们从服务器得到以下 react :
显然,该函数已确定用户可以登录并将他们重定向到索引页面,但是,当他们请求索引页面时,他们未被 session 识别为已登录并被重定向回。
这是 Django session token 的图像,当尝试为单个用户登录时,它实际上只更新一行:
为了回答大牛,这里是按顺序排列的中间件列表:
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
]
最佳答案
我建议不要“完全覆盖身份验证过程”......最好是:
- 使用
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
而不是自定义 View 函数。 - 要实现
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
附言以上所有更像是“最佳实践”。如果您只是想让您当前的代码正常工作,那么您可以试试这个:
- 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
-> 这样你就不会错过任何东西关键。
关于Django 请求 session 未与登录用户模型链接,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55733803/