Реализация надежной аутентификации и управления пользователями в интерактивных приложениях, таких как созданные с помощью Streamlit, часто требует использования полноценного backend-фреймворка. Django, с его зрелой системой аутентификации и богатыми возможностями, является отличным кандидатом для этой задачи.
Введение в аутентификацию Streamlit с использованием Django
Streamlit — это мощный инструмент для быстрого создания и обмена данными с интерактивными веб-приложениями на Python. Однако, по умолчанию Streamlit не предоставляет встроенной системы аутентификации или управления пользователями. Для приложений, требующих контроля доступа или персонализации контента, необходимо интегрировать backend-решение.
Почему Django для аутентификации в Streamlit?
Django — это высокоуровневый Python-фреймворк, который поощряет быструю разработку и чистый дизайн. Он поставляется с готовой, полностью настраиваемой системой аутентификации, поддержкой пользовательских моделей, разрешений и групп. Интеграция Django в качестве backend для аутентификации Streamlit позволяет использовать проверенные и безопасные механизмы управления пользователями, не изобретая велосипед.
Надежность: Система аутентификации Django используется на множестве высоконагруженных сайтов.
Безопасность: Встроенная защита от распространенных веб-угроз.
Функциональность: Поддержка пользователей, групп, разрешений, сброса пароля и т.д.
Расширяемость: Легко кастомизировать модели пользователей и добавлять свои поля.
Обзор Streamlit и его интеграции с backend-фреймворками
Streamlit предназначен для создания data-приложений и не является традиционным веб-фреймворком типа Django или Flask. Он управляет состоянием приложения на стороне клиента и сервера, но не имеет встроенных механизмов маршрутизации API или управления сессиями в классическом понимании. Поэтому для реализации функционала backend (аутентификация, доступ к базе данных, сложная логика) необходимо развернуть отдельный backend-сервис, с которым Streamlit будет взаимодействовать.
Интеграция Streamlit с backend обычно происходит через HTTP-запросы к REST API, предоставляемому backend-фреймворком. Django REST Framework (DRF) идеально подходит для создания такого API.
Необходимые инструменты и библиотеки
Для реализации решения понадобятся:
Python 3.6+
Django
Django REST Framework (DRF)
djangorestframework-simplejwt (для аутентификации на основе токенов)
django-cors-headers (для обработки CORS)
Streamlit
requests (для выполнения HTTP-запросов из Streamlit)
Рекомендуется использовать виртуальные окружения (venv, virtualenv, Poetry, pipenv) для управления зависимостями.
Настройка Django проекта для аутентификации
Первым шагом является настройка Django проекта, который будет выступать в роли backend для аутентификации.
Создание нового Django проекта и приложения
Создайте новый проект Django и приложение, например, authentication.
pip install Django djangorestframework djangorestframework-simplejwt django-cors-headers
django-admin startproject streamlit_auth_backend .
python manage.py startapp authentication
Добавьте созданные приложения, DRF, Simple JWT и CORS в INSTALLED_APPS в settings.py:
# settings.py
INSTALLED_APPS = [
# ... другие приложения
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework',
'rest_framework_simplejwt',
'corsheaders',
'authentication', # Ваше приложение
]
Настройка моделей пользователей Django (Custom User Model)
Для гибкости и возможности добавления собственных полей в будущем, всегда рекомендуется использовать кастомную модель пользователя.
В authentication/models.py создайте простую кастомную модель, наследуясь от AbstractUser.
# authentication/models.py
from django.contrib.auth.models import AbstractUser
from django.db import models
class CustomUser(AbstractUser):
# Добавьте любые дополнительные поля здесь, если необходимо
# Например: role = models.CharField(max_length=50, blank=True)
pass # Пока используем только поля AbstractUser
Укажите Django использовать эту модель в settings.py:
# settings.py
AUTH_USER_MODEL = 'authentication.CustomUser'
Примените миграции:
python manage.py makemigrations
python manage.py migrate
Настройка REST API для аутентификации (Django REST Framework)
Используем djangorestframework-simplejwt для аутентификации по JWT токенам. Это stateless подход, подходящий для API взаимодействия.
Добавьте URL-адреса для получения и обновления токенов в urls.py проекта:
# streamlit_auth_backend/urls.py
from django.contrib import admin
from django.urls import path, include
from rest_framework_simplejwt.views import (
TokenObtainPairView,
TokenRefreshView,
)
urlpatterns = [
path('admin/', admin.site.urls),
path('api/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'),
path('api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
path('api/', include('authentication.urls')), # URL'ы вашего приложения аутентификации
]
Создайте authentication/urls.py для эндпоинта регистрации и, возможно, эндпоинта для получения информации о пользователе.
# authentication/urls.py
from django.urls import path
from .views import RegisterView, UserDetailView
urlpatterns = [
path('register/', RegisterView.as_view(), name='auth_register'),
path('user/', UserDetailView.as_view(), name='auth_user_detail'),
]
В authentication/views.py реализуйте View для регистрации и получения данных пользователя.
# authentication/views.py
from rest_framework import generics
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView
from .serializers import RegisterSerializer, CustomUserSerializer
class RegisterView(generics.CreateAPIView):
queryset = CustomUser.objects.all()
# Не используем пермишены, чтобы любой мог зарегистрироваться
# permission_classes = (AllowAny,)
serializer_class = RegisterSerializer
class UserDetailView(APIView):
permission_classes = (IsAuthenticated,)
def get(self, request):
# Сериализуем информацию о текущем пользователе
serializer = CustomUserSerializer(request.user)
return Response(serializer.data)
Создайте authentication/serializers.py.
# authentication/serializers.py
from rest_framework import serializers
from .models import CustomUser
from django.contrib.auth.password_validation import validate_password
class CustomUserSerializer(serializers.ModelSerializer):
class Meta:
model = CustomUser
fields = ('id', 'username', 'email', 'first_name', 'last_name', 'is_staff', 'is_superuser')
read_only_fields = ('id', 'is_staff', 'is_superuser')
class RegisterSerializer(serializers.ModelSerializer):
password = serializers.CharField(write_only=True, required=True, validators=[validate_password])
password2 = serializers.CharField(write_only=True, required=True)
class Meta:
model = CustomUser
fields = ('username', 'password', 'password2', 'email', 'first_name', 'last_name')
extra_kwargs = {
'first_name': {'required': False},
'last_name': {'required': False},
'email': {'required': True} # Сделать email обязательным
}
def validate(self, attrs):
# Проверка совпадения паролей
if attrs['password'] != attrs['password2']:
raise serializers.ValidationError({"password": "Пароли не совпадают."})
return attrs
def create(self, validated_data):
# Удаляем password2, так как он не нужен для создания пользователя
validated_data.pop('password2')
# Создаем пользователя с хешированным паролем
user = CustomUser.objects.create_user(
username=validated_data['username'],
email=validated_data['email'],
password=validated_data['password'],
first_name=validated_data.get('first_name', ''),
last_name=validated_data.get('last_name', '')
)
return user
Настройте Simple JWT в settings.py:
# settings.py
from datetime import timedelta
# ... другие настройки
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework_simplejwt.authentication.JWTAuthentication',
)
}
SIMPLE_JWT = {
'ACCESS_TOKEN_LIFETIME': timedelta(minutes=5), # Время жизни Access токена
'REFRESH_TOKEN_LIFETIME': timedelta(days=1), # Время жизни Refresh токена
'ROTATE_REFRESH_TOKENS': False,
'BLACKLIST_ENABLED': False, # В простых случаях можно не включать блэклист
'ALGORITHM': 'HS256',
'SIGNING_KEY': SECRET_KEY, # Используем SECRET_KEY Django
'VERIFYING_KEY': None,
'AUDIENCE': None,
'ISSUER': None,
'AUTH_HEADER_TYPES': ('Bearer',),
'USER_ID_FIELD': 'id',
'USER_ID_CLAIM': 'user_id',
'AUTH_TOKEN_CLASSES': ('rest_framework_simplejwt.tokens.AccessToken',),
'TOKEN_TYPE_CLAIM': 'token_type',
'JTI_CLAIM': 'jti',
'SLIDING_TOKEN_LIFETIME': timedelta(minutes=5),
'SLIDING_TOKEN_REFRESH_LIFETIME': timedelta(days=1),
}
Настройка CORS для взаимодействия с Streamlit
Streamlit будет запускаться на другом порту или даже другом домене по сравнению с Django backend. Для разрешения кросс-доменных запросов необходимо настроить CORS.
Добавьте CorsMiddleware в MIDDLEWARE в settings.py:
# settings.py
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
# Добавьте CorsMiddleware выше других middleware, особенно выше CommonMiddleware
'corsheaders.middleware.CorsMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
Настройте разрешенные источники CORS в settings.py. В режиме разработки можно разрешить все источники или указать адрес Streamlit приложения (например, http://localhost:8501).
# settings.py
# Разрешить все источники в режиме разработки
# CORS_ALLOW_ALL_ORIGINS = True
# Или разрешить конкретные источники:
CORS_ALLOWED_ORIGINS = [
"http://localhost:8501",
# Добавьте продакшн URL Streamlit приложения здесь
]
# Если вы используете кастомные заголовки в запросах из Streamlit, их нужно разрешить
# CORS_ALLOW_HEADERS = [
# 'accept',
# 'accept-encoding',
# 'authorization',
# 'content-type',
# 'origin',
# 'dnt',
# 'user-agent',
# 'x-csrftoken',
# 'x-requested-with',
# ]
Запустите Django backend для тестирования:
python manage.py runserver
Теперь у вас есть Django backend с API для регистрации и аутентификации по JWT токенам.
Интеграция Django аутентификации в Streamlit
Теперь создадим Streamlit приложение, которое будет взаимодействовать с нашим Django backend.
Создание Streamlit приложения
Создайте файл streamlit_app.py в корне вашего проекта Streamlit.
# streamlit_app.py
import streamlit as st
import requests
import json
# URL вашего Django backend'а
BACKEND_URL = "http://localhost:8000/api/"
Реализация форм входа и регистрации в Streamlit
Используйте виджеты Streamlit (st.form, st.text_input, st.button) для создания форм.
Создадим функции для взаимодействия с API и вспомогательные функции для управления состоянием (сессией) пользователя в Streamlit.
Состояние пользователя в Streamlit можно хранить с помощью st.session_state.
# streamlit_app.py (продолжение)
# Инициализация st.session_state
if 'token' not in st.session_state:
st.session_state.token = None
if 'user_info' not in st.session_state:
st.session_state.user_info = None
def set_token(token: str):
""" Сохраняет токен в session_state """
st.session_state.token = token
def clear_token():
""" Удаляет токен из session_state """
st.session_state.token = None
st.session_state.user_info = None
def set_user_info(user_data: dict):
""" Сохраняет информацию о пользователе в session_state """
st.session_state.user_info = user_data
def is_authenticated() -> bool:
""" Проверяет, авторизован ли пользователь """
return st.session_state.token is not None
def get_auth_header() -> dict:
""" Формирует заголовок авторизации """
if is_authenticated():
return {"Authorization": f"Bearer {st.session_state.token}"}
return {}
Взаимодействие с Django REST API для аутентификации
Реализуем функции для отправки запросов к backend.
# streamlit_app.py (продолжение)
def login(username: str, password: str) -> dict | None:
""" Отправляет запрос на аутентификацию и возвращает токены """
url = f"{BACKEND_URL}token/"
data = {"username": username, "password": password}
try:
response = requests.post(url, json=data)
response.raise_for_status() # Вызовет исключение для ошибок HTTP (4xx или 5xx)
tokens = response.json()
set_token(tokens['access'])
# Можно сразу получить инфо о пользователе после входа
fetch_user_info()
return tokens
except requests.exceptions.RequestException as e:
st.error(f"Ошибка при входе: {e}")
return None
def register(username: str, password: str, password2: str, email: str) -> dict | None:
""" Отправляет запрос на регистрацию пользователя """
url = f"{BACKEND_URL}register/"
data = {
"username": username,
"password": password,
"password2": password2,
"email": email
}
try:
response = requests.post(url, json=data)
response.raise_for_status()
st.success("Регистрация успешна. Теперь вы можете войти.")
return response.json()
except requests.exceptions.RequestException as e:
st.error(f"Ошибка при регистрации: {e.response.json() if e.response else e}")
return None
def fetch_user_info():
""" Получает информацию о текущем пользователе """
url = f"{BACKEND_URL}user/"
headers = get_auth_header()
if not headers:
return # Не авторизован
try:
response = requests.get(url, headers=headers)
response.raise_for_status()
set_user_info(response.json())
except requests.exceptions.RequestException as e:
st.error(f"Ошибка при получении данных пользователя: {e}")
clear_token() # Если не удалось получить данные, возможно, токен истек или невалиденТеперь создадим UI в Streamlit:
# streamlit_app.py (продолжение)
st.title("Streamlit App с Django Auth")
# Проверка статуса аутентификации и отображение соответствующего контента
if is_authenticated():
st.sidebar.header("Профиль пользователя")
user_info = st.session_state.user_info
if user_info:
st.sidebar.write(f"Привет, {user_info.get('first_name') or user_info.get('username')}!")
st.sidebar.write(f"Email: {user_info.get('email')}")
# Добавьте другие поля по необходимости
st.success("Вы успешно вошли!")
st.write("Здесь находится контент приложения.")
if st.sidebar.button("Выйти"):
clear_token()
st.experimental_rerun() # Перезапустить приложение для обновления UI
else:
# Отображаем формы входа/регистрации
st.info("Пожалуйста, войдите или зарегистрируйтесь.")
# Выбор формы (вход или регистрация)
auth_mode = st.selectbox("Выберите действие", ("Вход", "Регистрация"))
if auth_mode == "Вход":
st.header("Вход")
with st.form("login_form"):
username = st.text_input("Имя пользователя")
password = st.text_input("Пароль", type="password")
submit_button = st.form_submit_button("Войти")
if submit_button:
if username and password:
login(username, password)
# Streamlit автоматически перерисует страницу, если session_state изменился
# Или можно явно вызвать st.experimental_rerun() после успешного входа
# st.experimental_rerun()
else:
st.warning("Пожалуйста, введите имя пользователя и пароль.")
elif auth_mode == "Регистрация":
st.header("Регистрация")
with st.form("register_form"):
username = st.text_input("Имя пользователя")
email = st.text_input("Email")
password = st.text_input("Пароль", type="password")
password2 = st.text_input("Повторите пароль", type="password")
submit_button = st.form_submit_button("Зарегистрироваться")
if submit_button:
if username and email and password and password2:
if password == password2:
register(username, password, password2, email)
else:
st.error("Пароли не совпадают.")
else:
st.warning("Пожалуйста, заполните все поля.")
# При первом запуске или после перезапуска, если токен есть, попробуем получить инфо о пользователе
# Это позволит сохранить сессию после обновления страницы Streamlit
if is_authenticated() and st.session_state.user_info is None:
fetch_user_info()
# Если fetch_user_info не удался (например, токен протух), is_authenticated() станет False
# Streamlit автоматически перерисует UI, показав формы входа/регистрации
Запустите Streamlit приложение:
streamlit run streamlit_app.py
Теперь у вас есть Streamlit приложение, которое взаимодействует с Django backend для аутентификации.
Управление сессиями пользователей в Streamlit
Streamlit по своей природе не поддерживает традиционные веб-сессии на стороне сервера. st.session_state позволяет сохранять состояние между перезапусками скрипта, вызванными взаимодействием пользователя или изменениями кода. Мы используем его для хранения JWT токена и информации о пользователе.
Хранение токена: JWT access токен хранится в st.session_state.token. Он отправляется с каждым запросом к защищенным эндпоинтам Django.
Хранение информации о пользователе: Полученная информация (имя, email, роли) хранится в st.session_state.user_info.
Обновление токена: В более сложных сценариях можно добавить логику для использования refresh токена (/api/token/refresh/) для получения нового access токена до того, как старый истечет, или при получении ошибки 401 от backend. Для простоты в примере выше этого нет.
Выход: При выходе токен и информация о пользователе удаляются из st.session_state.
Сохранение сессии после обновления: При перезагрузке страницы Streamlit, скрипт выполняется с начала. st.session_state сохраняет свое состояние. Это позволяет при запуске проверить наличие токена и, если он есть, попытаться получить данные пользователя (fetch_user_info), тем самым "восстановив" аутентифицированное состояние приложения.
Управление пользователями и ролями
Управление пользователями и их разрешениями удобно выполнять средствами Django.
Реализация панели управления пользователями в Django Admin
Django Admin предоставляет мощный интерфейс для управления данными, включая пользователей и группы.
В authentication/admin.py зарегистрируйте вашу кастомную модель пользователя.
# authentication/admin.py
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from .models import CustomUser
# Optional: Customize the UserAdmin if you added extra fields
# class CustomUserAdmin(UserAdmin):
# model = CustomUser
# list_display = ['username', 'email', 'first_name', 'last_name', 'is_staff', 'is_superuser']
# # Add custom fields to fieldsets and add_fieldsets if needed
# # fieldsets = UserAdmin.fieldsets + ( (None, {'fields': ('role',)}), )
# # add_fieldsets = UserAdmin.add_fieldsets + ( (None, {'fields': ('role',)}), )
# admin.site.register(CustomUser, CustomUserAdmin)
admin.site.register(CustomUser) # Простая регистрация
Создайте суперпользователя для доступа к Admin панели:
python manage.py createsuperuser
Теперь вы можете зайти на http://localhost:8000/admin/ и управлять пользователями.
Настройка разрешений и ролей пользователей (Django Permissions)
Django имеет встроенную систему разрешений. Вы можете назначать разрешения конкретным пользователям или группам пользователей через Admin панель.
Группы: Создайте группы (например, "Менеджеры", "Аналитики") и назначайте пользователям эти группы.
Разрешения: Django автоматически создает разрешения для всех действий CRUD (create, retrieve, update, delete) над каждой моделью. Вы можете создавать свои кастомные разрешения.
Например, чтобы разрешить группе "Менеджеры" просматривать определенный раздел приложения Streamlit, вы можете назначить этой группе кастомное разрешение в Django (например, authentication.can_view_manager_dashboard).
Отображение информации о пользователе в Streamlit (на основе ролей)
Полученная информация о пользователе (st.session_state.user_info) включает поля is_staff, is_superuser, а также информацию о группах и разрешениях (если вы их сериализуете в CustomUserSerializer).
Вы можете использовать эту информацию в Streamlit для условного отображения контента:
# streamlit_app.py (фрагмент в секции if is_authenticated(): )
# ... (код выше)
st.success("Вы успешно вошли!")
user_info = st.session_state.user_info
if user_info:
# Проверка на суперадмина или staff
if user_info.get('is_superuser') or user_info.get('is_staff'):
st.header("Админский раздел")
st.write("Доступно только администраторам.")
# Пример проверки по группам (нужно сериализовать группы в user_info)
# if 'Менеджеры' in [g['name'] for g in user_info.get('groups', [])]:
# st.header("Панель менеджера")
# st.write("Доступно менеджерам.")
# Пример проверки по кастомному разрешению (нужно сериализовать разрешения)
# if 'authentication.can_view_manager_dashboard' in user_info.get('user_permissions', []):
# st.header("Отчеты для менеджеров")
# st.write("Специальные отчеты.")
st.write("Здесь находится общий контент приложения.")
# ... (код ниже)
Для сериализации групп и разрешений, обновите CustomUserSerializer в authentication/serializers.py:
# authentication/serializers.py (обновленный CustomUserSerializer)
# ... импорты
class CustomUserSerializer(serializers.ModelSerializer):
# Сериализуем имена групп
groups = serializers.SlugRelatedField(many=True, read_only=True, slug_field='name')
# Можно сериализовать имена разрешений напрямую, если их немного, или добавить отдельный эндпоинт
# user_permissions = serializers.SlugRelatedField(many=True, read_only=True, slug_field='codename')
class Meta:
model = CustomUser
fields = ('id', 'username', 'email', 'first_name', 'last_name', 'is_staff', 'is_superuser', 'groups') # Добавьте 'groups' сюда
read_only_fields = ('id', 'is_staff', 'is_superuser', 'groups')
После этого st.session_state.user_info будет содержать список групп пользователя.
Развертывание и безопасность
Развертывание двух связанных приложений (Django backend и Streamlit frontend) требует внимания к деталям.
Развертывание Django и Streamlit приложений
Django Backend: Развертывается как обычное Django приложение. Используйте Gunicorn/uWSGI в качестве WSGI-сервера и Nginx/Apache в качестве reverse proxy. Базу данных (PostgreSQL, MySQL) также следует развернуть. Убедитесь, что DEBUG в settings.py установлен в False и SECRET_KEY надежно защищен. Настройте CORS для разрешения запросов только с домена, на котором будет работать Streamlit.
Streamlit Frontend: Может быть развернут на сервере с использованием Gunicorn или streamlit run. При использовании Gunicorn перед ним также ставят reverse proxy (Nginx/Apache). Также можно использовать специализированные сервисы, такие как Streamlit Cloud, но в этом случае убедитесь, что ваш Django backend доступен извне и настроены CORS.
Взаимодействие: Убедитесь, что Streamlit приложение знает правильный публичный URL вашего Django backend API.
Рекомендации по безопасности аутентификации (защита от CSRF, XSS)
CSRF: Django REST Framework и JWT аутентификация по своей природе менее подвержены CSRF атакам на API эндпоинты, поскольку не используют куки сессий. Однако, если вы используете куки (что нетипично для API), убедитесь, что включена защита CSRF в Django.
XSS: Streamlit сам по себе санитаризует выводимый контент, но всегда будьте осторожны при отображении пользовательского ввода. На стороне Django убедитесь, что данные пользователя (например, имя) правильно экранируются при их отображении в Admin панели или любых других views.
HTTPS: Всегда используйте HTTPS для обоих приложений, чтобы защитить передаваемые токены и учетные данные.
SECRET_KEY и токены: Никогда не коммитьте SECRET_KEY в репозиторий. Используйте переменные окружения для его хранения. JWT токены должны передаваться только по HTTPS.
Валидация данных: Всегда строго валидируйте входящие данные на стороне backend (Django), как это делает DRF.
Обработка ошибок и логирование
Реализуйте централизованную обработку ошибок в Django API (DRF имеет встроенные механизмы) и добавьте логирование для отслеживания неудачных попыток входа, ошибок регистрации и других важных событий безопасности. В Streamlit обрабатывайте исключения при выполнении HTTP-запросов к backend и отображайте информативные сообщения пользователю, не раскрывая слишком много деталей о внутренней ошибке.
Пример базового логирования в Django settings.py:
# settings.py
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'handlers': {
'console': {
'class': 'logging.StreamHandler',
},
},
'loggers': {
'django': {
'handlers': ['console'],
'level': 'INFO',
},
'authentication': {
'handlers': ['console'],
'level': 'DEBUG', # Или INFO/ERROR в зависимости от нужд
'propagate': True,
}
# Добавьте другие логгеры для ваших приложений
},
}
В Streamlit приложении можно использовать st.error или st.exception для отображения ошибок пользователю и стандартный модуль logging для записи ошибок в лог на сервере, где запущено Streamlit приложение.
# streamlit_app.py (пример логирования в функции login)
import logging
# Настройка базового логирования в Streamlit (опционально, зависит от окружения)
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
def login(username: str, password: str) -> dict | None:
url = f"{BACKEND_URL}token/"
data = {"username": username, "password": password}
try:
response = requests.post(url, json=data)
response.raise_for_status()
tokens = response.json()
set_token(tokens['access'])
fetch_user_info()
logging.info(f"User '{username}' logged in successfully.")
return tokens
except requests.exceptions.RequestException as e:
error_detail = e.response.json() if e.response and hasattr(e.response, 'json') else str(e)
st.error(f"Ошибка при входе: {error_detail}")
logging.error(f"Failed login attempt for user '{username}': {error_detail}")
return None
# ... остальные функции и UI
Реализация надежной аутентификации и управления пользователями в Streamlit с помощью Django backend является вполне достижимой задачей. Используя проверенные инструменты, такие как Django, DRF и JWT, вы можете построить безопасную и масштабируемую систему, позволяющую контролировать доступ к вашим Streamlit приложениям и персонализировать пользовательский опыт.