Бэкенд пользовательской аутентификации Django - "is_valid" вызывает authenticate
Я впервые делаю свой пользовательский бэкенд auth, и в целом он работает, но я не понял пару вещей, и есть некоторое нежелательное, на мой взгляд, поведение.
В принципе, я просто хотел добавить дополнительное обязательное поле - имя домена, которое должно быть заполнено (или выбрано) при входе в систему.
Так, у меня есть пользовательский DomainUser (AbstractUser), который определяет дополнительное поле "domainname"
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.
Моя форма входа также имеет поле domainname, и оно работает.
У меня две проблемы: Я заполняю форму входа, и вызывается представление:
It seems that "authenticate" is called 2 times in my "domain_login" view, first time is by "is_valid" method and this first time it passes "domainname" parameter in authenticate as None. Why is it called here? Is it a normal behaviour? I couldn't find anything like this in the documentation, but i suppose there is an error in my code. Second manual call in my view is correct - all parameters are passed as forseen, login works as well.
What's the purpose of get_user method? It's called only after logging on (and while using admin, especially browsing user model), but not while authenticating.
Спасибо.
Попробуйте это:
Бэкэнд аутентификации
# 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
в ваших настройках.
Редактировать
Я проанализировал код AuthenticationForm
на GitHub и обнаружил, что он уже аутентифицирует пользователя в 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})