我正在编写自定义身份验证后端,以对其进行身份验证。
我不知道如何将Profile
模型(将包含远程数据库中不包含信息的信息)连接到那些用户,以不受用户名的更改等的方式等。例如,如果我想要为这些用户提供" Bio"字段,然后我通常会这样做:
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
中?
我是否只需要确保身份验证后端更新用户的本地数据库,然后配置文件模型将连接到该数据库?这就是我要尝试的方式,但我认为我会在其他地方询问社区。
希望将来对某人有所帮助。我解决此问题的方式是在我的身份验证后端中查询API,然后找到(或创建)本地用户对象并更新该对象的相关属性。它有点奇怪,因为本地数据库的主要键是远程用户对象的ID(和PK)(以使get_user()
快速查找查找),但是我将用户名作为查找密钥进行认证。
so,总结:
- 进行后端查询API,然后将本地DB与一个用户同步(本地DB仅用于保留用户对象的远程数据)。
- 为了确保您的系统即使其远程用户名更改,请确保您使用远程ID(可能永远不会更改)来同步本地DB。
- 确保本地用户模型的主要键是远程数据库的主要键,以帮助您自己使用数据完整性。
另外,在我的示例中,我必须下载整个帐户列表并为合适的用户求助。在我的现实示例中,我使用的是一个API,该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
。