我正在编写自定义身份验证后端,以针对RESTful API进行身份验证。
我不知道如何以不受用户名更改等影响的方式将Profile
模型(该模型包含远程数据库中未包含的信息)连接到这些用户。例如,如果我想要为那些用户提供一个“生物”字段,那么我通常会这样做:
from django.contrib.auth.models import User
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
bio = models.TextField(blank=True)
def __str__(self):
return self.user.email
使用自定义的基于API的身份验证后端是否可以实现?如果是这样,我会在
OneToOneField
中放置什么以将其连接到远程用户?我是否只需要确保身份验证后端更新用户的本地数据库,然后将Profile模型连接到该数据库?这就是我要尝试的方式,但我想我会向社区询问如何在其他地方完成此操作。
最佳答案
希望这对以后的人有所帮助。解决此问题的方法是在身份验证后端中查询API,然后找到(或创建)本地用户对象并更新该对象的相关属性。有点奇怪,因为本地数据库的主键是远程用户对象的ID(和pk)(以便快速查找get_user()
),但是我正在使用用户名作为查找键进行身份验证。
因此,总结一下:
制作后端查询API,然后将本地数据库与该用户上的远程数据库同步(存在本地数据库只是为了保留用户对象的远程数据)。
为确保即使远程用户名更改,系统也能正常工作,请确保使用远程ID(假定永远不会更改)来同步本地数据库。
确保本地用户模型的主键是远程数据库的主键,以帮助自己提高数据完整性。
另外,在我的示例中,我必须下载整个帐户列表并grep给合适的用户。在我的真实示例中,我使用的API允许我通过用户名进行查找。下载整个帐户列表是个疯狂的坏主意,我在这里就这样做了,因为我的测试API不支持该功能。models.py
:
class RemoteUserManager(BaseUserManager):
def create_user(self, remote_id, remote_username=None, remote_first_name=None, remote_last_name=None, remote_email=None):
if not remote_id:
raise ValueError('Users must have a remote_id')
user = self.model(
remote_id=remote_id,
remote_username=remote_username,
remote_first_name=remote_first_name,
remote_last_name=remote_last_name,
remote_email=remote_email,
)
user.save(using=self._db)
return user
def create_superuser(self, remote_id, remote_username=None, remote_first_name=None, remote_last_name=None, remote_email=None):
user = self.create_user(
remote_id=remote_id,
remote_username=remote_username,
remote_first_name=remote_first_name,
remote_last_name=remote_last_name,
remote_email=remote_email,
)
user.is_staff = True
user.is_superuser = True
user.save(using=self._db)
return user
class RemoteUser(AbstractBaseUser):
remote_id = models.IntegerField(primary_key=True)
remote_username = models.CharField(max_length=255, blank=True)
remote_first_name = models.CharField(max_length=255, blank=True)
remote_last_name = models.CharField(max_length=255, blank=True)
remote_email = models.EmailField(blank=True)
is_active = models.BooleanField(default=True)
is_staff = models.BooleanField(default=True) # testing login to admin interface
is_superuser = models.BooleanField(default=True) # testing login to admin interface
created = models.DateTimeField(auto_now_add=True)
last_login = models.DateTimeField(auto_now=True)
objects = RemoteUserManager()
USERNAME_FIELD = 'remote_username'
REQUIRED_FIELDS = []
def has_perm(self, perm, obj=None):
return True
def has_module_perms(self, app_label):
return True
def get_full_name(self):
return str(self.remote_first_name + ' ' + self.remote_last_name).strip()
def get_short_name(self):
return self.remote_first_name
def get_display_name(self):
if (self.remote_first_name):
return self.remote_first_name
else:
return self.remote_email
def __str__(self):
return self.remote_username
backends.py
:class RemoteAuthBackend(object):
apikey = 'yourlongandsecureapikeygoeshere'
target = 'https://remote.domain.com'
list_call = '/admin/scaffolds/accounts/list.json'
show_call = '/admin/scaffolds/accounts/show/'
def authenticate(self, username=None, password=None):
username = username.strip()
# run API call and find user by username
request = urllib.request.Request(self.target + self.list_call + '?api_key=' + self.apikey)
response = urllib.request.urlopen(request)
resp_parsed = json.loads(response.read().decode('utf-8'))
# go through list of dicts and find the matching username
match_user = None
for user_record in resp_parsed:
if user_record.get('login', None) == username:
match_user = user_record
print('testy')
break
if not match_user: return None
# get password-crypted and salt
crypted_password = match_user.get('crypted_password', None)
salt = match_user.get('salt', None)
# hash password and see if the digests match (base64 encoded sha1 digest)
hash = b64encode(sha1((salt + password).encode('utf-8')).digest()).decode('utf-8')
if hash == match_user.get('crypted_password'):
# update user object that matches remote_id and return local user object
try: local_user = models.RemoteUser.objects.get(pk=match_user['id'])
except: local_user = None
if not local_user:
try: local_user = models.RemoteUser(remote_id=match_user['id'])
except: return None # This should never happen, ever
local_user.remote_username = match_user.get('login', None)
local_user.remote_first_name = match_user.get('first_name', None)
local_user.remote_last_name = match_user.get('last_name', None)
local_user.remote_email = match_user.get('email', None)
try: local_user.save()
except: return None
return local_user
else:
# failed auth
return None
def get_user(self, user_id):
# get user from remote and sync up local object properties based on remote_id
request = urllib.request.Request(self.target + self.show_call + str(user_id) + '.json?api_key=' + self.apikey)
response = urllib.request.urlopen(request)
match_user = json.loads(response.read().decode('utf-8'))
if not match_user: return None
try: local_user = models.RemoteUser.objects.get(pk=match_user['id'])
except: local_user = None
if not local_user:
try: local_user = models.RemoteUser(remote_id=match_user['id'])
except: return None # This should never happen, ever
local_user.remote_username = match_user.get('login', None)
local_user.remote_first_name = match_user.get('first_name', None)
local_user.remote_last_name = match_user.get('last_name', None)
local_user.remote_email = match_user.get('email', None)
try: local_user.save()
except: return None
return local_user
而且,您仍然必须像使用任何自定义用户模型一样修改
admin.py
。关于python - 将配置文件模型连接到远程用户(自定义身份验证后端),我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/41365871/