我正在编写自定义身份验证后端,以针对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模型将连接到该用户?这就是我要尝试这个的方法,但我想我会向社区询问如何在其他地方完成这项工作。
答案 0 :(得分:1)
希望这可以帮助将来的某个人。我解决这个问题的方法是在我的身份验证后端我查询API然后找到(或创建)本地用户对象并更新该对象的相关属性。它有点奇怪,因为本地数据库的主键是远程用户对象的id(和pk)(以便快速查找get_user()
),但我使用用户名作为查找键进行身份验证。 / p>
所以,总结一下:
此外,在我的示例中,我必须下载整个帐户列表并为正确的用户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
。