20

默认情况下,Django 将用户名实现为区分大小写,现在为了进行身份验证,我编写了自己的Authentication Backend来处理身份验证时不区分大小写的用户名。

如图: http: //blog.shopfiber.com/ ?p=220

现在,问题是:

我有各种观点和实用方法,与username一些刺痛相比。

IE

request.user.username == username_from_some_other_system_as_str

现在,如果用户名是yugal那么:

request.user.username == 'Yugal' # Returns False

现在,它应该返回True[我想要实现的目标]

为此,我记得从C++几天开始,Operator Overloading。但我认为简单地为 django 这样做并不是auth user一个好主意,因为auth user它与django. 此外,重载==将使整个类不区分大小写,而不仅仅是username字段。

所以,即使在整个比较时,我应该如何处理这种username不区分大小写的问题。

笔记:

  • 创建一个get_username总是返回小写用户名的方法是不可能的,因为它需要重构所有代码才能使用它。您可以一次性为您的代码执行此操作,但如果您使用的是 3rd 方 django 应用程序,则不可能。

  • 我知道这user.username.lower() = something.lower()是可能的,但容易出错,而不是多开发人员设置中经常使用的东西的编写解决方案。

  • 我用过SomeModel.objects.filter(username__iexact=username),只要有可能。但这仍然使系统容易受到任何不知情的开发人员的错误影响。

=======================================

从概念上想出解决方案,但无法使其工作(帮助):

####### Custom CharField for username case-insensitivity #######
from django.db.models.fields import CharField
class iUnicode:
    def __init__(self, value):
        self.value = value

    def __eq__(self, other):
        if isinstance(other, str) or isinstance(other, unicode):
            return self.value.lower() == other.lower()
        if isinstance(other, self.__class__):
            return other == self.value

    def __unicode__(self):
        return unicode(self.value)
    def __str__(self):
        return self.__unicode__()


class UsernameCharField(CharField):
    def to_python(self, value):  # Its not getting called
        unicode_val = super(CharField, self).to_python(value)
        return iUnicode(unicode_val)

if User._meta.local_fields[1].name == 'username':
    User._meta.local_fields[1] = UsernameCharField(max_length=30)
    User._meta.local_fields[1].model = User
################################################################

我假设to_python用于将从数据库接收到的值转换为unicodepython 中的值。但是,我想我to_python的没有被调用。

这也将确保第 3 方应用程序不区分大小写,并且不需要任何重构。它将修补User其核心。我将把这个添加到__init__.py我的第一个INSTALLED_APP

我究竟做错了什么 ?

4

7 回答 7

36

从 Django 1.5 开始,使用户名不区分大小写很简单:

class MyUserManager(BaseUserManager):
    def get_by_natural_key(self, username):
        return self.get(username__iexact=username)

来源1、2 _

于 2015-10-31T20:29:34.770 回答
6

我在注册和登录过程中修改了几行似乎对我有用的行。使用我的解决方案,用户名仍将显示为用户在注册时编写的用户名,但它不允许其他人使用以不同方式编写的相同用户名。它还允许用户登录而不必担心编写区分大小写的用户名。

我修改了注册表以搜索不区分大小写的用户名。

这是我验证用户名的行,它使用此用户名搜索用户。

User._default_manager.get(username__iexact=username)

然后我需要允许用户使用不区分大小写的用户名登录。

从我的登录视图:

username = request.POST['username']
password = request.POST['password']
caseSensitiveUsername = username
try:
  findUser = User._default_manager.get(username__iexact=username)
except User.DoesNotExist:
  findUser = None
if findUser is not None:
  caseSensitiveUsername = findUser.get_username
user = auth.authenticate(username=caseSensitiveUsername, password=password)
于 2015-02-22T14:03:42.813 回答
4

终于明白了:

经过如此多的实验和对模型的最小影响User,终于实现了。[感谢@freakish 先生的不同想法]

这里是 :

############ username case-insensitivity ############
class iunicode(unicode):
    def __init__(self, value):
        super(iunicode, self).__init__(value)
        self.value = value

    def __eq__(self, other):
        if isinstance(other, str) or isinstance(other, unicode):
            return self.value.lower() == other.lower()
        if isinstance(other, self.__class__):
            return other == self.value


def custom_getattribute(self, name):
    val = object.__getattribute__(self, name)
    if name == "username":
        val = iunicode(val)
    return val

def auth_user_save(self, *args, **kwargs): # Ensures lowercase usernames
    username = self.username
    if username and type(username) in [unicode, str, iunicode]:
        self.username = username.lower()   # Only lower case allowed
    super(User, self).save(*args, **kwargs)

User.__getattribute__ = custom_getattribute
User.save = MethodType(auth_user_save, None, User)
#####################################################

我测试了它,它按预期工作。:D

所以,这里是测试用例

from django.test.testcases import TestCase

def create_user(data='testuser'):
    email = '%s@%s.com' % (data, data)
    user = G(User, username=data, email=email, is_active=True)
    user.set_password(data)
    user.save()
    return user

class UsernameCaseInsensitiveTests(TestCase):

    def test_user_create(self):
        testuser = 'testuser'
        user = create_user(testuser)
        # Lowercase
        self.assertEqual(testuser, user.username)
        # Uppercase
        user.username = testuser.upper()
        user.save()
        self.assertEqual(testuser, user.username)

def test_username_eq(self):
    testuser = 'testuser'
    user = create_user(testuser)
    self.assertTrue(isinstance(user.username, iunicode))
    self.assertEqual(user.username, testuser)
    self.assertEqual(user.username, testuser.upper())
    self.assertTrue(user.username == testuser.upper())
    self.assertTrue(testuser.upper() == user.username)
    self.assertTrue(user.username == iunicode(testuser.upper()))
数据库的隐式不区分大小写查询
###################### QuerySet #############################
def _filter_or_exclude(self, negate, *args, **kwargs):
    if 'username' in kwargs:
        kwargs['username__iexact'] = kwargs['username']
        del kwargs['username']
    if args or kwargs:
        assert self.query.can_filter(),\
        "Cannot filter a query once a slice has been taken."
    from django.db.models import Q
    clone = self._clone()
    if negate:
        clone.query.add_q(~Q(*args, **kwargs))
    else:
        clone.query.add_q(Q(*args, **kwargs))
    return clone

from django.db.models.query import QuerySet
QuerySet._filter_or_exclude = _filter_or_exclude
#############################################################

这将允许User.objects.get(username='yugal')User.objects.get(username='YUGAl')产生相同的用户。

于 2012-11-03T09:21:56.137 回答
2

使用不区分大小写的用户名的最简单方法是从默认ModelBackend和覆盖authenticate方法继承。

请注意,在except我们正在执行的块内UserModel().set_password(password),这样做会减少哈希器的工作时间。修复了错误报告

from django.contrib.auth.backends import ModelBackend
from django.contrib.auth import get_user_model

from users.models import User

class CaseInsensitiveModelBackend(ModelBackend):
    def authenticate(self, username=None, password=None, **kwargs):
        UserModel = get_user_model()
        if username is None:
            username = kwargs.get(UserModel.USERNAME_FIELD)
        try:
            d = {'%s__iexact'%UserModel.USERNAME_FIELD: username}
            user = UserModel.objects.get(**d)
            if user.check_password(password):
                return user
        except UserModel.DoesNotExist:
            # Run the default password hasher once to reduce the timing
            # difference between an existing and a non-existing user (#20760).
            UserModel().set_password(password)

        return None

并将此后端添加到AUTHENTICATION_BACKENDSsettings.py

AUTHENTICATION_BACKENDS = (
    'sdcpy.backends.CaseInsensitiveModelBackend', # inherits from 'django.contrib.auth.backends.ModelBackend'
)
于 2015-02-02T13:59:26.347 回答
0

这个猴子补丁看起来是个坏主意。将来你肯定会遇到一些问题(Django 在幕后做了很多事情)。我强烈建议重新设计您的应用程序。

但是,您可以尝试以下方法(使用您的iUnicode课程):

def new_getattribute( self, name ):
    val = object.__getattribute__( self, name )
    if name == "username":
        val = iUnicode( val )
    return val

User.__getattribute__ = new_getattr

现在,我不是 100% 认为这会起作用,而且它有点 hacky,所以请谨慎使用。:)

于 2012-11-02T15:55:06.630 回答
0

有一种相对干净的方法可以做到这一点:

# Case-insensitive django authentication, modified from
# http://justcramer.com/2008/08/23/logging-in-with-email-addresses-in-django/
# See also https://github.com/dabapps/django-email-as-username
# And https://docs.djangoproject.com/en/dev/topics/auth/customizing/#auth-custom-user
from django.contrib.auth.models     import User

class EmailOrUsernameModelBackend(object):
    def authenticate(self, username=None, password=None):
        username = username.lower()     # Force all usernames & email to all lower case
        if '@' in username:
            kwargs = {'email': username}
        else:
            kwargs = {'username': username}
        try:
            user = User.objects.get(**kwargs)
            if user.check_password(password):
                return user
        except User.DoesNotExist:
            return None

    def get_user(self, user_id):
        try:
            return User.objects.get(pk=user_id)
        except User.DoesNotExist:
            return None

def my_password_reset(request, **kwargs):
    # Override django.contrib.auth.views.password_reset not needed because django does
    # SELECT FROM "auth_user" WHERE UPPER("auth_user"."email"::text) = UPPER(E'xxx@emaple.com')
    # But note you may want to manually create an UPPER index in the database for speed.
    return password_reset(request, **kwargs)

然后设置

AUTHENTICATION_BACKENDS = (
    'obviously.backends.EmailOrUsernameModelBackend',
    'django.contrib.auth.backends.ModelBackend',
)

您还必须在注册工作流程中强制用户名小写

这一切正常,但不保留用户给出的大小写,在数据库中查找也没有效率。默认的 django 行为是设计使然,请参阅https://code.djangoproject.com/ticket/2273

于 2013-09-11T02:39:38.927 回答
0

使用UserManager是实现不区分大小写用户名的最简单方法之一,而不会弄乱其他东西。

示例代码(Models.py):

from django.contrib.auth.models import AbstractUser, UserManager

class CustomUserManager(UserManager):
    def get_by_natural_key(self, username):
        case_insensitive_username_field = '{}__iexact'.format(self.model.USERNAME_FIELD)
        return self.get(**{case_insensitive_username_field: username})

class CustomUser(AbstractUser):
    objects = CustomUserManager()

你很高兴去!来源

于 2020-09-19T10:22:45.537 回答