Django 哈希与遗留数据库集成
Django hash integrate with legacy database
我在 mysql 遗留数据库上使用 django。我已经集成了除密码以外的所有内容。遗留数据库以这种方式存储密码
a$Pdg3h8AVZ6Vl3X1mMKgQDuMriv8iysnValEa5YZO3j9pEboLrOBUK
并且 django 仅在相同的散列具有 bcrypt$
前缀 bcrypt$a$Pdg3h8AVZ6Vl3X1mMKgQDuMriv8iysnValEa5YZO3j9pEboLrOBUK
时才读取。如何让 Django 通过读取第一个示例哈希来验证用户?
为什么 django 在密码中添加前缀?
更新
我添加了模型后端
from django.contrib.auth.backends import ModelBackend
from users.models import TbUser
from django.contrib.auth import get_user_model
from django.contrib.auth.models import Permission
from django.db.models import Exists, OuterRef, Q
UserModel = get_user_model()
class BaseBackend:
def authenticate(self, request, **kwargs):
return None
def get_user(self, user_id):
return None
def get_user_permissions(self, user_obj, obj=None):
return set()
def get_group_permissions(self, user_obj, obj=None):
return set()
def get_all_permissions(self, user_obj, obj=None):
return {
*self.get_user_permissions(user_obj, obj=obj),
*self.get_group_permissions(user_obj, obj=obj),
}
def has_perm(self, user_obj, perm, obj=None):
return perm in self.get_all_permissions(user_obj, obj=obj)
class ModelBackend(BaseBackend):
"""
Authenticates against settings.AUTH_USER_MODEL.
"""
def authenticate(self, request, username=None, password=None, **kwargs):
print(">>>>>>>>>>>>>>>>>> Authentication start")
print("User: ", username, " Password: ", password)
if username is None:
username = kwargs.get(UserModel.USERNAME_FIELD)
if username is None or password is None:
return
try:
user = UserModel._default_manager.get_by_natural_key(username)
except UserModel.DoesNotExist:
# Run the default password hasher once to reduce the timing
# difference between an existing and a nonexistent user (#20760).
UserModel().set_password(password)
else:
if user.password.startswith('bcrypt'):
u = user
else:
u = UserModel(password=f'bcrypt${user.password}')
print(">>>>>>>>>>>>>>>>>> Authentication prefix adding...")
print(">>>>>>>>>>>>>>>>>> NO-PREFIX: ", user.password)
print(">>>>>>>>>>>>>>>>>> WITH-PREFIX: ",
f'bcrypt${user.password}')
print(">>>>>>>>>>>>>>>>>> CHECKING PASSWORD NO-PREFIX: ",
user.check_password(password))
print(">>>>>>>>>>>>>>>>>> CHECKING PASSWORD WITH-PREFIX: ",
user.check_password(f'bcrypt${user.password}'))
#user = UserModel(password=f'bcrypt${user.password}')
if u.check_password(password) and self.user_can_authenticate(user):
return user
def user_can_authenticate(self, user):
"""
Reject users with is_active=False. Custom user models that don't have
that attribute are allowed.
"""
is_active = getattr(user, 'is_active', None)
return is_active or is_active is None
settings.py 正在使用此身份验证后端
AUTHENTICATION_BACKENDS = [
'users.backends.ModelBackend',
'django.contrib.auth.backends.ModelBackend'
]
当 django 检查密码时,它会抛出以下错误
File "\site-packages\django\contrib\auth\__init__.py", line 73, in authenticate
user = backend.authenticate(request, **credentials)
File "\auth\users\backends.py", line 74, in authenticate
if user.check_password(password) and self.user_can_authenticate(user):
File "\site-packages\django\contrib\auth\base_user.py", line 112, in check_password
return check_password(raw_password, self.password, setter)
File "\site-packages\django\contrib\auth\hashers.py", line 49, in check_password
must_update = hasher_changed or preferred.must_update(encoded)
File "\site-packages\django\contrib\auth\hashers.py", line 443, in must_update
return int(rounds) != self.rounds
ValueError: invalid literal for int() with base 10: '2b'
我已经使用了这个实现的一部分https://github.com/django/django/blob/main/django/contrib/auth/backends.py
测试
>>> from users.models import TbUser
>>> from django.contrib.auth import check_password
>>> user = TbUser.objects.get(username="Gastro")
>>> user
<TbUser: Gastro>
>>> ps = user.password
>>> ps_bc = "bcrypt$" + ps
>>> ps_bc
'bcrypt$a$rA59QU2GsWR4v6hugdYhruxY0bgZYVLv6ncxRe3BiDJMEpK0A0huW'
>>> check_password("111111", ps_bc)
True
>>> check_password("111111", ps)
False
>>> user.check_password("111111")
False
Django 首先指定使用的 散列 算法,因为它可以为每个用户使用不同的散列算法。
基本上有两种方法可以解决这个问题:
- 更改数据库中的密码;或
- 制作一个(稍微)不同的身份验证后端,它将在其前面加上密码。
选项 1:更新密码
您可以批量更新记录:
from django.db.models import F, Value
from django.db.models.functions import Concat
MyModel.objects.update(
<b>password=Concat(Value('bcrypt$'), F('password'))</b>
)
这将因此更新密码字段(如果密码存储在另一个字段中,则使用该字段名称),方法是在哈希值前添加 'bcrypt$'
。
选项 2:自定义身份验证方法
我们可以将 ModelBackend
子类化并稍微重写为:
# <i>app_name</i>/backends.py
django.contrib.auth.backends import ModelBackend
class MyModelBackend(ModelBackend):
def authenticate(self, request, username=None, password=None, **kwargs):
if username is None:
username = kwargs.get(UserModel.USERNAME_FIELD)
if username is None or password is None:
return
try:
user = UserModel._default_manager.get_by_natural_key(username)
except UserModel.DoesNotExist:
# Run the default password hasher once to reduce the timing
# difference between an existing and a nonexistent user (#20760).
UserModel().set_password(password)
else:
if user.password.startswith('bcrypt'):
u = user
else:
<b>u = UserModel(password=f'bcrypt_sha256${user.password}')</b>
if u.check_password(password) and self.user_can_authenticate(user):
return user
然后我们可以在 AUTHENTICATION_BACKENDS
[Django-doc]:
中注册这个后端
# settings.py
# …
AUTHENTICATION_BACKENDS = [
'<i>app_name</i>.backends.MyModelBackend',
'django.contrib.auth.backends.ModelBackend'
]
# …
我在 mysql 遗留数据库上使用 django。我已经集成了除密码以外的所有内容。遗留数据库以这种方式存储密码
a$Pdg3h8AVZ6Vl3X1mMKgQDuMriv8iysnValEa5YZO3j9pEboLrOBUK
并且 django 仅在相同的散列具有 bcrypt$
前缀 bcrypt$a$Pdg3h8AVZ6Vl3X1mMKgQDuMriv8iysnValEa5YZO3j9pEboLrOBUK
时才读取。如何让 Django 通过读取第一个示例哈希来验证用户?
为什么 django 在密码中添加前缀?
更新 我添加了模型后端
from django.contrib.auth.backends import ModelBackend
from users.models import TbUser
from django.contrib.auth import get_user_model
from django.contrib.auth.models import Permission
from django.db.models import Exists, OuterRef, Q
UserModel = get_user_model()
class BaseBackend:
def authenticate(self, request, **kwargs):
return None
def get_user(self, user_id):
return None
def get_user_permissions(self, user_obj, obj=None):
return set()
def get_group_permissions(self, user_obj, obj=None):
return set()
def get_all_permissions(self, user_obj, obj=None):
return {
*self.get_user_permissions(user_obj, obj=obj),
*self.get_group_permissions(user_obj, obj=obj),
}
def has_perm(self, user_obj, perm, obj=None):
return perm in self.get_all_permissions(user_obj, obj=obj)
class ModelBackend(BaseBackend):
"""
Authenticates against settings.AUTH_USER_MODEL.
"""
def authenticate(self, request, username=None, password=None, **kwargs):
print(">>>>>>>>>>>>>>>>>> Authentication start")
print("User: ", username, " Password: ", password)
if username is None:
username = kwargs.get(UserModel.USERNAME_FIELD)
if username is None or password is None:
return
try:
user = UserModel._default_manager.get_by_natural_key(username)
except UserModel.DoesNotExist:
# Run the default password hasher once to reduce the timing
# difference between an existing and a nonexistent user (#20760).
UserModel().set_password(password)
else:
if user.password.startswith('bcrypt'):
u = user
else:
u = UserModel(password=f'bcrypt${user.password}')
print(">>>>>>>>>>>>>>>>>> Authentication prefix adding...")
print(">>>>>>>>>>>>>>>>>> NO-PREFIX: ", user.password)
print(">>>>>>>>>>>>>>>>>> WITH-PREFIX: ",
f'bcrypt${user.password}')
print(">>>>>>>>>>>>>>>>>> CHECKING PASSWORD NO-PREFIX: ",
user.check_password(password))
print(">>>>>>>>>>>>>>>>>> CHECKING PASSWORD WITH-PREFIX: ",
user.check_password(f'bcrypt${user.password}'))
#user = UserModel(password=f'bcrypt${user.password}')
if u.check_password(password) and self.user_can_authenticate(user):
return user
def user_can_authenticate(self, user):
"""
Reject users with is_active=False. Custom user models that don't have
that attribute are allowed.
"""
is_active = getattr(user, 'is_active', None)
return is_active or is_active is None
settings.py 正在使用此身份验证后端
AUTHENTICATION_BACKENDS = [
'users.backends.ModelBackend',
'django.contrib.auth.backends.ModelBackend'
]
当 django 检查密码时,它会抛出以下错误
File "\site-packages\django\contrib\auth\__init__.py", line 73, in authenticate
user = backend.authenticate(request, **credentials)
File "\auth\users\backends.py", line 74, in authenticate
if user.check_password(password) and self.user_can_authenticate(user):
File "\site-packages\django\contrib\auth\base_user.py", line 112, in check_password
return check_password(raw_password, self.password, setter)
File "\site-packages\django\contrib\auth\hashers.py", line 49, in check_password
must_update = hasher_changed or preferred.must_update(encoded)
File "\site-packages\django\contrib\auth\hashers.py", line 443, in must_update
return int(rounds) != self.rounds
ValueError: invalid literal for int() with base 10: '2b'
我已经使用了这个实现的一部分https://github.com/django/django/blob/main/django/contrib/auth/backends.py
测试
>>> from users.models import TbUser
>>> from django.contrib.auth import check_password
>>> user = TbUser.objects.get(username="Gastro")
>>> user
<TbUser: Gastro>
>>> ps = user.password
>>> ps_bc = "bcrypt$" + ps
>>> ps_bc
'bcrypt$a$rA59QU2GsWR4v6hugdYhruxY0bgZYVLv6ncxRe3BiDJMEpK0A0huW'
>>> check_password("111111", ps_bc)
True
>>> check_password("111111", ps)
False
>>> user.check_password("111111")
False
Django 首先指定使用的 散列 算法,因为它可以为每个用户使用不同的散列算法。
基本上有两种方法可以解决这个问题:
- 更改数据库中的密码;或
- 制作一个(稍微)不同的身份验证后端,它将在其前面加上密码。
选项 1:更新密码
您可以批量更新记录:
from django.db.models import F, Value
from django.db.models.functions import Concat
MyModel.objects.update(
<b>password=Concat(Value('bcrypt$'), F('password'))</b>
)
这将因此更新密码字段(如果密码存储在另一个字段中,则使用该字段名称),方法是在哈希值前添加 'bcrypt$'
。
选项 2:自定义身份验证方法
我们可以将 ModelBackend
子类化并稍微重写为:
# <i>app_name</i>/backends.py
django.contrib.auth.backends import ModelBackend
class MyModelBackend(ModelBackend):
def authenticate(self, request, username=None, password=None, **kwargs):
if username is None:
username = kwargs.get(UserModel.USERNAME_FIELD)
if username is None or password is None:
return
try:
user = UserModel._default_manager.get_by_natural_key(username)
except UserModel.DoesNotExist:
# Run the default password hasher once to reduce the timing
# difference between an existing and a nonexistent user (#20760).
UserModel().set_password(password)
else:
if user.password.startswith('bcrypt'):
u = user
else:
<b>u = UserModel(password=f'bcrypt_sha256${user.password}')</b>
if u.check_password(password) and self.user_can_authenticate(user):
return user
然后我们可以在 AUTHENTICATION_BACKENDS
[Django-doc]:
# settings.py
# …
AUTHENTICATION_BACKENDS = [
'<i>app_name</i>.backends.MyModelBackend',
'django.contrib.auth.backends.ModelBackend'
]
# …