代码之家  ›  专栏  ›  技术社区  ›  Steg

在Django身份验证期间分别验证用户名和密码

  •  5
  • Steg  · 技术社区  · 15 年前

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

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

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

    任何人都有简单方法的经验。问题的症结似乎直接转到了最底层——django.contrib.auth.backends.modelbackend类中。此类的authenticate()方法以用户名和密码作为参数,只返回用户对象(如果验证成功),如果验证失败,则返回none。考虑到这段代码处于最低的级别(好吧,在数据库代码之上的最低级别),绕过它似乎有很多代码被丢弃了。

    最好的方法是简单地实现一个新的认证后端并将其添加到认证后端设置中吗?可以实现一个返回(user,bool)元组的后端,其中只有在用户名不存在时,user对象才为none,只有在密码正确时,bool才为true。但是,这将破坏后端与django.contrib.auth.authenticate()方法(即 documented 在成功身份验证时返回用户对象,否则不返回任何对象)。

    也许,这都是无谓的担心?不管用户名或密码是否不正确,用户可能无论如何都要去“丢失密码”页面,所以这可能是学术性的。不过,我就是情不自禁地感觉到…

    编辑:

    关于我选择的答案的评论: 我选择的答案是实现此功能的方法。下面还有另一个答案,讨论了这样做的潜在安全影响,我也将其视为指定的答案。然而,我提名的答案解释了 怎样 可以实现此功能。基于安全性的答案讨论了 应该 实现这个特性,这实际上是一个不同的问题。

    5 回复  |  直到 14 年前
        1
  •  2
  •   Jason Christa    15 年前

    这不是后端的一个功能,而只是验证表单。只需重写表单以显示每个字段所需的错误。编写一个使用新表单的登录视图,并使其成为默认的登录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
  •  19
  •   Daniel Roseman    15 年前

    你真的不想区分这两种情况。否则,你就给潜在的黑客一个关于用户名是否有效的线索——这对于获得一个欺诈性的登录是一个很大的帮助。

        3
  •  0
  •   Peter Rowell    15 年前

    我们必须在一个使用外部会员订阅服务的网站上处理这个问题。基本上是这样的

    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
    

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

        4
  •  0
  •   Jake Wilson    15 年前

    这个答案并不特定于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
    
        5
  •  0
  •   Alex    14 年前
    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.\
                    """)