问题描述
我在 MysqL 旧数据库上使用 django。我已经集成了除密码之外的所有内容。旧数据库以这种方式存储密码
$2a$10$Pdg3h8AVZ6Vl3X1mMKgQDuMriv8iysnValEa5YZO3j9pEboLrOBUK
和 django 仅在相同的哈希具有 bcrypt$
前缀 bcrypt$$2a$10$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,obj=None):
return set()
def get_all_permissions(self,obj=None):
return {
*self.get_user_permissions(user_obj,obj=obj),*self.get_group_permissions(user_obj,}
def has_perm(self,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,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$$2a$10$rA59QU2GsWR4v6hugdYhruxY0bgZYVLv6ncxRe3BiDJMEpK0A0huW'
>>> check_password("111111",ps_bc)
True
>>> check_password("111111",ps)
False
>>> user.check_password("111111")
False
解决方法
Django 首先指定使用的 哈希 算法,因为它可以例如为每个用户使用不同的哈希算法。
基本上有两种方法可以解决这个问题:
- 更改数据库中的密码;或
- 制作一个(稍微)不同的身份验证后端,将在其前面添加密码。
方案一:更新密码
您可以通过以下方式批量更新记录:
from django.db.models import F,Value
from django.db.models.functions import Concat
MyModel.objects.update(
password=Concat(Value('bcrypt$'),F('password'))
)
这将因此通过在散列前添加 'bcrypt$'
来更新密码字段(如果密码存储在另一个字段中,则使用该字段名称)。
选项 2:自定义身份验证方法
我们可以将 ModelBackend
子类化并稍微将其重写为:
# app_name/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:
u = UserModel(password=f'bcrypt_sha256${user.password}')
if u.check_password(password) and self.user_can_authenticate(user):
return user
然后我们可以在 AUTHENTICATION_BACKENDS
[Django-doc] 中注册这个后端:
# settings.py
# …
AUTHENTICATION_BACKENDS = [
'app_name.backends.MyModelBackend','django.contrib.auth.backends.ModelBackend'
]
# …