2

我需要在登录尝试失败时显示一条错误消息,说明用户是否处于非活动状态、密码不正确或未找到用户。我正在针对 API 进行身份验证,因此我创建了一个自定义身份验证后端。目前,为了证明客户端登录正常,我让它引发了异常,称为 UserNotFoundError、InactiveUserError 和 InvalidPasswordError。

我很确定这不是正确的方法,如果我想使用 Django 的身份验证视图,我无论如何也无法捕捉到这些异常。

我假设我需要创建自己的身份验证视图。但是,有没有更好的方法?

(我知道给出登录失败的原因并不安全,但我必须这样做。)

4

2 回答 2

3

这是完全未经测试的,也不能保证工作,但你可以尝试这些方面的东西。此解决方案的唯一警告是您的后端必须是最后使用的身份验证后端(或唯一使用的)。

contrib.auth.views.authenticate 方法接受 authentication_form 的参数,这意味着您应该能够使用您自己的扩展 contrib.auth.forms.AuthenticationForm 的表单来覆盖它。这种形式就是调用后端的 authenticate() 方法。因此,在您的后端,您可以返回一些自定义对象,其中包含用户实例以及身份验证失败的“失败”状态和“失败原因”。

如果您的自定义对象处于失败状态,您可以抛出带有异常的 ValidationError,此时登录视图不会对用户进行任何进一步的处理。

还要确保覆盖 form.get_user() 以便它返回实际的 User 实例,而不是您的自定义对象。

于 2012-07-14T02:02:51.727 回答
0

或者只是覆盖 login(),如下所示:

from django.conf import settings
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.contrib.auth.views import logout
from django.http import HttpResponseRedirect
from django.template.response import TemplateResponse
from django.utils.translation import ugettext as _
from django.views.decorators.debug import sensitive_post_parameters
from django.views.decorators.cache import never_cache
from django.views.decorators.csrf import csrf_protect

# Avoid shadowing the login() and logout() views below.
from django.contrib.auth import REDIRECT_FIELD_NAME, login as auth_login
from django.contrib.auth.forms import AuthenticationForm
from django.contrib.sites.models import get_current_site

# App specific
from axes.models import AccessAttempt


@sensitive_post_parameters()
@csrf_protect
@never_cache
def login(request, template_name='registration/login.html',
          redirect_field_name=REDIRECT_FIELD_NAME,
          authentication_form=AuthenticationForm,
          current_app=None, extra_context=None,
          form_name='form'):
    """
    Displays the login form and handles the login action.
    """
    redirect_to = request.REQUEST.get(redirect_field_name, '')

    if request.method == "POST":
        form = authentication_form(data=request.POST)
        if form.is_valid():
            netloc = urlparse.urlparse(redirect_to)[1]

            # Use default setting if redirect_to is empty
            if not redirect_to:
                redirect_to = settings.LOGIN_REDIRECT_URL

            # Heavier security check -- don't allow redirection to a different
            # host.
            elif netloc and netloc != request.get_host():
                redirect_to = settings.LOGIN_REDIRECT_URL

            # Okay, security checks complete. Log the user in.
            auth_login(request, form.get_user())

            if request.session.test_cookie_worked():
                request.session.delete_test_cookie()

            messages.add_message(request, messages.SUCCESS, _("Welcome <strong>%s</strong>.") % [request.user.get_full_name() if request.user.get_full_name() is not u"" else request.user.username].pop())

            return HttpResponseRedirect(redirect_to)
        else:
            if extra_context is None:
                extra_context = {}
            extra_context.update({'login_form_errors': True})
            try:
                attempt = AccessAttempt.objects.get(ip_address=request.META.get('REMOTE_ADDR', ''))
                if attempt.failures_since_start < 2:
                    messages.add_message(request, messages.ERROR, _(u"Invalid credentials, last chance."))
                if attempt.failures_since_start == 2:
                    messages.add_message(request, messages.ERROR, _(u"Authentication has been blocked for a 24h cooldown period."))
            except AccessAttempt.DoesNotExist:
                messages.add_message(request, messages.ERROR, _(u"Invalid credentials, please try again."))
    else:
        form = authentication_form(request)

    request.session.set_test_cookie()

    current_site = get_current_site(request)

    context = {
        form_name: form,
        redirect_field_name: redirect_to,
        'site': current_site,
        'site_name': current_site.name,
        }
    if extra_context is not None:
        context.update(extra_context)
    return TemplateResponse(request, template_name, context,
        current_app=current_app)

笔记:

  • 这个例子使用 django-axes
  • 出于明显的安全原因,向用户提供有关错误凭据的特定错误是一种不好的做法
于 2012-07-14T14:15:10.023 回答