python-3.x - Django 自定义身份验证后端 - “is_valid”调用身份验证
问题描述
我是第一次做我的自定义身份验证后端,它通常可以工作,但我无法理解一两件事,并且在我看来有一些不需要的行为。
基本上我只是想添加一个额外的必填字段 - 必须在登录时填写(或选择)的域名。
所以我有我的自定义域用户(AbstractUser),它定义了额外的“域名”字段
class DomainUser(AbstractUser):
domainname = models.CharField(max_length=50)
REQUIRED_FIELDS = ['domainname', ]
def __str__(self):
return self.username
@property
def domainusername(self):
return ''.join([self.domainname, '\\', self.username])
然后是观点:
def domain_login(request):
if request.method == 'POST':
login_form = DomainLoginForm(data=request.POST)
if login_form.is_valid():
username = login_form.cleaned_data.get('username')
domainname = login_form.cleaned_data.get('domainname')
raw_password = login_form.cleaned_data.get('password')
user = authenticate(username=username, domainname=domainname, password=raw_password)
if user is not None:
login(request, user)
return HttpResponseRedirect(reverse('admin:index'))
else:
pass # should return invalid logon page
else:
login_form = DomainLoginForm()
context = {
'login_form': login_form,
}
return render(request, 'domain_auth/login.html', context)
和身份验证:
class DomainLDAPBackend(BaseBackend):
def authenticate(self, request, username=None, domainname=None, password=None):
#return DomainUser.objects.get(username__iexact=username, domainname__iexact=domainname)
return DomainUser.objects.get(username__iexact=username)
def get_user(self, user_id):
try:
return DomainUser.objects.get(pk=user_id)
except DomainUser.DoesNotExists:
return None
Auth 现在是一个完整的占位符(它并没有真正验证域的使用,但这只是暂时的),它只是为了在我在这里连接我真正的 auth 模块之前进行测试。
我的登录表单也有域名字段,并且可以正常工作。
我有两个问题:我填写了登录表单并调用了视图:
在我的“domain_login”视图中,似乎“authenticate”被调用了 2 次,第一次是通过“is_valid”方法,而这第一次它在身份验证中将“domainname”参数传递为 None。为什么叫这里?这是正常行为吗?我在文档中找不到类似的东西,但我想我的代码中有错误。在我看来,第二次手动调用是正确的——所有参数都按预期传递,登录也可以。
get_user 方法的目的是什么?它仅在登录后调用(并且在使用管理员时,尤其是浏览用户模型时),而不是在身份验证时调用。
谢谢你。
解决方案
试试这个:
身份验证后端
# Override ModelBackend instead of BaseBackend, so you get various methods out of the box
class DomainLDAPBackend(ModelBackend):
# Don' t set default values for username, domainname and password (so they are manadatory)
def authenticate(self, request, username, domainname, password):
try:
# iexact is used by default
user = DomainUser.objects.get(username=username, domainname=domainname)
# Checks the password
if user.check_password(password):
return user
except ObjectDoesNotExist:
return
如果您希望用户可以在登录更改时选择域名authenticate
(我建议您在注册时请求域名):
def authenticate(self, request, username, domainname, password):
try:
user = DomainUser.objects.get(username=username)
if user.check_password(password):
if user.domainname is None:
user.domainname = domainname
user.save()
return user
except ObjectDoesNotExist:
return
意见
def domain_login(request):
if request.method == 'POST':
login_form = DomainLoginForm(data=request.POST)
# There is not need to call login_form.is_valid: access POST data directly, the validation is performed by DomainLDAPBackend
username = request.POST.get('username')
domainname = request.POST.get('domainname')
raw_password = request.POST.get('password')
user = authenticate(username=username, domainname=domainname, password=raw_password)
if user is not None:
login(request, user)
return HttpResponseRedirect(reverse('admin:index'))
else:
pass # Should return invalid login page
else:
login_form = DomainLoginForm()
return render(request, 'domain_auth/login.html', {'login_form': login_form})
请注意,不需要覆盖ModelBackend.get_user
与您的自定义用户模型不冲突的内容。Django 在内部使用该方法来检索有关登录用户的信息。请记住在您的设置AUTHENTICATION_BACKENDS
中AUTH_USER_MODEL
进行设置。
编辑
我分析了GitHub 上AuthenticationForm
的代码,发现它已经在对用户进行身份验证(这就是您的原始代码调用两次的原因)。首先,您需要覆盖默认情况下不要使用您的自定义后端方法:validate
authenticate
validate
class DomainLoginForm(AuthenticationForm):
def clean(self):
username = self.cleaned_data.get('username')
password = self.cleaned_data.get('password')
domainname = self.cleaned_data.get('domainname')
if username is not None and password is not None and domainname is not None:
self.user_cache = authenticate(self.request, username=username, domainname=domainname, password=password)
if self.user_cache is None:
raise self.get_invalid_login_error()
else:
self.confirm_login_allowed(self.user_cache) # Raises an exception if the user is not active, you can override this method
return self.cleaned_data
然后在你的观点中你可以简单地写:
def domain_login(request):
if request.method == 'POST':
login_form = DomainLoginForm(data=request.POST)
if login_form.is_valid():
# If the form is valid, DomainLoginForm.get_user() never returns None
user = login_form.get_user()
login(request, user)
return HttpResponseRedirect(reverse('admin:index'))
else:
login_form = DomainLoginForm()
return render(request, 'domain_auth/login.html', {'login_form': login_form})
推荐阅读
- visual-studio-code - 从 cmd -> ctrl 映射 Visual Studio MacOS 超级键
- html - JQueryUI 对话框移出浏览器窗口
- discord - 如果频道中没有人超过 5 分钟,如何让我的机器人离开语音频道
- java - 从嵌套的 RecyclerView 获取 onSwipe 事件到父 Fragment
- javascript - 使用 jest 在 vuejs 的 router-link 上测试点击事件
- ios - 我们如何从普通文本数据生成符号崩溃报告?
- flutter - 为什么这个未来函数返回空列表颤动
- reactjs - PayFast 集成到 React 签名错误
- c++ - 如何在 C++11 中用 boost::optional 重写?
- haskell - 计算简单的数学表达式传递作为命令行参数