Аутентификация в нескольких приложениях Django: совместное использование пользовательских токенов для проверки подлинности и разрешений в разных кодовых базах
Я создаю мультиплатформенную систему, в которой у меня есть одна центральная служба аутентификации (назовем ее "Auth") и несколько других приложений Django (App1, App2, App3), которым необходимо аутентифицировать пользователей с помощью пользовательских токенов и разрешений.
Текущая настройка:
- Служба аутентификации: занимается регистрацией пользователей, входом в систему, управлением токенами
- App1, App2, App3: отдельные проекты Django с собственной базой данных и бизнес-логикой
- Все приложения должны проверять пользователей, прошедших проверку подлинности через службу аутентификации
Процесс аутентификации:
- Пользователь входит в систему через службу аутентификации → получает токен
- Пользователь отправляет запросы в App1/App2/App3 с помощью этого токена
- Приложениям App1/App2/App3 необходимо подтвердить токен и получить пользовательские данные
Модель аутентификации:
class AuthToken(models.Model):
token = models.CharField(max_length=48, unique=True)
user = models.ForeignKey(User, on_delete=models.CASCADE)
platform = models.ForeignKey(Platform, on_delete=models.CASCADE)
expires_at = models.DateTimeField(null=True, blank=True)
is_revoked = models.BooleanField(default=False)
# other fields (ip, device_id, device_name, etc...)
Конфигурация токена:
Settings.py
# settings.py
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'path.to.authentication.AuthTokenAuthentication',
],
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticated',
],
}
Класс аутентификации
# path/to/AuthTokenAuthentication.py
class AuthTokenAuthentication(BaseAuthentication):
"""
Simple, clean AuthToken authentication.
"""
def authenticate(self, request):
# Get Authorization header
auth_header = request.META.get('HTTP_AUTHORIZATION')
if not auth_header or not auth_header.startswith('Bearer '):
return None # No token provided
# rest of my code
<время работы/>
Проблема:
App1, App2, App3 должны использовать одни и те же пользовательские AuthTokenAuthentication
и permission classes
, но у них нет модели AuthToken
или соответствующего кода аутентификации.
Что вы подразумеваете под приложениями django: приложения в смысле повторно используемых приложений django-проекта или в смысле отдельных приложений/сервисов django, которые запускаются как свои собственные экземпляры? Если я правильно понял последнее.
Если все ваши приложения работают на одном сервере, но вам нужен доступ к разным базам данных, вы можете создать пользовательский маршрутизатор базы данных, смотрите документы django по этому вопросу: https://docs.djangoproject.com/en/5.2/topics/db/multi-db/ В качестве примера явно указан authRouter.
Тогда ваше приложение для авторизации могло бы использовать одну базу данных, а другие приложения могли бы использовать другую базу данных или каждую свою собственную базу данных ... .
Однако, если ваши приложения запускаются как отдельные Django-приложения (например, на разных серверах), у вас есть два варианта:
Первый вариант заключается в том, что каждое из ваших django-приложений использует одно и то же повторно используемое auth-приложение и имеет пользовательский db-адаптер, который гарантирует, что это приложение использует другую базу данных, отличную от других моделей проекта. Эта база данных аутентификации затем используется для передачи данных аутентификации между всеми авторизационными приложениями каждого из ваших Djano-приложений.
Вторым вариантом было бы использовать SAML или, что лучше, OpenID connect для единого входа (SSO). Когда пользователь хочет пройти аутентификацию в одном из ваших приложений, запрос на аутентификацию перенаправляется на конечную точку вашей службы аутентификации. Там пользователю предоставляется форма входа в систему, и он проходит аутентификацию, используя свои учетные данные. После успешной аутентификации служба аутентификации затем выдает токен (например, идентификационный токен и/или токен доступа) и перенаправляет пользователя обратно в исходное клиентское приложение с помощью этого токена. Клиентское приложение проверяет токен (обычно с помощью открытых ключей службы аутентификации или другой конечной точки вашего приложения для аутентификации) и устанавливает сеанс для пользователя.
Если предположить, что "служба аутентификации" означает приложение, доступ к которому могут получить App1, App2, ... во время обычных операций, то это вполне выполнимо. Просто запросите службу аутентификации для проверки токена.
Главный вопрос заключается в том, имеют ли App1, App2, ... и т.д. свои собственные пользовательские объекты, локальные для приложения, т.е. хранится ли информация о пользователе в "локальной" базе данных или только в авторизационном сервисе? И то, и другое выполнимо, но реализация в значительной степени зависит от результата решения этого вопроса.
Я предполагаю, что у вас есть локальное хранилище пользователей в приложении, потому что в противном случае я не уверен, в чем конкретно заключается проблема, с которой вы столкнулись.
Централизованные токены аутентификации для децентрализованных пользовательских хранилищ
- Определите поле, отличное от id/pk, которое является уникальным в пользовательской модели, например
email
- Требуется, чтобы пользователь предварительно зарегистрировался в приложениях App1, App2, ..., прежде чем сможет ими пользоваться
- Служба аутентификации выдает токен, который действителен для этого адреса электронной почты, а не для конкретного пользовательского объекта (поскольку пользовательские объекты изолированы от приложения)
- App1, App2, ... запрашивает службу аутентификации с помощью токена пользователя, и служба аутентификации отвечает адресом электронной почты
- Каждое приложение преобразует адрес электронной почты в пользовательский объект из своего собственного хранилища
# auth service
class AuthToken(models.Model):
token = models.CharField(max_length=48, unique=True)
user = models.ForeignKey(User, on_delete=models.CASCADE)
platform = models.ForeignKey(Platform, on_delete=models.CASCADE)
expires_at = models.DateTimeField(null=True, blank=True)
is_revoked = models.BooleanField(default=False)
email = models.EmailField()
@api_view(['POST'])
def auth_verification_view(request):
token = request.data.get('token')
try:
auth_token = AuthToken.objects.get(token=token, is_revoked=False)
# Check whatever other things you want here, too - expiration, platform-specific stuff, etc.
except AuthToken.DoesNotExist:
# Whatever you want a missing token situation to result in
return Response({'email': auth_token.email})
# app1, app2, etc.
# users/models.py
class User(AbstractUser):
...
email = EmailField()
class Meta:
constraints = [
UniqueConstraint(fields=['email'], name='unique_email'),
]
# authentication/AuthTokenAuthentication.py
class AuthTokenAuthentication(BaseAuthentication):
"""
Simple, clean AuthToken authentication.
"""
def authenticate(self, request):
# Get Authorization header
auth_header = request.META.get('HTTP_AUTHORIZATION')
token = auth_header.split(' ', 1)[1].strip()
if not token:
return None
# Query the auth service
try:
res = requests.post(AUTH_SERVICE_URL, json={"token": token}, timeout=5)
except:
...
# Look up the app-local user with the email we got from auth service
try:
user = User.objects.get(email=email)
except User.DoesNotExist:
raise exceptions.AuthenticationFailed("No matching user found")
return (user, None)
Обратите внимание, что вам также следует кэшировать результат проверки подлинности, либо результат службы проверки подлинности, либо пользовательский кортеж аутентификатора, в зависимости от потребностей вашего бизнеса, иначе производительность ваших приложений может резко снизиться.
Как упоминал @Vegard, нам нужно больше информации, чтобы дать полный ответ. Однако, насколько я понимаю, это звучит так, как будто вы хотите, чтобы ваша служба аутентификации действовала как поставщик OIDC и отражала пользователей в приложениях App1, App2 и App3.
В этом случае вы можете использовать id_token, выданный вашим поставщиком OIDC, для аутентификации пользователей в различных приложениях.
Я реализовал аналогичную настройку в этом репозитории: django-oidc-provider – это может помочь в качестве ссылки.