Auth-Fundament: JWT + Redis (Phase 1, non-breaking)

Vorbereitung für pod-unabhängige Sessions (k8s-Ziel). Ändert am Live-System
NICHTS: AUTH_BACKEND='session' bleibt Default, die neuen Libs sind noch
nirgends verdrahtet.

- composer eingeführt: firebase/php-jwt ^6.11 + predis/predis ^2.4 (pure PHP,
  keine Redis-Extension nötig → läuft auf XAMPP wie im Prod-Image). vendor/
  committed (kein Composer auf dem Server).
- Jwt.php: HS256-Access-Token (issue/verify), decode() IMMER mit explizitem
  Algorithmus (mitigiert Algorithmus-Confusion). 8/8 Tests grün.
- SessionStore.php: predis-gestützter Session/Refresh-Store (sess:<sid>,
  Sliding-TTL, revoke=DEL) — die widerrufbare Session hinter dem kurzen JWT.
- app.php: vendor/autoload (guarded), Konstanten AUTH_BACKEND (Default session),
  REDIS_URL, JWT_TTL, JWT_REFRESH_TTL. JWT_SECRET bewusst ohne Default.
- docker-compose.dev.yml: Redis (+ redis-commander UI) für lokale Dev-Parität.
- .env.example um Auth-Block ergänzt.

Nächste Phasen: $_SESSION-Zugriffe (293) durch Session:: kanalisieren →
Backend-Umschalter in Session.php → doppelgleisiger Cutover → Reporting auf
student_id-FK ziehen.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-08-18 16:21:27 +02:00
parent db4b75c5e6
commit 5d94572569
585 changed files with 39582 additions and 0 deletions
+18
View File
@@ -38,6 +38,13 @@ if (strpos($host, 'geograsim.at') !== false) {
}
define('APP_ROOT', dirname(__DIR__, 2));
// Composer-Autoloader (firebase/php-jwt, predis). Guard: erst ab dem
// JWT/Redis-Umbau vorhanden; fehlt er, läuft die App im Legacy-Session-Modus
// unverändert weiter.
if (is_file(APP_ROOT . '/vendor/autoload.php')) {
require_once APP_ROOT . '/vendor/autoload.php';
}
// Plattform-Limits — eine einzelne Sim-Session darf nie länger als 120 min
// dauern. Schützt DB vor absurden Werten (Tab über Nacht offen, Bug im
// Frontend-Zeit-Tracking, etc.). Backend-Cap ist defensive Garantie:
@@ -73,6 +80,17 @@ if (!defined('DB_USER')) define('DB_USER', 'root');
if (!defined('DB_PASS')) define('DB_PASS', '');
if (!defined('DB_PORT')) define('DB_PORT', '3306');
// ── Auth-Backend (JWT + Redis Migration) ────────────────────────────────
// 'session' = Legacy (native PHP-Session-Dateien, wie bisher) — DEFAULT,
// damit sich am Live-System NICHTS ändert, bis der Cutover steht.
// 'jwt' = JWT-Access-Token (HttpOnly-Cookie) + Redis-Session/Refresh.
if (!defined('AUTH_BACKEND')) define('AUTH_BACKEND', 'session');
if (!defined('REDIS_URL')) define('REDIS_URL', 'tcp://127.0.0.1:6379');
if (!defined('JWT_TTL')) define('JWT_TTL', 900); // Access-Token: 15 min
if (!defined('JWT_REFRESH_TTL')) define('JWT_REFRESH_TTL', 2592000); // Refresh/Session: 30 Tage
// JWT_SECRET hat BEWUSST keinen Default — muss in .env gesetzt sein, sobald
// AUTH_BACKEND='jwt'. Jwt.php wirft sonst eine klare Exception.
/**
* Cockpit-URL je nach Login-Status:
* - Lehrkraft eingeloggt → /teacher