5d94572569
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>
83 lines
2.5 KiB
PHP
83 lines
2.5 KiB
PHP
<?php
|
|
/**
|
|
* GeoGraSim — Redis-Session/Refresh-Store (predis)
|
|
* ------------------------------------------------
|
|
* Hält die WIDERRUFBARE Session hinter dem kurzlebigen JWT. Der Access-Token
|
|
* trägt nur die Session-ID (`sid`); die eigentliche Session lebt hier in Redis:
|
|
*
|
|
* sess:<sid> → JSON { typ, sub, class_id?, roles, created } (TTL, Sliding)
|
|
*
|
|
* Widerruf (Logout, Bann, „alle Geräte abmelden", Passwort-Wechsel) = DEL.
|
|
* Da der Access-Token kurz lebt (JWT_TTL), wirkt der Widerruf binnen Minuten.
|
|
*
|
|
* Reine Bibliothek — wird erst im Cutover genutzt. predis ist pure PHP,
|
|
* braucht KEINE Redis-Extension (läuft auf XAMPP wie im Prod-Image).
|
|
*/
|
|
|
|
use Predis\Client as PredisClient;
|
|
|
|
class SessionStore
|
|
{
|
|
private static ?PredisClient $client = null;
|
|
|
|
public static function client(): PredisClient
|
|
{
|
|
if (self::$client === null) {
|
|
$url = defined('REDIS_URL') ? REDIS_URL : 'tcp://127.0.0.1:6379';
|
|
self::$client = new PredisClient($url);
|
|
}
|
|
return self::$client;
|
|
}
|
|
|
|
private static function key(string $sid): string
|
|
{
|
|
return 'sess:' . $sid;
|
|
}
|
|
|
|
private static function ttl(): int
|
|
{
|
|
return defined('JWT_REFRESH_TTL') ? JWT_REFRESH_TTL : 2592000;
|
|
}
|
|
|
|
/** Opake 256-bit Session-ID. */
|
|
public static function newSid(): string
|
|
{
|
|
return bin2hex(random_bytes(32));
|
|
}
|
|
|
|
/** Neue Session anlegen → sid. */
|
|
public static function create(array $data, ?int $ttl = null): string
|
|
{
|
|
$sid = self::newSid();
|
|
$data['created'] = time();
|
|
self::client()->setex(self::key($sid), $ttl ?? self::ttl(), json_encode($data));
|
|
return $sid;
|
|
}
|
|
|
|
/** Session lesen (null wenn widerrufen/abgelaufen). Sliding-Expiry per default. */
|
|
public static function get(string $sid, bool $touch = true): ?array
|
|
{
|
|
$raw = self::client()->get(self::key($sid));
|
|
if ($raw === null) return null;
|
|
if ($touch) self::client()->expire(self::key($sid), self::ttl());
|
|
$data = json_decode($raw, true);
|
|
return is_array($data) ? $data : null;
|
|
}
|
|
|
|
/** Widerruf (Logout / Bann). */
|
|
public static function revoke(string $sid): void
|
|
{
|
|
self::client()->del([self::key($sid)]);
|
|
}
|
|
|
|
/** Verbindungstest — für Health-Check / Setup-Verifikation. */
|
|
public static function ping(): bool
|
|
{
|
|
try {
|
|
return (string) self::client()->ping() !== '';
|
|
} catch (\Throwable $e) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|