# 🔧 Token Issue Fix - Восстановление подключений ## Проблема При восстановлении подключений после logout/login возникала ошибка 403 Forbidden при переключении между подключениями. ### Причина Middleware получал Guacamole токен **напрямую из JWT payload**, а не из активной сессии в Redis. **ДО исправления:** ```python # middleware.py (строка 77) user_token = jwt_payload.get("guac_token") # ❌ guac_token нет в JWT! ``` **Проблема:** - JWT содержит только `session_id`, а не `guac_token` - При логине создается новая сессия в Redis с новым Guacamole токеном - Но middleware не загружал сессию из Redis, поэтому `user_token` был `None` или старый --- ## Решение ### ✅ Исправление в `middleware.py` Теперь middleware: 1. Извлекает `session_id` из JWT 2. Загружает актуальную сессию из Redis 3. Получает текущий Guacamole токен из сессии **ПОСЛЕ исправления:** ```python # middleware.py (строки 77-112) session_id = jwt_payload.get("session_id") if session_id: # Загружаем сессию из Redis from auth.session_storage import session_storage session_data = session_storage.get_session(session_id) if session_data: # ✅ Получаем актуальный Guacamole token из сессии user_token = session_data.get("guac_token") else: # Сессия истекла или удалена user_token = None else: # Backwards compatibility user_token = jwt_payload.get("guac_token") ``` --- ## Как это работает ### Схема аутентификации ``` ┌─────────────────┐ │ 1. Пользователь │ │ логинится │ └────────┬────────┘ │ ▼ ┌────────────────────────────────────────────┐ │ 2. Guacamole возвращает auth_token │ └────────┬───────────────────────────────────┘ │ ▼ ┌────────────────────────────────────────────┐ │ 3. API создает Redis сессию: │ │ - session_id: "abc123" │ │ - guac_token: "guac_token_xyz" │ │ - user_info: {...} │ └────────┬───────────────────────────────────┘ │ ▼ ┌────────────────────────────────────────────┐ │ 4. API создает JWT token: │ │ { │ │ "username": "user", │ │ "role": "USER", │ │ "session_id": "abc123", ← ТОЛЬКО ID! │ │ "exp": ... │ │ } │ └────────┬───────────────────────────────────┘ │ ▼ ┌────────────────────────────────────────────┐ │ 5. Клиент отправляет запросы с JWT │ └────────┬───────────────────────────────────┘ │ ▼ ┌────────────────────────────────────────────┐ │ 6. Middleware: │ │ - Извлекает session_id из JWT │ │ - Загружает сессию из Redis ✅ │ │ - Получает актуальный guac_token ✅ │ └────────────────────────────────────────────┘ ``` ### Восстановление подключений ``` User API Redis Guacamole │ │ │ │ │─────LOGIN──────>│ │ │ │ │───AUTH───────────────────────────────>│ │ │<──guac_token_A────────────────────────│ │ │ │ │ │ │──CREATE SESSION──>│ │ │ │ (guac_token_A) │ │ │<─JWT(session_id)│ │ │ │ │ │ │ │──CREATE CONN───>│ │ │ │ │──GET TOKEN───────>│ │ │ │<─guac_token_A─────│ │ │ │────CREATE CONNECTION─────────────────>│ │<─connection_url─│ │ │ │ (token_A) │ │ │ │ │ │ │ │────LOGOUT──────>│ │ │ │ │──DELETE SESSION──>│ │ │ │ │ │ │─────LOGIN──────>│ │ │ │ │───AUTH───────────────────────────────>│ │ │<──guac_token_B────────────────────────│ ← NEW TOKEN! │ │ │ │ │ │──CREATE SESSION──>│ │ │ │ (guac_token_B) │ │ │<─JWT(session_id)│ │ │ │ │ │ │ │──GET CONNECTIONS│ │ │ │ │──GET TOKEN───────>│ │ │ │<─guac_token_B─────│ ✅ CURRENT TOKEN │ │<─URLs (token_B) │ │ │ │ ✅ РАБОТАЕТ! │ │ │ ``` --- ## Применение исправления ```bash cd GuacamoleRemoteAccess docker-compose restart api ``` --- ## Проверка После применения исправления: 1. ✅ Логинитесь в клиент 2. ✅ Создайте подключение к любой машине 3. ✅ Сделайте logout 4. ✅ Залогиньтесь снова 5. ✅ Восстановите подключение 6. ✅ Создайте новое подключение к другой машине 7. ✅ Переключайтесь между подключениями → **403 ошибки больше нет!** 🎉 --- ## Технические детали ### JWT Payload (после логина) ```json { "username": "user", "role": "USER", "permissions": [], "session_id": "4edb****************************8c45", "token_type": "access", "exp": 1730721881, "iat": 1730718281, "iss": "remote-access-api" } ``` **Замечание:** `guac_token` НЕ хранится в JWT по соображениям безопасности. ### Redis Session (при логине) ```json { "user_info": { "username": "user", "role": "USER", "permissions": [] }, "guac_token": "589f****************************6edc", "ecdh_session_id": "abc123...", "created_at": "2025-11-04T14:24:41.123456Z", "expires_at": "2025-11-04T15:24:41.123456Z" } ``` --- ## Backwards Compatibility Исправление поддерживает старые JWT токены (если они содержат `guac_token` напрямую): ```python else: # Старый формат JWT с guac_token напрямую (backwards compatibility) user_token = jwt_payload.get("guac_token") ``` Это позволяет избежать проблем при rolling deployment. --- ## Связанные файлы - `GuacamoleRemoteAccess/api/auth/middleware.py` - middleware для извлечения токена - `GuacamoleRemoteAccess/api/auth/utils.py` - создание JWT с `session_id` - `GuacamoleRemoteAccess/api/auth/guacamole_auth.py` - создание сессий в Redis - `GuacamoleRemoteAccess/api/auth/session_storage.py` - хранилище сессий - `GuacamoleRemoteAccess/api/main.py` - endpoint `/connections` для восстановления --- ## Дата исправления 2025-11-04