在Django身份验证期间单独验证用户名和密码

时间:2009-10-11 00:11:56

标签: python django security authentication

在django中使用标准身份验证模块时,失败的用户身份验证不明确。也就是说,似乎无法区分以下两种情况:

  • 用户名有效,密码无效
  • 用户名无效

我想我想在这两种情况下向用户显示相应的消息,而不是单个“用户名或密码无效......”。

任何人都有使用简单方法的经验。问题的症结似乎是在最低级别 - 在django.contrib.auth.backends.ModelBackend类中。此类的authenticate()方法(以用户名和密码作为参数)仅在身份验证成功时返回User对象,如果身份验证失败则返回None。鉴于此代码处于最低级别(好,最低级别高于数据库代码),绕过它似乎很多代码被抛弃了。

最简单的方法是实现新的身份验证后端并将其添加到AUTHENTICATION_BACKENDS设置中吗?可以实现返回(User,Bool)元组的后端,其中如果用户名不存在,则User对象仅为None,如果密码正确,则Bool仅为True。但是,这会破坏后端与django.contrib.auth.authenticate()方法的约定(documented在成功验证时返回User对象,否则为None)。

也许,这一切都是无所顾忌的?无论用户名或密码是否不正确,用户可能无论如何都必须转到“忘记密码”页面,所以这可能都是学术性的。我只是忍不住感觉......

编辑:

关于我选择的答案的评论: 我选择的答案是实现此功能的方法。下面还有另一个答案,讨论了这样做的潜在安全隐患,我也将其视为提名的答案。但是,我提名的答案解释了 如何实现此功能。基于安全性的答案讨论了一个是否应实现此功能,这实际上是一个不同的问题。

5 个答案:

答案 0 :(得分:19)

你真的不想区分这两种情况。否则,您将为潜在的黑客提供关于用户名是否有效的线索 - 这对获取欺诈性登录有重大帮助。

答案 1 :(得分:2)

这不仅仅是后端的功能,只是身份验证表单。只需重写表单即可显示每个字段所需的错误。编写一个使用新表单的登录视图,并将其设置为默认登录URL。 (实际上我刚刚在最近的Django提交中看到,您现在可以将自定义表单传递给登录视图,因此这更容易实现)。这应该花费大约5分钟的努力。你需要的一切都在django.contrib.auth。

这里澄清的是目前的表格:

class AuthenticationForm(forms.Form):
    """
    Base class for authenticating users. Extend this to get a form that accepts
    username/password logins.
    """
    username = forms.CharField(label=_("Username"), max_length=30)
    password = forms.CharField(label=_("Password"), widget=forms.PasswordInput)

    def __init__(self, request=None, *args, **kwargs):
        """
        If request is passed in, the form will validate that cookies are
        enabled. Note that the request (a HttpRequest object) must have set a
        cookie with the key TEST_COOKIE_NAME and value TEST_COOKIE_VALUE before
        running this validation.
        """
        self.request = request
        self.user_cache = None
        super(AuthenticationForm, self).__init__(*args, **kwargs)

    def clean(self):
        username = self.cleaned_data.get('username')
        password = self.cleaned_data.get('password')

        if username and password:
            self.user_cache = authenticate(username=username, password=password)
            if self.user_cache is None:
                raise forms.ValidationError(_("Please enter a correct username and password. Note that both fields are case-sensitive."))
            elif not self.user_cache.is_active:
                raise forms.ValidationError(_("This account is inactive."))

        # TODO: determine whether this should move to its own method.
        if self.request:
            if not self.request.session.test_cookie_worked():
                raise forms.ValidationError(_("Your Web browser doesn't appear to have cookies enabled. Cookies are required for logging in."))

        return self.cleaned_data

    def get_user_id(self):
        if self.user_cache:
            return self.user_cache.id
        return None

    def get_user(self):
        return self.user_cache

添加:

def clean_username(self):
    username = self.cleaned_data['username']
    try:
        User.objects.get(username=username)
    except User.DoesNotExist:
        raise forms.ValidationError("The username you have entered does not exist.")
    return username

答案 2 :(得分:0)

我们必须在使用外部会员订阅服务的网站上处理此问题。基本上你做了

from django.contrib.auth.models import User

try:
    user = User.objects.get(username=whatever)
    # if you get here the username exists and you can do a normal authentication
except:
    pass # no such username

在我们的例子中,如果用户名不存在,那么我们必须检查由外部站点的Perl脚本更新的HTPASSWD文件。如果文件中存在名称,那么我们将创建用户,设置密码,然后进行身份验证。

答案 3 :(得分:0)

这个答案并不是特定于Django,但这是我用来实现这个目的的伪代码:

//Query if user exists who's username=<username> and password=<password>

//If true
    //successful login!

//If false
    //Query if user exists who's username=<username>
        //If true
            //This means the user typed in the wrong password
        //If false
            //This means the user typed in the wrong username

答案 4 :(得分:0)

def clean_username(self):
    """
    Verifies that the username is available.
    """
    username = self.cleaned_data["username"]
    try:
        user = User.objects.get(username=username)
    except User.DoesNotExist:
        return username
    else:
        raise forms.ValidationError(u"""\
                This username is already registered, 
                please choose another one.\
                """)