Enable Discovery debug logging for production troubleshooting
- Add DISCOVERY_LOG_LEVEL=debug - Add DISCOVERY_SHOW_PROGRESS=true - Temporary changes for debugging InitializerProcessor fixes on production
This commit is contained in:
477
docs/components/analytics/configuration.md
Normal file
477
docs/components/analytics/configuration.md
Normal file
@@ -0,0 +1,477 @@
|
||||
# Analytics Konfiguration
|
||||
|
||||
> **Dokumentationshinweis:** Diese Dokumentation ist vollständig aktualisiert und stellt die aktuelle Implementierung der Analytics-Konfiguration korrekt dar.
|
||||
|
||||
## Übersicht
|
||||
|
||||
Das Analytics Framework bietet umfangreiche Konfigurationsmöglichkeiten, um das Verhalten und die Leistung an die Anforderungen Ihrer Anwendung anzupassen. Diese Dokumentation beschreibt die verfügbaren Konfigurationsoptionen und wie Sie diese anwenden können.
|
||||
|
||||
## Konfigurationsklasse
|
||||
|
||||
Die zentrale Klasse für die Analytics-Konfiguration ist `AnalyticsConfig`. Diese Klasse enthält alle Einstellungen, die das Verhalten des Analytics Frameworks steuern:
|
||||
|
||||
```php
|
||||
use App\Framework\Analytics\AnalyticsConfig;
|
||||
|
||||
// Konfiguration erstellen
|
||||
$config = new AnalyticsConfig(
|
||||
$enabled = true, // Analytics aktivieren/deaktivieren
|
||||
$samplingRate = 1.0, // Sampling-Rate (0.0 - 1.0)
|
||||
$securityAnalyticsEnabled = true, // Sicherheits-Analytics aktivieren
|
||||
$dataPath = '/var/www/html/storage/analytics', // Pfad für Datenspeicherung
|
||||
$bufferSize = 1000, // Puffergröße für Ereignisse
|
||||
$retentionDays = 365, // Aufbewahrungsdauer in Tagen
|
||||
$trackPageViews = true, // Seitenaufrufe erfassen
|
||||
$trackApiCalls = true, // API-Aufrufe erfassen
|
||||
$trackUserActions = true, // Benutzeraktionen erfassen
|
||||
$trackErrors = true, // Fehler erfassen
|
||||
$trackPerformance = true // Leistungsmetriken erfassen
|
||||
);
|
||||
```
|
||||
|
||||
## Standardkonfiguration
|
||||
|
||||
Die `AnalyticsConfig`-Klasse bietet eine Standardkonfiguration, die für die meisten Anwendungen geeignet ist:
|
||||
|
||||
```php
|
||||
// Standardkonfiguration verwenden
|
||||
$config = AnalyticsConfig::default();
|
||||
```
|
||||
|
||||
Die Standardkonfiguration aktiviert alle Features mit sinnvollen Standardwerten:
|
||||
- Analytics aktiviert
|
||||
- 100% Sampling (alle Daten werden erfasst)
|
||||
- Sicherheits-Analytics aktiviert
|
||||
- 1000 Ereignisse im Puffer
|
||||
- 365 Tage Aufbewahrungsdauer
|
||||
- Alle Tracking-Typen aktiviert
|
||||
|
||||
## Umgebungsvariablen
|
||||
|
||||
Die Konfiguration kann auch über Umgebungsvariablen angepasst werden:
|
||||
|
||||
```php
|
||||
// In der AnalyticsConfig-Klasse
|
||||
public static function default(): self
|
||||
{
|
||||
return new self(
|
||||
enabled: filter_var($_ENV['ANALYTICS_ENABLED'] ?? true, FILTER_VALIDATE_BOOLEAN),
|
||||
samplingRate: (float)($_ENV['ANALYTICS_SAMPLING_RATE'] ?? 1.0),
|
||||
securityAnalyticsEnabled: filter_var($_ENV['SECURITY_ANALYTICS_ENABLED'] ?? true, FILTER_VALIDATE_BOOLEAN),
|
||||
dataPath: $_ENV['ANALYTICS_DATA_PATH'] ?? '/var/www/html/storage/analytics',
|
||||
bufferSize: (int)($_ENV['ANALYTICS_BUFFER_SIZE'] ?? 1000),
|
||||
retentionDays: (int)($_ENV['ANALYTICS_RETENTION_DAYS'] ?? 365),
|
||||
trackPageViews: filter_var($_ENV['ANALYTICS_TRACK_PAGE_VIEWS'] ?? true, FILTER_VALIDATE_BOOLEAN),
|
||||
trackApiCalls: filter_var($_ENV['ANALYTICS_TRACK_API_CALLS'] ?? true, FILTER_VALIDATE_BOOLEAN),
|
||||
trackUserActions: filter_var($_ENV['ANALYTICS_TRACK_USER_ACTIONS'] ?? true, FILTER_VALIDATE_BOOLEAN),
|
||||
trackErrors: filter_var($_ENV['ANALYTICS_TRACK_ERRORS'] ?? true, FILTER_VALIDATE_BOOLEAN),
|
||||
trackPerformance: filter_var($_ENV['ANALYTICS_TRACK_PERFORMANCE'] ?? true, FILTER_VALIDATE_BOOLEAN)
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
Beispiel für eine `.env`-Datei:
|
||||
|
||||
```
|
||||
ANALYTICS_ENABLED=true
|
||||
ANALYTICS_SAMPLING_RATE=0.1
|
||||
SECURITY_ANALYTICS_ENABLED=true
|
||||
ANALYTICS_DATA_PATH=/var/www/html/storage/analytics
|
||||
ANALYTICS_BUFFER_SIZE=1000
|
||||
ANALYTICS_RETENTION_DAYS=365
|
||||
ANALYTICS_TRACK_PAGE_VIEWS=true
|
||||
ANALYTICS_TRACK_API_CALLS=true
|
||||
ANALYTICS_TRACK_USER_ACTIONS=true
|
||||
ANALYTICS_TRACK_ERRORS=true
|
||||
ANALYTICS_TRACK_PERFORMANCE=true
|
||||
```
|
||||
|
||||
## Konfigurationsoptionen im Detail
|
||||
|
||||
### Grundlegende Optionen
|
||||
|
||||
#### enabled
|
||||
|
||||
Aktiviert oder deaktiviert das gesamte Analytics Framework:
|
||||
|
||||
```php
|
||||
$config = new AnalyticsConfig(
|
||||
enabled: true, // Analytics aktivieren
|
||||
// ... weitere Optionen
|
||||
);
|
||||
|
||||
// oder
|
||||
|
||||
$config = new AnalyticsConfig(
|
||||
enabled: false, // Analytics deaktivieren
|
||||
// ... weitere Optionen
|
||||
);
|
||||
```
|
||||
|
||||
Wenn deaktiviert, werden keine Daten erfasst, und alle Aufrufe an den `AnalyticsCollector` werden ignoriert.
|
||||
|
||||
#### samplingRate
|
||||
|
||||
Definiert den Prozentsatz der Daten, die erfasst werden sollen:
|
||||
|
||||
```php
|
||||
$config = new AnalyticsConfig(
|
||||
enabled: true,
|
||||
samplingRate: 1.0, // 100% der Daten erfassen
|
||||
// ... weitere Optionen
|
||||
);
|
||||
|
||||
// oder
|
||||
|
||||
$config = new AnalyticsConfig(
|
||||
enabled: true,
|
||||
samplingRate: 0.1, // 10% der Daten erfassen
|
||||
// ... weitere Optionen
|
||||
);
|
||||
```
|
||||
|
||||
Die Sampling-Rate ist nützlich, um die Datenmenge und die Serverbelastung bei hohem Datenaufkommen zu reduzieren. Ein Wert von 0.1 bedeutet, dass nur 10% aller Ereignisse erfasst werden.
|
||||
|
||||
#### securityAnalyticsEnabled
|
||||
|
||||
Aktiviert oder deaktiviert die Erfassung von Sicherheitsereignissen:
|
||||
|
||||
```php
|
||||
$config = new AnalyticsConfig(
|
||||
enabled: true,
|
||||
samplingRate: 1.0,
|
||||
securityAnalyticsEnabled: true, // Sicherheits-Analytics aktivieren
|
||||
// ... weitere Optionen
|
||||
);
|
||||
```
|
||||
|
||||
Wenn aktiviert, werden Sicherheitsereignisse (wie Anmeldeversuche, CSRF-Angriffe, WAF-Blockierungen) erfasst und analysiert.
|
||||
|
||||
### Speicheroptionen
|
||||
|
||||
#### dataPath
|
||||
|
||||
Definiert den Pfad, in dem Analytics-Daten gespeichert werden:
|
||||
|
||||
```php
|
||||
$config = new AnalyticsConfig(
|
||||
// ... vorherige Optionen
|
||||
dataPath: '/var/www/html/storage/analytics',
|
||||
// ... weitere Optionen
|
||||
);
|
||||
```
|
||||
|
||||
Stellen Sie sicher, dass dieser Pfad existiert und für die Anwendung beschreibbar ist.
|
||||
|
||||
#### bufferSize
|
||||
|
||||
Definiert die Anzahl der Ereignisse, die im Speicher gepuffert werden, bevor sie auf die Festplatte geschrieben werden:
|
||||
|
||||
```php
|
||||
$config = new AnalyticsConfig(
|
||||
// ... vorherige Optionen
|
||||
bufferSize: 1000, // 1000 Ereignisse puffern
|
||||
// ... weitere Optionen
|
||||
);
|
||||
```
|
||||
|
||||
Ein größerer Puffer reduziert die Anzahl der Schreibvorgänge, erhöht aber den Speicherverbrauch und das Risiko von Datenverlust bei einem Absturz.
|
||||
|
||||
#### retentionDays
|
||||
|
||||
Definiert die Anzahl der Tage, für die Analytics-Daten aufbewahrt werden:
|
||||
|
||||
```php
|
||||
$config = new AnalyticsConfig(
|
||||
// ... vorherige Optionen
|
||||
retentionDays: 365, // Daten für 1 Jahr aufbewahren
|
||||
// ... weitere Optionen
|
||||
);
|
||||
```
|
||||
|
||||
Ältere Daten werden automatisch gelöscht, um Speicherplatz zu sparen.
|
||||
|
||||
### Tracking-Optionen
|
||||
|
||||
#### trackPageViews
|
||||
|
||||
Aktiviert oder deaktiviert die Erfassung von Seitenaufrufen:
|
||||
|
||||
```php
|
||||
$config = new AnalyticsConfig(
|
||||
// ... vorherige Optionen
|
||||
trackPageViews: true, // Seitenaufrufe erfassen
|
||||
// ... weitere Optionen
|
||||
);
|
||||
```
|
||||
|
||||
#### trackApiCalls
|
||||
|
||||
Aktiviert oder deaktiviert die Erfassung von API-Aufrufen:
|
||||
|
||||
```php
|
||||
$config = new AnalyticsConfig(
|
||||
// ... vorherige Optionen
|
||||
trackApiCalls: true, // API-Aufrufe erfassen
|
||||
// ... weitere Optionen
|
||||
);
|
||||
```
|
||||
|
||||
#### trackUserActions
|
||||
|
||||
Aktiviert oder deaktiviert die Erfassung von Benutzeraktionen:
|
||||
|
||||
```php
|
||||
$config = new AnalyticsConfig(
|
||||
// ... vorherige Optionen
|
||||
trackUserActions: true, // Benutzeraktionen erfassen
|
||||
// ... weitere Optionen
|
||||
);
|
||||
```
|
||||
|
||||
#### trackErrors
|
||||
|
||||
Aktiviert oder deaktiviert die Erfassung von Fehlern:
|
||||
|
||||
```php
|
||||
$config = new AnalyticsConfig(
|
||||
// ... vorherige Optionen
|
||||
trackErrors: true, // Fehler erfassen
|
||||
// ... weitere Optionen
|
||||
);
|
||||
```
|
||||
|
||||
#### trackPerformance
|
||||
|
||||
Aktiviert oder deaktiviert die Erfassung von Leistungsmetriken:
|
||||
|
||||
```php
|
||||
$config = new AnalyticsConfig(
|
||||
// ... vorherige Optionen
|
||||
trackPerformance: true, // Leistungsmetriken erfassen
|
||||
);
|
||||
```
|
||||
|
||||
## Konfiguration in der Anwendung
|
||||
|
||||
### Konfigurationsdatei
|
||||
|
||||
Die Analytics-Konfiguration kann in einer dedizierten Konfigurationsdatei definiert werden:
|
||||
|
||||
```php
|
||||
// config/analytics.php
|
||||
return [
|
||||
'enabled' => true,
|
||||
'sampling_rate' => 1.0,
|
||||
'security_analytics_enabled' => true,
|
||||
'data_path' => '/var/www/html/storage/analytics',
|
||||
'buffer_size' => 1000,
|
||||
'retention_days' => 365,
|
||||
'track_page_views' => true,
|
||||
'track_api_calls' => true,
|
||||
'track_user_actions' => true,
|
||||
'track_errors' => true,
|
||||
'track_performance' => true,
|
||||
];
|
||||
```
|
||||
|
||||
### Dependency Injection
|
||||
|
||||
Die Konfiguration kann über Dependency Injection in die Anwendung eingebunden werden:
|
||||
|
||||
```php
|
||||
// In einem Service Provider oder Container-Konfiguration
|
||||
$container->set(AnalyticsConfig::class, function () {
|
||||
return new AnalyticsConfig(
|
||||
enabled: true,
|
||||
samplingRate: 0.1,
|
||||
// ... weitere Optionen
|
||||
);
|
||||
});
|
||||
```
|
||||
|
||||
### Laufzeitkonfiguration
|
||||
|
||||
Die Konfiguration kann zur Laufzeit angepasst werden:
|
||||
|
||||
```php
|
||||
// Analytics-Konfiguration aktualisieren
|
||||
$newConfig = new AnalyticsConfig(
|
||||
enabled: true,
|
||||
samplingRate: 0.5, // Sampling-Rate anpassen
|
||||
// ... weitere Optionen
|
||||
);
|
||||
|
||||
// Konfiguration aktualisieren
|
||||
$analyticsManager->updateConfig($newConfig);
|
||||
```
|
||||
|
||||
## Umgebungsspezifische Konfiguration
|
||||
|
||||
Es ist oft sinnvoll, unterschiedliche Konfigurationen für verschiedene Umgebungen zu verwenden:
|
||||
|
||||
### Produktionsumgebung
|
||||
|
||||
```php
|
||||
// Produktionskonfiguration
|
||||
$config = new AnalyticsConfig(
|
||||
enabled: true,
|
||||
samplingRate: 0.1, // Reduzierte Sampling-Rate für hohe Lasten
|
||||
securityAnalyticsEnabled: true,
|
||||
dataPath: '/var/www/html/storage/analytics',
|
||||
bufferSize: 5000, // Größerer Puffer für bessere Leistung
|
||||
retentionDays: 365,
|
||||
trackPageViews: true,
|
||||
trackApiCalls: true,
|
||||
trackUserActions: true,
|
||||
trackErrors: true,
|
||||
trackPerformance: true
|
||||
);
|
||||
```
|
||||
|
||||
### Entwicklungsumgebung
|
||||
|
||||
```php
|
||||
// Entwicklungskonfiguration
|
||||
$config = new AnalyticsConfig(
|
||||
enabled: true,
|
||||
samplingRate: 1.0, // Alle Daten erfassen für einfachere Fehlersuche
|
||||
securityAnalyticsEnabled: true,
|
||||
dataPath: '/var/www/html/storage/analytics',
|
||||
bufferSize: 100, // Kleinerer Puffer für sofortiges Feedback
|
||||
retentionDays: 30, // Kürzere Aufbewahrungsdauer
|
||||
trackPageViews: true,
|
||||
trackApiCalls: true,
|
||||
trackUserActions: true,
|
||||
trackErrors: true,
|
||||
trackPerformance: true
|
||||
);
|
||||
```
|
||||
|
||||
### Testumgebung
|
||||
|
||||
```php
|
||||
// Testkonfiguration
|
||||
$config = new AnalyticsConfig(
|
||||
enabled: false, // Deaktiviert für Tests
|
||||
samplingRate: 1.0,
|
||||
securityAnalyticsEnabled: false,
|
||||
dataPath: '/tmp/analytics',
|
||||
bufferSize: 10,
|
||||
retentionDays: 1,
|
||||
trackPageViews: false,
|
||||
trackApiCalls: false,
|
||||
trackUserActions: false,
|
||||
trackErrors: false,
|
||||
trackPerformance: false
|
||||
);
|
||||
```
|
||||
|
||||
## Leistungsoptimierung
|
||||
|
||||
### Sampling für hohe Lasten
|
||||
|
||||
Bei hohem Datenaufkommen kann die Sampling-Rate reduziert werden, um die Serverbelastung zu verringern:
|
||||
|
||||
```php
|
||||
$config = new AnalyticsConfig(
|
||||
enabled: true,
|
||||
samplingRate: 0.01, // Nur 1% der Daten erfassen
|
||||
// ... weitere Optionen
|
||||
);
|
||||
```
|
||||
|
||||
### Pufferoptimierung
|
||||
|
||||
Die Puffergröße kann angepasst werden, um die Anzahl der Schreibvorgänge zu optimieren:
|
||||
|
||||
```php
|
||||
$config = new AnalyticsConfig(
|
||||
// ... vorherige Optionen
|
||||
bufferSize: 10000, // Größerer Puffer für weniger Schreibvorgänge
|
||||
// ... weitere Optionen
|
||||
);
|
||||
```
|
||||
|
||||
### Selektives Tracking
|
||||
|
||||
Nicht alle Tracking-Typen müssen aktiviert sein. Deaktivieren Sie die Typen, die Sie nicht benötigen:
|
||||
|
||||
```php
|
||||
$config = new AnalyticsConfig(
|
||||
// ... vorherige Optionen
|
||||
trackPageViews: true,
|
||||
trackApiCalls: true,
|
||||
trackUserActions: false, // Benutzeraktionen deaktivieren
|
||||
trackErrors: true,
|
||||
trackPerformance: false // Leistungsmetriken deaktivieren
|
||||
);
|
||||
```
|
||||
|
||||
## Fehlerbehebung
|
||||
|
||||
### Häufige Probleme
|
||||
|
||||
#### Keine Daten werden erfasst
|
||||
|
||||
Mögliche Ursachen:
|
||||
- Analytics ist deaktiviert (`enabled = false`)
|
||||
- Sampling-Rate ist zu niedrig
|
||||
- Fehler beim Speichern der Daten
|
||||
|
||||
Lösung:
|
||||
- Überprüfen Sie die Konfiguration
|
||||
- Erhöhen Sie die Sampling-Rate
|
||||
- Überprüfen Sie die Logs auf Fehler
|
||||
- Stellen Sie sicher, dass der Datenpfad existiert und beschreibbar ist
|
||||
|
||||
```php
|
||||
// Überprüfen Sie die Konfiguration
|
||||
var_dump($analyticsConfig);
|
||||
|
||||
// Erhöhen Sie die Sampling-Rate
|
||||
$newConfig = new AnalyticsConfig(
|
||||
enabled: true,
|
||||
samplingRate: 1.0, // 100% der Daten erfassen
|
||||
// ... weitere Optionen
|
||||
);
|
||||
|
||||
// Überprüfen Sie die Berechtigungen des Datenpfads
|
||||
if (!is_writable($analyticsConfig->dataPath)) {
|
||||
echo "Datenpfad ist nicht beschreibbar: " . $analyticsConfig->dataPath;
|
||||
}
|
||||
```
|
||||
|
||||
#### Hohe Serverbelastung
|
||||
|
||||
Mögliche Ursachen:
|
||||
- Zu viele Ereignisse werden erfasst
|
||||
- Puffergröße ist zu klein
|
||||
- Synchrone Verarbeitung bei hohem Datenaufkommen
|
||||
|
||||
Lösung:
|
||||
- Reduzieren Sie die Sampling-Rate
|
||||
- Erhöhen Sie die Puffergröße
|
||||
- Verwenden Sie asynchrone Verarbeitung
|
||||
|
||||
```php
|
||||
// Reduzieren Sie die Sampling-Rate
|
||||
$newConfig = new AnalyticsConfig(
|
||||
enabled: true,
|
||||
samplingRate: 0.1, // Nur 10% der Daten erfassen
|
||||
// ... weitere Optionen
|
||||
);
|
||||
|
||||
// Erhöhen Sie die Puffergröße
|
||||
$newConfig = new AnalyticsConfig(
|
||||
// ... vorherige Optionen
|
||||
bufferSize: 10000, // Größerer Puffer
|
||||
// ... weitere Optionen
|
||||
);
|
||||
```
|
||||
|
||||
## Weiterführende Informationen
|
||||
|
||||
- [Analytics Framework Übersicht](index.md)
|
||||
- [Analytics Beispiele](examples.md)
|
||||
- [Performance-Monitoring](/components/performance/index.md)
|
||||
760
docs/components/analytics/examples.md
Normal file
760
docs/components/analytics/examples.md
Normal file
@@ -0,0 +1,760 @@
|
||||
# Analytics Framework Beispiele
|
||||
|
||||
> **Dokumentationshinweis:** Diese Dokumentation ist vollständig aktualisiert und stellt die aktuelle Implementierung des Analytics Frameworks korrekt dar.
|
||||
|
||||
## Übersicht
|
||||
|
||||
Diese Dokumentation enthält praktische Beispiele für die Verwendung des Analytics Frameworks in verschiedenen Szenarien. Die Beispiele zeigen, wie Sie Analytics-Daten erfassen, analysieren und in Ihre Anwendung integrieren können.
|
||||
|
||||
## Grundlegende Verwendung
|
||||
|
||||
### Benutzeraktionen erfassen
|
||||
|
||||
```php
|
||||
use App\Framework\Analytics\AnalyticsCollector;
|
||||
use App\Framework\Analytics\AnalyticsCategory;
|
||||
|
||||
class UserController
|
||||
{
|
||||
public function __construct(
|
||||
private readonly AnalyticsCollector $analyticsCollector
|
||||
) {}
|
||||
|
||||
public function handleLogin(Request $request): Response
|
||||
{
|
||||
// Login-Logik
|
||||
$success = $this->authService->login($request->getParam('email'), $request->getParam('password'));
|
||||
|
||||
// Login-Aktion erfassen
|
||||
$this->analyticsCollector->trackAction(
|
||||
'user_login',
|
||||
AnalyticsCategory::USER,
|
||||
[
|
||||
'success' => $success,
|
||||
'email_domain' => explode('@', $request->getParam('email'))[1] ?? 'unknown',
|
||||
'ip' => $request->getClientIp(),
|
||||
'user_agent' => $request->getUserAgent()
|
||||
]
|
||||
);
|
||||
|
||||
// Antwort zurückgeben
|
||||
return $success
|
||||
? $this->redirect('/dashboard')
|
||||
: $this->renderForm('login', ['error' => 'Ungültige Anmeldedaten']);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Seitenaufrufe erfassen
|
||||
|
||||
```php
|
||||
use App\Framework\Analytics\AnalyticsCollector;
|
||||
|
||||
class ProductController
|
||||
{
|
||||
public function __construct(
|
||||
private readonly AnalyticsCollector $analyticsCollector,
|
||||
private readonly ProductRepository $productRepository
|
||||
) {}
|
||||
|
||||
public function showProduct(Request $request, string $productId): Response
|
||||
{
|
||||
$product = $this->productRepository->find($productId);
|
||||
|
||||
if (!$product) {
|
||||
return $this->notFound();
|
||||
}
|
||||
|
||||
// Seitenaufruf erfassen
|
||||
$this->analyticsCollector->trackPageView(
|
||||
"/products/{$productId}",
|
||||
[
|
||||
'product_id' => $productId,
|
||||
'product_name' => $product->name,
|
||||
'product_category' => $product->category,
|
||||
'referrer' => $request->getHeader('Referer'),
|
||||
'user_id' => $this->session->get('user_id')
|
||||
]
|
||||
);
|
||||
|
||||
return $this->render('product', ['product' => $product]);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Fehler erfassen
|
||||
|
||||
```php
|
||||
use App\Framework\Analytics\AnalyticsCollector;
|
||||
|
||||
class ErrorHandler
|
||||
{
|
||||
public function __construct(
|
||||
private readonly AnalyticsCollector $analyticsCollector,
|
||||
private readonly Logger $logger
|
||||
) {}
|
||||
|
||||
public function handleException(\Throwable $exception): Response
|
||||
{
|
||||
// Fehler loggen
|
||||
$this->logger->error('Unbehandelte Ausnahme: ' . $exception->getMessage(), [
|
||||
'exception' => $exception
|
||||
]);
|
||||
|
||||
// Fehler für Analytics erfassen
|
||||
$this->analyticsCollector->trackError(
|
||||
get_class($exception),
|
||||
[
|
||||
'message' => $exception->getMessage(),
|
||||
'file' => $exception->getFile(),
|
||||
'line' => $exception->getLine(),
|
||||
'trace' => $exception->getTraceAsString(),
|
||||
'request_uri' => $_SERVER['REQUEST_URI'] ?? null,
|
||||
'user_id' => $this->session->get('user_id')
|
||||
]
|
||||
);
|
||||
|
||||
// Fehlerseite anzeigen
|
||||
return new Response(500, [], 'Ein Fehler ist aufgetreten');
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Geschäftsereignisse erfassen
|
||||
|
||||
```php
|
||||
use App\Framework\Analytics\AnalyticsCollector;
|
||||
|
||||
class OrderService
|
||||
{
|
||||
public function __construct(
|
||||
private readonly AnalyticsCollector $analyticsCollector,
|
||||
private readonly OrderRepository $orderRepository
|
||||
) {}
|
||||
|
||||
public function placeOrder(Order $order): bool
|
||||
{
|
||||
// Bestellung speichern
|
||||
$success = $this->orderRepository->save($order);
|
||||
|
||||
if ($success) {
|
||||
// Geschäftsereignis erfassen
|
||||
$this->analyticsCollector->trackBusinessEvent(
|
||||
'order_placed',
|
||||
[
|
||||
'order_id' => $order->id,
|
||||
'user_id' => $order->userId,
|
||||
'total_amount' => $order->totalAmount,
|
||||
'items_count' => count($order->items),
|
||||
'payment_method' => $order->paymentMethod,
|
||||
'shipping_country' => $order->shippingAddress->country
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
return $success;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### API-Aufrufe erfassen
|
||||
|
||||
```php
|
||||
use App\Framework\Analytics\AnalyticsCollector;
|
||||
|
||||
class ApiClient
|
||||
{
|
||||
public function __construct(
|
||||
private readonly AnalyticsCollector $analyticsCollector,
|
||||
private readonly HttpClient $httpClient
|
||||
) {}
|
||||
|
||||
public function sendRequest(string $endpoint, string $method, array $data = []): Response
|
||||
{
|
||||
$startTime = microtime(true);
|
||||
|
||||
try {
|
||||
$response = $this->httpClient->request($method, $endpoint, $data);
|
||||
$statusCode = $response->getStatusCode();
|
||||
$success = $statusCode >= 200 && $statusCode < 300;
|
||||
} catch (\Exception $e) {
|
||||
$statusCode = 0;
|
||||
$success = false;
|
||||
}
|
||||
|
||||
$duration = (microtime(true) - $startTime) * 1000; // in ms
|
||||
|
||||
// API-Aufruf erfassen
|
||||
$this->analyticsCollector->trackApiCall(
|
||||
$endpoint,
|
||||
$method,
|
||||
$statusCode,
|
||||
[
|
||||
'duration' => $duration,
|
||||
'success' => $success,
|
||||
'data_size' => strlen(json_encode($data)),
|
||||
'user_id' => $this->session->get('user_id')
|
||||
]
|
||||
);
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Fortgeschrittene Verwendung
|
||||
|
||||
### Automatische Erfassung mit Middleware
|
||||
|
||||
Die `AnalyticsMiddleware` erfasst automatisch Seitenaufrufe und API-Aufrufe:
|
||||
|
||||
```php
|
||||
use App\Framework\Analytics\Middleware\AnalyticsMiddleware;
|
||||
|
||||
// In der Bootstrap-Datei oder Router-Konfiguration
|
||||
$app->addMiddleware(AnalyticsMiddleware::class);
|
||||
```
|
||||
|
||||
Anpassung der Middleware für bestimmte Routen:
|
||||
|
||||
```php
|
||||
use App\Framework\Analytics\Middleware\AnalyticsMiddleware;
|
||||
use App\Framework\Http\MiddlewareContext;
|
||||
use App\Framework\Http\Next;
|
||||
|
||||
class CustomAnalyticsMiddleware extends AnalyticsMiddleware
|
||||
{
|
||||
public function __invoke(MiddlewareContext $context, Next $next): MiddlewareContext
|
||||
{
|
||||
$request = $context->request;
|
||||
|
||||
// Bestimmte Routen ausschließen
|
||||
if (str_starts_with($request->path, '/api/health') || str_starts_with($request->path, '/static/')) {
|
||||
return $next($context);
|
||||
}
|
||||
|
||||
// Zusätzliche Daten für bestimmte Routen hinzufügen
|
||||
$additionalData = [];
|
||||
if (str_starts_with($request->path, '/products/')) {
|
||||
$productId = substr($request->path, 10);
|
||||
$additionalData['product_id'] = $productId;
|
||||
}
|
||||
|
||||
// Standardverhalten mit zusätzlichen Daten
|
||||
return parent::__invoke($context, $next, $additionalData);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Sicherheitsereignisse erfassen
|
||||
|
||||
Integration mit dem Security-System:
|
||||
|
||||
```php
|
||||
use App\Framework\Analytics\Listeners\SecurityAnalyticsListener;
|
||||
use App\Framework\Analytics\Bridges\SecurityEventBridge;
|
||||
use App\Framework\Security\Events\LoginAttemptEvent;
|
||||
|
||||
// In der Bootstrap-Datei
|
||||
$securityEventBridge = SecurityEventBridge::create(
|
||||
$container->get(SecurityAnalyticsListener::class),
|
||||
$config->securityAnalyticsEnabled
|
||||
);
|
||||
|
||||
$eventDispatcher->addListener(LoginAttemptEvent::class, [$securityEventBridge, 'handleSecurityEvent']);
|
||||
```
|
||||
|
||||
Benutzerdefinierter Security-Event-Listener:
|
||||
|
||||
```php
|
||||
use App\Framework\Analytics\AnalyticsCollector;
|
||||
use App\Framework\Analytics\AnalyticsCategory;
|
||||
use App\Framework\Security\Events\LoginAttemptEvent;
|
||||
|
||||
class LoginAnalyticsListener
|
||||
{
|
||||
public function __construct(
|
||||
private readonly AnalyticsCollector $analyticsCollector
|
||||
) {}
|
||||
|
||||
public function onLoginAttempt(LoginAttemptEvent $event): void
|
||||
{
|
||||
$this->analyticsCollector->trackAction(
|
||||
'login_attempt',
|
||||
AnalyticsCategory::SECURITY,
|
||||
[
|
||||
'username' => $event->getUsername(),
|
||||
'success' => $event->isSuccessful(),
|
||||
'ip' => $event->getIp(),
|
||||
'user_agent' => $event->getUserAgent(),
|
||||
'timestamp' => $event->getTimestamp()->format('Y-m-d H:i:s')
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Asynchrone Datenerfassung
|
||||
|
||||
Für maximale Leistung kann die Analytics-Datenerfassung asynchron erfolgen:
|
||||
|
||||
```php
|
||||
use App\Framework\Analytics\AsyncAnalyticsCollector;
|
||||
use App\Framework\Queue\QueueInterface;
|
||||
|
||||
class AsyncAnalyticsService
|
||||
{
|
||||
public function __construct(
|
||||
private readonly AnalyticsCollector $analyticsCollector,
|
||||
private readonly QueueInterface $queue
|
||||
) {}
|
||||
|
||||
public function initialize(): void
|
||||
{
|
||||
// Asynchronen Collector erstellen
|
||||
$asyncCollector = new AsyncAnalyticsCollector(
|
||||
$this->analyticsCollector,
|
||||
$this->queue
|
||||
);
|
||||
|
||||
// Asynchronen Collector registrieren
|
||||
$this->container->set(AnalyticsCollector::class, $asyncCollector);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Benutzerdefinierte Ereignistypen
|
||||
|
||||
Sie können benutzerdefinierte Ereignistypen für spezifische Anforderungen erstellen:
|
||||
|
||||
```php
|
||||
use App\Framework\Analytics\AnalyticsCollector;
|
||||
use App\Framework\Analytics\AnalyticsCategory;
|
||||
|
||||
class CustomAnalyticsService
|
||||
{
|
||||
public function __construct(
|
||||
private readonly AnalyticsCollector $analyticsCollector
|
||||
) {}
|
||||
|
||||
public function trackProductView(string $productId, string $productName, string $category): void
|
||||
{
|
||||
$this->analyticsCollector->trackAction(
|
||||
'product_view',
|
||||
AnalyticsCategory::BUSINESS,
|
||||
[
|
||||
'product_id' => $productId,
|
||||
'product_name' => $productName,
|
||||
'category' => $category,
|
||||
'timestamp' => time(),
|
||||
'user_id' => $this->session->get('user_id')
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function trackAddToCart(string $productId, int $quantity, float $price): void
|
||||
{
|
||||
$this->analyticsCollector->trackAction(
|
||||
'add_to_cart',
|
||||
AnalyticsCategory::BUSINESS,
|
||||
[
|
||||
'product_id' => $productId,
|
||||
'quantity' => $quantity,
|
||||
'price' => $price,
|
||||
'total' => $quantity * $price,
|
||||
'timestamp' => time(),
|
||||
'user_id' => $this->session->get('user_id')
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Datenanalyse
|
||||
|
||||
### Rohdaten abfragen
|
||||
|
||||
```php
|
||||
use App\Framework\Analytics\Storage\AnalyticsStorage;
|
||||
|
||||
class AnalyticsReportService
|
||||
{
|
||||
public function __construct(
|
||||
private readonly AnalyticsStorage $analyticsStorage
|
||||
) {}
|
||||
|
||||
public function getPageViewsReport(\DateTimeInterface $from, \DateTimeInterface $to): array
|
||||
{
|
||||
// Rohdaten für Seitenaufrufe abrufen
|
||||
$pageViews = $this->analyticsStorage->getRawData(
|
||||
'page_view',
|
||||
$from,
|
||||
$to
|
||||
);
|
||||
|
||||
// Daten nach Seite gruppieren
|
||||
$pageViewsByPath = [];
|
||||
foreach ($pageViews as $pageView) {
|
||||
$path = $pageView['path'] ?? 'unknown';
|
||||
if (!isset($pageViewsByPath[$path])) {
|
||||
$pageViewsByPath[$path] = 0;
|
||||
}
|
||||
$pageViewsByPath[$path]++;
|
||||
}
|
||||
|
||||
// Nach Anzahl sortieren
|
||||
arsort($pageViewsByPath);
|
||||
|
||||
return $pageViewsByPath;
|
||||
}
|
||||
|
||||
public function getUserActivityReport(string $userId, \DateTimeInterface $from, \DateTimeInterface $to): array
|
||||
{
|
||||
// Alle Aktionen für einen bestimmten Benutzer abrufen
|
||||
$actions = $this->analyticsStorage->getRawData(
|
||||
'user_action',
|
||||
$from,
|
||||
$to,
|
||||
['user_id' => $userId]
|
||||
);
|
||||
|
||||
// Nach Zeitstempel sortieren
|
||||
usort($actions, function ($a, $b) {
|
||||
return $a['timestamp'] <=> $b['timestamp'];
|
||||
});
|
||||
|
||||
return $actions;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Aggregierte Daten abfragen
|
||||
|
||||
```php
|
||||
use App\Framework\Analytics\Storage\AnalyticsStorage;
|
||||
|
||||
class PerformanceReportService
|
||||
{
|
||||
public function __construct(
|
||||
private readonly AnalyticsStorage $analyticsStorage
|
||||
) {}
|
||||
|
||||
public function getApiPerformanceReport(\DateTimeInterface $from, \DateTimeInterface $to): array
|
||||
{
|
||||
// Aggregierte Daten für API-Aufrufe abrufen
|
||||
$apiCallStats = $this->analyticsStorage->getAggregatedData(
|
||||
'analytics_api_calls_total',
|
||||
$from,
|
||||
$to
|
||||
);
|
||||
|
||||
// Durchschnittliche Antwortzeit pro Endpunkt berechnen
|
||||
$responseTimeByEndpoint = [];
|
||||
foreach ($apiCallStats as $stat) {
|
||||
$endpoint = $stat['tags']['path'] ?? 'unknown';
|
||||
$method = $stat['tags']['method'] ?? 'unknown';
|
||||
$key = "{$method} {$endpoint}";
|
||||
|
||||
if (!isset($responseTimeByEndpoint[$key])) {
|
||||
$responseTimeByEndpoint[$key] = [
|
||||
'count' => 0,
|
||||
'total_time' => 0,
|
||||
'min_time' => PHP_FLOAT_MAX,
|
||||
'max_time' => 0,
|
||||
];
|
||||
}
|
||||
|
||||
$duration = $stat['tags']['duration'] ?? 0;
|
||||
$responseTimeByEndpoint[$key]['count']++;
|
||||
$responseTimeByEndpoint[$key]['total_time'] += $duration;
|
||||
$responseTimeByEndpoint[$key]['min_time'] = min($responseTimeByEndpoint[$key]['min_time'], $duration);
|
||||
$responseTimeByEndpoint[$key]['max_time'] = max($responseTimeByEndpoint[$key]['max_time'], $duration);
|
||||
}
|
||||
|
||||
// Durchschnitt berechnen
|
||||
foreach ($responseTimeByEndpoint as $key => $data) {
|
||||
$responseTimeByEndpoint[$key]['avg_time'] = $data['total_time'] / $data['count'];
|
||||
}
|
||||
|
||||
// Nach durchschnittlicher Antwortzeit sortieren
|
||||
uasort($responseTimeByEndpoint, function ($a, $b) {
|
||||
return $b['avg_time'] <=> $a['avg_time'];
|
||||
});
|
||||
|
||||
return $responseTimeByEndpoint;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Dashboard-Integration
|
||||
|
||||
```php
|
||||
use App\Framework\Analytics\Dashboard\AnalyticsDashboard;
|
||||
|
||||
class AdminController
|
||||
{
|
||||
public function __construct(
|
||||
private readonly AnalyticsDashboard $dashboard,
|
||||
private readonly TemplateEngine $templateEngine
|
||||
) {}
|
||||
|
||||
public function showDashboard(Request $request): Response
|
||||
{
|
||||
// Zeitraum aus Anfrage oder Standardwerte
|
||||
$from = new \DateTimeImmutable($request->getQueryParam('from', '-7 days'));
|
||||
$to = new \DateTimeImmutable($request->getQueryParam('to', 'now'));
|
||||
|
||||
// Dashboard-Daten abrufen
|
||||
$stats = $this->dashboard->getStats($from, $to);
|
||||
|
||||
// Spezifische Statistiken abrufen
|
||||
$pageViewStats = $this->dashboard->getPageViewStats($from, $to);
|
||||
$errorStats = $this->dashboard->getErrorStats($from, $to);
|
||||
$performanceStats = $this->dashboard->getPerformanceStats($from, $to);
|
||||
$businessStats = $this->dashboard->getBusinessStats($from, $to);
|
||||
|
||||
// Dashboard rendern
|
||||
return $this->templateEngine->render('admin/dashboard', [
|
||||
'stats' => $stats,
|
||||
'pageViewStats' => $pageViewStats,
|
||||
'errorStats' => $errorStats,
|
||||
'performanceStats' => $performanceStats,
|
||||
'businessStats' => $businessStats,
|
||||
'from' => $from,
|
||||
'to' => $to
|
||||
]);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Datenexport
|
||||
|
||||
### CSV-Export
|
||||
|
||||
```php
|
||||
use App\Framework\Analytics\Export\AnalyticsExporter;
|
||||
|
||||
class ExportController
|
||||
{
|
||||
public function __construct(
|
||||
private readonly AnalyticsExporter $exporter
|
||||
) {}
|
||||
|
||||
public function exportPageViews(Request $request): Response
|
||||
{
|
||||
// Zeitraum aus Anfrage
|
||||
$from = new \DateTimeImmutable($request->getQueryParam('from', '-7 days'));
|
||||
$to = new \DateTimeImmutable($request->getQueryParam('to', 'now'));
|
||||
|
||||
// Daten exportieren
|
||||
$csvData = $this->exporter->exportToCsv(
|
||||
'page_view',
|
||||
$from,
|
||||
$to
|
||||
);
|
||||
|
||||
// CSV-Datei zurückgeben
|
||||
return new Response(
|
||||
200,
|
||||
[
|
||||
'Content-Type' => 'text/csv',
|
||||
'Content-Disposition' => 'attachment; filename="page_views.csv"'
|
||||
],
|
||||
$csvData
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### JSON-Export
|
||||
|
||||
```php
|
||||
use App\Framework\Analytics\Export\AnalyticsExporter;
|
||||
|
||||
class ApiController
|
||||
{
|
||||
public function __construct(
|
||||
private readonly AnalyticsExporter $exporter
|
||||
) {}
|
||||
|
||||
public function getAnalyticsData(Request $request): Response
|
||||
{
|
||||
// Parameter aus Anfrage
|
||||
$eventType = $request->getQueryParam('type', 'page_view');
|
||||
$from = new \DateTimeImmutable($request->getQueryParam('from', '-7 days'));
|
||||
$to = new \DateTimeImmutable($request->getQueryParam('to', 'now'));
|
||||
|
||||
// Daten exportieren
|
||||
$data = $this->exporter->exportToArray(
|
||||
$eventType,
|
||||
$from,
|
||||
$to
|
||||
);
|
||||
|
||||
// JSON-Daten zurückgeben
|
||||
return new Response(
|
||||
200,
|
||||
['Content-Type' => 'application/json'],
|
||||
json_encode($data)
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Integration mit anderen Systemen
|
||||
|
||||
### Event-System-Integration
|
||||
|
||||
```php
|
||||
use App\Framework\Analytics\EventSubscribers\AnalyticsEventSubscriber;
|
||||
use App\Framework\EventDispatcher\EventDispatcherInterface;
|
||||
|
||||
class AnalyticsBootstrap
|
||||
{
|
||||
public function initialize(EventDispatcherInterface $eventDispatcher): void
|
||||
{
|
||||
// Analytics-Event-Subscriber registrieren
|
||||
$eventDispatcher->addSubscriber(new AnalyticsEventSubscriber(
|
||||
$this->container->get(AnalyticsCollector::class)
|
||||
));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Externe Analytics-Integration
|
||||
|
||||
```php
|
||||
use App\Framework\Analytics\AnalyticsCollector;
|
||||
use App\Framework\Analytics\AnalyticsCategory;
|
||||
|
||||
class ExternalAnalyticsService
|
||||
{
|
||||
public function __construct(
|
||||
private readonly AnalyticsCollector $analyticsCollector,
|
||||
private readonly ExternalAnalyticsClient $externalClient
|
||||
) {}
|
||||
|
||||
public function trackEvent(string $eventName, array $properties): void
|
||||
{
|
||||
// Intern erfassen
|
||||
$this->analyticsCollector->trackAction(
|
||||
$eventName,
|
||||
AnalyticsCategory::BUSINESS,
|
||||
$properties
|
||||
);
|
||||
|
||||
// An externes System senden
|
||||
$this->externalClient->trackEvent($eventName, $properties);
|
||||
}
|
||||
|
||||
public function syncData(\DateTimeInterface $from, \DateTimeInterface $to): void
|
||||
{
|
||||
// Daten aus internem Analytics abrufen
|
||||
$internalData = $this->analyticsStorage->getRawData('*', $from, $to);
|
||||
|
||||
// Daten an externes System senden
|
||||
$this->externalClient->batchImport($internalData);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Fehlerbehebung
|
||||
|
||||
### Debugging der Datenerfassung
|
||||
|
||||
```php
|
||||
use App\Framework\Analytics\AnalyticsCollector;
|
||||
use App\Framework\Analytics\AnalyticsCategory;
|
||||
|
||||
class DebugAnalyticsCollector extends AnalyticsCollector
|
||||
{
|
||||
public function trackAction(string $action, AnalyticsCategory $category, array $data = []): void
|
||||
{
|
||||
// Debug-Ausgabe
|
||||
echo "Tracking action: {$action}, category: {$category->value}\n";
|
||||
echo "Data: " . json_encode($data, JSON_PRETTY_PRINT) . "\n";
|
||||
|
||||
// Original-Methode aufrufen
|
||||
parent::trackAction($action, $category, $data);
|
||||
}
|
||||
|
||||
public function trackPageView(string $path, array $data = []): void
|
||||
{
|
||||
// Debug-Ausgabe
|
||||
echo "Tracking page view: {$path}\n";
|
||||
echo "Data: " . json_encode($data, JSON_PRETTY_PRINT) . "\n";
|
||||
|
||||
// Original-Methode aufrufen
|
||||
parent::trackPageView($path, $data);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Überprüfung der Datenspeicherung
|
||||
|
||||
```php
|
||||
use App\Framework\Analytics\Storage\AnalyticsStorage;
|
||||
|
||||
class AnalyticsDebugService
|
||||
{
|
||||
public function __construct(
|
||||
private readonly AnalyticsStorage $analyticsStorage,
|
||||
private readonly string $dataPath
|
||||
) {}
|
||||
|
||||
public function checkStorage(): array
|
||||
{
|
||||
$result = [
|
||||
'status' => 'ok',
|
||||
'messages' => [],
|
||||
'files' => []
|
||||
];
|
||||
|
||||
// Prüfen, ob der Datenpfad existiert
|
||||
if (!is_dir($this->dataPath)) {
|
||||
$result['status'] = 'error';
|
||||
$result['messages'][] = "Datenpfad existiert nicht: {$this->dataPath}";
|
||||
return $result;
|
||||
}
|
||||
|
||||
// Prüfen, ob der Datenpfad beschreibbar ist
|
||||
if (!is_writable($this->dataPath)) {
|
||||
$result['status'] = 'error';
|
||||
$result['messages'][] = "Datenpfad ist nicht beschreibbar: {$this->dataPath}";
|
||||
return $result;
|
||||
}
|
||||
|
||||
// Dateien im Datenpfad auflisten
|
||||
$files = glob($this->dataPath . '/*.json');
|
||||
foreach ($files as $file) {
|
||||
$size = filesize($file);
|
||||
$modified = date('Y-m-d H:i:s', filemtime($file));
|
||||
$result['files'][] = [
|
||||
'name' => basename($file),
|
||||
'size' => $size,
|
||||
'modified' => $modified
|
||||
];
|
||||
}
|
||||
|
||||
// Testdaten speichern
|
||||
try {
|
||||
$this->analyticsStorage->storeRawData('debug_test', [
|
||||
'timestamp' => time(),
|
||||
'message' => 'Debug test'
|
||||
]);
|
||||
$result['messages'][] = "Testdaten erfolgreich gespeichert";
|
||||
} catch (\Exception $e) {
|
||||
$result['status'] = 'error';
|
||||
$result['messages'][] = "Fehler beim Speichern von Testdaten: " . $e->getMessage();
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Weiterführende Informationen
|
||||
|
||||
- [Analytics Framework Übersicht](index.md)
|
||||
- [Analytics Konfiguration](configuration.md)
|
||||
- [Performance-Monitoring](/components/performance/index.md)
|
||||
507
docs/components/analytics/index.md
Normal file
507
docs/components/analytics/index.md
Normal file
@@ -0,0 +1,507 @@
|
||||
# Analytics Framework
|
||||
|
||||
> **Dokumentationshinweis:** Diese Dokumentation ist vollständig aktualisiert und stellt die aktuelle Implementierung des Analytics Frameworks korrekt dar.
|
||||
|
||||
## Übersicht
|
||||
|
||||
Das Analytics Framework ist ein leistungsstarkes System zur Erfassung, Speicherung und Analyse von Daten über die Nutzung und Leistung Ihrer Anwendung. Es ist vollständig in das Framework integriert und ermöglicht die Erfassung verschiedener Arten von Ereignissen, darunter Seitenaufrufe, Benutzeraktionen, API-Aufrufe, Fehler und Geschäftsereignisse.
|
||||
|
||||
Das Framework basiert auf dem Performance-System und bietet eine flexible, erweiterbare Architektur für verschiedene Analyseanforderungen, ohne auf externe Dienste angewiesen zu sein.
|
||||
|
||||
## Hauptkomponenten
|
||||
|
||||
### AnalyticsCollector
|
||||
|
||||
Die zentrale Klasse für die Erfassung von Analytics-Daten:
|
||||
|
||||
```php
|
||||
use App\Framework\Analytics\AnalyticsCollector;
|
||||
use App\Framework\Analytics\AnalyticsCategory;
|
||||
|
||||
// Benutzeraktion erfassen
|
||||
$analyticsCollector->trackAction(
|
||||
'button_click',
|
||||
AnalyticsCategory::USER,
|
||||
['button_id' => 'submit', 'page' => '/checkout']
|
||||
);
|
||||
|
||||
// Seitenaufruf erfassen
|
||||
$analyticsCollector->trackPageView(
|
||||
'/products',
|
||||
['referrer' => '/home', 'user_id' => $userId]
|
||||
);
|
||||
|
||||
// Fehler erfassen
|
||||
$analyticsCollector->trackError(
|
||||
'validation_error',
|
||||
['field' => 'email', 'message' => 'Invalid email format']
|
||||
);
|
||||
|
||||
// Geschäftsereignis erfassen
|
||||
$analyticsCollector->trackBusinessEvent(
|
||||
'order_completed',
|
||||
['order_id' => '12345', 'amount' => 99.99, 'items' => 3]
|
||||
);
|
||||
|
||||
// API-Aufruf erfassen
|
||||
$analyticsCollector->trackApiCall(
|
||||
'/api/products',
|
||||
'GET',
|
||||
200,
|
||||
['duration' => 45, 'cache_hit' => true]
|
||||
);
|
||||
```
|
||||
|
||||
### AnalyticsCategory
|
||||
|
||||
Ein Enum, das die verschiedenen Kategorien von Analytics-Daten definiert:
|
||||
|
||||
```php
|
||||
use App\Framework\Analytics\AnalyticsCategory;
|
||||
|
||||
// Verfügbare Kategorien
|
||||
$category = AnalyticsCategory::USER; // Benutzeraktionen
|
||||
$category = AnalyticsCategory::SYSTEM; // Systemereignisse
|
||||
$category = AnalyticsCategory::BUSINESS; // Geschäftsereignisse
|
||||
$category = AnalyticsCategory::PERFORMANCE; // Leistungsmetriken
|
||||
$category = AnalyticsCategory::SECURITY; // Sicherheitsereignisse
|
||||
```
|
||||
|
||||
### AnalyticsStorage
|
||||
|
||||
Das Interface für die Speicherung von Analytics-Daten:
|
||||
|
||||
```php
|
||||
use App\Framework\Analytics\Storage\AnalyticsStorage;
|
||||
|
||||
interface AnalyticsStorage
|
||||
{
|
||||
// Rohdaten speichern
|
||||
public function storeRawData(string $eventType, array $data): void;
|
||||
|
||||
// Aggregierte Daten speichern
|
||||
public function storeAggregatedData(string $metricName, float $value, array $tags = []): void;
|
||||
|
||||
// Rohdaten abrufen
|
||||
public function getRawData(string $eventType, \DateTimeInterface $from, \DateTimeInterface $to): array;
|
||||
|
||||
// Aggregierte Daten abrufen
|
||||
public function getAggregatedData(string $metricName, \DateTimeInterface $from, \DateTimeInterface $to, array $tags = []): array;
|
||||
}
|
||||
```
|
||||
|
||||
Das Framework bietet eine leistungsoptimierte Implementierung:
|
||||
|
||||
```php
|
||||
use App\Framework\Analytics\Storage\PerformanceBasedAnalyticsStorage;
|
||||
|
||||
// Leistungsoptimierte Speicherung
|
||||
$storage = new PerformanceBasedAnalyticsStorage(
|
||||
$filesystem,
|
||||
$performanceSystem,
|
||||
$clock,
|
||||
$config
|
||||
);
|
||||
```
|
||||
|
||||
### AnalyticsMiddleware
|
||||
|
||||
Eine Middleware, die automatisch Analytics-Daten für HTTP-Anfragen erfasst:
|
||||
|
||||
```php
|
||||
use App\Framework\Analytics\Middleware\AnalyticsMiddleware;
|
||||
|
||||
// In der Bootstrap-Datei oder Router-Konfiguration
|
||||
$app->addMiddleware(AnalyticsMiddleware::class);
|
||||
```
|
||||
|
||||
Die Middleware erfasst automatisch:
|
||||
- Seitenaufrufe
|
||||
- API-Aufrufe
|
||||
- Leistungsmetriken (Antwortzeit, Speicherverbrauch)
|
||||
- HTTP-Statuscodes
|
||||
- Benutzeragenten und Referrer
|
||||
|
||||
### SecurityAnalyticsListener
|
||||
|
||||
Ein Listener, der Sicherheitsereignisse in Analytics-Daten umwandelt:
|
||||
|
||||
```php
|
||||
use App\Framework\Analytics\Listeners\SecurityAnalyticsListener;
|
||||
use App\Framework\Analytics\Bridges\SecurityEventBridge;
|
||||
|
||||
// In der Bootstrap-Datei
|
||||
$securityEventBridge = SecurityEventBridge::create(
|
||||
$container->get(SecurityAnalyticsListener::class),
|
||||
$config->securityAnalyticsEnabled
|
||||
);
|
||||
|
||||
$eventDispatcher->addListener(SecurityEventInterface::class, [$securityEventBridge, 'handleSecurityEvent']);
|
||||
```
|
||||
|
||||
## Datenerfassung
|
||||
|
||||
### Benutzeraktionen
|
||||
|
||||
Benutzeraktionen repräsentieren Interaktionen des Benutzers mit der Anwendung:
|
||||
|
||||
```php
|
||||
$analyticsCollector->trackAction(
|
||||
'button_click', // Aktionsname
|
||||
AnalyticsCategory::USER, // Kategorie
|
||||
[ // Zusätzliche Daten
|
||||
'button_id' => 'submit',
|
||||
'page' => '/checkout',
|
||||
'user_id' => $userId
|
||||
]
|
||||
);
|
||||
```
|
||||
|
||||
### Seitenaufrufe
|
||||
|
||||
Seitenaufrufe repräsentieren den Besuch einer Seite durch einen Benutzer:
|
||||
|
||||
```php
|
||||
$analyticsCollector->trackPageView(
|
||||
'/products', // Pfad
|
||||
[ // Zusätzliche Daten
|
||||
'referrer' => '/home',
|
||||
'user_id' => $userId,
|
||||
'device_type' => 'mobile'
|
||||
]
|
||||
);
|
||||
```
|
||||
|
||||
### Fehler
|
||||
|
||||
Fehler repräsentieren Probleme, die während der Ausführung der Anwendung auftreten:
|
||||
|
||||
```php
|
||||
$analyticsCollector->trackError(
|
||||
'validation_error', // Fehlertyp
|
||||
[ // Zusätzliche Daten
|
||||
'field' => 'email',
|
||||
'message' => 'Invalid email format',
|
||||
'user_id' => $userId
|
||||
]
|
||||
);
|
||||
|
||||
// Oder mit einer Exception
|
||||
try {
|
||||
// Code, der eine Exception werfen könnte
|
||||
} catch (\Exception $e) {
|
||||
$analyticsCollector->trackException($e);
|
||||
}
|
||||
```
|
||||
|
||||
### Geschäftsereignisse
|
||||
|
||||
Geschäftsereignisse repräsentieren wichtige Ereignisse aus geschäftlicher Sicht:
|
||||
|
||||
```php
|
||||
$analyticsCollector->trackBusinessEvent(
|
||||
'order_completed', // Ereignisname
|
||||
[ // Zusätzliche Daten
|
||||
'order_id' => '12345',
|
||||
'amount' => 99.99,
|
||||
'items' => 3,
|
||||
'user_id' => $userId
|
||||
]
|
||||
);
|
||||
```
|
||||
|
||||
### API-Aufrufe
|
||||
|
||||
API-Aufrufe repräsentieren Anfragen an die API der Anwendung:
|
||||
|
||||
```php
|
||||
$analyticsCollector->trackApiCall(
|
||||
'/api/products', // Pfad
|
||||
'GET', // Methode
|
||||
200, // Statuscode
|
||||
[ // Zusätzliche Daten
|
||||
'duration' => 45,
|
||||
'cache_hit' => true,
|
||||
'user_id' => $userId
|
||||
]
|
||||
);
|
||||
```
|
||||
|
||||
## Konfiguration
|
||||
|
||||
### AnalyticsConfig
|
||||
|
||||
Die `AnalyticsConfig`-Klasse enthält die Konfiguration für das Analytics Framework:
|
||||
|
||||
```php
|
||||
use App\Framework\Analytics\AnalyticsConfig;
|
||||
|
||||
// Standardkonfiguration
|
||||
$config = AnalyticsConfig::default();
|
||||
|
||||
// Benutzerdefinierte Konfiguration
|
||||
$config = new AnalyticsConfig(
|
||||
$enabled = true,
|
||||
$samplingRate = 1.0,
|
||||
$securityAnalyticsEnabled = true,
|
||||
$dataPath = '/var/www/html/storage/analytics',
|
||||
$bufferSize = 1000,
|
||||
$retentionDays = 365,
|
||||
$trackPageViews = true,
|
||||
$trackApiCalls = true,
|
||||
$trackUserActions = true,
|
||||
$trackErrors = true,
|
||||
$trackPerformance = true
|
||||
);
|
||||
```
|
||||
|
||||
### Umgebungsvariablen
|
||||
|
||||
Die Konfiguration kann auch über Umgebungsvariablen angepasst werden:
|
||||
|
||||
```
|
||||
ANALYTICS_ENABLED=true
|
||||
ANALYTICS_SAMPLING_RATE=0.1
|
||||
SECURITY_ANALYTICS_ENABLED=true
|
||||
ANALYTICS_DATA_PATH=/var/www/html/storage/analytics
|
||||
ANALYTICS_BUFFER_SIZE=1000
|
||||
ANALYTICS_RETENTION_DAYS=365
|
||||
ANALYTICS_TRACK_PAGE_VIEWS=true
|
||||
ANALYTICS_TRACK_API_CALLS=true
|
||||
ANALYTICS_TRACK_USER_ACTIONS=true
|
||||
ANALYTICS_TRACK_ERRORS=true
|
||||
ANALYTICS_TRACK_PERFORMANCE=true
|
||||
```
|
||||
|
||||
## Datenanalyse
|
||||
|
||||
### Rohdaten abrufen
|
||||
|
||||
```php
|
||||
// Rohdaten für Seitenaufrufe abrufen
|
||||
$pageViews = $analyticsStorage->getRawData(
|
||||
'page_view',
|
||||
new \DateTimeImmutable('-7 days'),
|
||||
new \DateTimeImmutable()
|
||||
);
|
||||
|
||||
// Rohdaten für Geschäftsereignisse abrufen
|
||||
$orders = $analyticsStorage->getRawData(
|
||||
'business_event_order_completed',
|
||||
new \DateTimeImmutable('-30 days'),
|
||||
new \DateTimeImmutable()
|
||||
);
|
||||
```
|
||||
|
||||
### Aggregierte Daten abrufen
|
||||
|
||||
```php
|
||||
// Aggregierte Daten für Seitenaufrufe abrufen
|
||||
$pageViewStats = $analyticsStorage->getAggregatedData(
|
||||
'analytics_page_views_total',
|
||||
new \DateTimeImmutable('-7 days'),
|
||||
new \DateTimeImmutable(),
|
||||
['path' => '/products']
|
||||
);
|
||||
|
||||
// Aggregierte Daten für API-Aufrufe abrufen
|
||||
$apiCallStats = $analyticsStorage->getAggregatedData(
|
||||
'analytics_api_calls_total',
|
||||
new \DateTimeImmutable('-7 days'),
|
||||
new \DateTimeImmutable(),
|
||||
['path' => '/api/products', 'method' => 'GET']
|
||||
);
|
||||
```
|
||||
|
||||
### Dashboard
|
||||
|
||||
Das Analytics-Dashboard bietet eine visuelle Darstellung der erfassten Daten:
|
||||
|
||||
```php
|
||||
use App\Framework\Analytics\Dashboard\AnalyticsDashboard;
|
||||
|
||||
// Dashboard initialisieren
|
||||
$dashboard = new AnalyticsDashboard($analyticsStorage);
|
||||
|
||||
// Statistiken abrufen
|
||||
$stats = $dashboard->getStats(
|
||||
new \DateTimeImmutable('-7 days'),
|
||||
new \DateTimeImmutable()
|
||||
);
|
||||
|
||||
// Spezifische Statistiken abrufen
|
||||
$pageViewStats = $dashboard->getPageViewStats();
|
||||
$errorStats = $dashboard->getErrorStats();
|
||||
$performanceStats = $dashboard->getPerformanceStats();
|
||||
$businessStats = $dashboard->getBusinessStats();
|
||||
```
|
||||
|
||||
## Integration
|
||||
|
||||
### Service-Container
|
||||
|
||||
Der Analytics-Service wird automatisch registriert und kann per Dependency Injection verwendet werden:
|
||||
|
||||
```php
|
||||
use App\Framework\Analytics\AnalyticsCollector;
|
||||
|
||||
class UserController
|
||||
{
|
||||
public function __construct(
|
||||
private readonly AnalyticsCollector $analyticsCollector
|
||||
) {}
|
||||
|
||||
public function register(Request $request): Response
|
||||
{
|
||||
// Benutzerregistrierung verarbeiten
|
||||
|
||||
// Geschäftsereignis erfassen
|
||||
$this->analyticsCollector->trackBusinessEvent(
|
||||
'user_registered',
|
||||
['user_id' => $user->id, 'email_domain' => $emailDomain]
|
||||
);
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Event-Integration
|
||||
|
||||
Das Analytics Framework kann mit dem Event-System des Frameworks integriert werden:
|
||||
|
||||
```php
|
||||
use App\Framework\Analytics\EventSubscribers\AnalyticsEventSubscriber;
|
||||
|
||||
// In der Bootstrap-Datei
|
||||
$eventDispatcher->addSubscriber(new AnalyticsEventSubscriber($analyticsCollector));
|
||||
```
|
||||
|
||||
## Leistungsoptimierung
|
||||
|
||||
### Sampling
|
||||
|
||||
Um die Leistung bei hohem Datenaufkommen zu verbessern, unterstützt das Analytics Framework Sampling:
|
||||
|
||||
```php
|
||||
// Sampling-Rate konfigurieren (10% der Daten erfassen)
|
||||
$config = new AnalyticsConfig(
|
||||
$enabled = true,
|
||||
$samplingRate = 0.1
|
||||
);
|
||||
```
|
||||
|
||||
### Pufferung
|
||||
|
||||
Das Analytics Framework verwendet Pufferung, um die Anzahl der Schreibvorgänge zu reduzieren:
|
||||
|
||||
```php
|
||||
// Puffergröße konfigurieren
|
||||
$config = new AnalyticsConfig(
|
||||
$enabled = true,
|
||||
$samplingRate = 1.0,
|
||||
$securityAnalyticsEnabled = true,
|
||||
$dataPath = '/var/www/html/storage/analytics',
|
||||
$bufferSize = 1000 // Anzahl der Ereignisse im Puffer
|
||||
);
|
||||
```
|
||||
|
||||
### Asynchrone Verarbeitung
|
||||
|
||||
Für maximale Leistung kann die Analytics-Verarbeitung asynchron erfolgen:
|
||||
|
||||
```php
|
||||
use App\Framework\Analytics\AsyncAnalyticsCollector;
|
||||
|
||||
// Asynchronen Collector verwenden
|
||||
$asyncCollector = new AsyncAnalyticsCollector(
|
||||
$analyticsCollector,
|
||||
$queue
|
||||
);
|
||||
|
||||
// Daten asynchron erfassen
|
||||
$asyncCollector->trackPageView('/products');
|
||||
```
|
||||
|
||||
## Datenverwaltung
|
||||
|
||||
### Datenaufbewahrung
|
||||
|
||||
Das Analytics Framework unterstützt automatische Datenaufbewahrungsrichtlinien:
|
||||
|
||||
```php
|
||||
// Aufbewahrungsdauer konfigurieren
|
||||
$config = new AnalyticsConfig(
|
||||
$enabled = true,
|
||||
$samplingRate = 1.0,
|
||||
$securityAnalyticsEnabled = true,
|
||||
$dataPath = '/var/www/html/storage/analytics',
|
||||
$bufferSize = 1000,
|
||||
$retentionDays = 365 // Daten für 1 Jahr aufbewahren
|
||||
);
|
||||
```
|
||||
|
||||
### Datenexport
|
||||
|
||||
Daten können für die weitere Analyse exportiert werden:
|
||||
|
||||
```php
|
||||
use App\Framework\Analytics\Export\AnalyticsExporter;
|
||||
|
||||
// Exporter initialisieren
|
||||
$exporter = new AnalyticsExporter($analyticsStorage);
|
||||
|
||||
// Daten exportieren
|
||||
$csvData = $exporter->exportToCsv(
|
||||
'page_view',
|
||||
new \DateTimeImmutable('-7 days'),
|
||||
new \DateTimeImmutable()
|
||||
);
|
||||
|
||||
// Daten in Datei speichern
|
||||
file_put_contents('page_views.csv', $csvData);
|
||||
```
|
||||
|
||||
## Fehlerbehebung
|
||||
|
||||
### Logging
|
||||
|
||||
Das Analytics Framework bietet umfangreiches Logging für die Fehlerbehebung:
|
||||
|
||||
```php
|
||||
// In der AnalyticsCollector-Klasse
|
||||
$this->logger->info("Analytics: trackAction called with action={$action}, category={$category->value}");
|
||||
$this->logger->error("Analytics: Failed to store data", ['error' => $e->getMessage()]);
|
||||
```
|
||||
|
||||
### Häufige Probleme
|
||||
|
||||
#### Keine Daten werden erfasst
|
||||
|
||||
Mögliche Ursachen:
|
||||
- Analytics ist deaktiviert (`enabled = false`)
|
||||
- Sampling-Rate ist zu niedrig
|
||||
- Fehler beim Speichern der Daten
|
||||
|
||||
Lösung:
|
||||
- Überprüfen Sie die Konfiguration
|
||||
- Erhöhen Sie die Sampling-Rate
|
||||
- Überprüfen Sie die Logs auf Fehler
|
||||
|
||||
#### Hohe Serverbelastung
|
||||
|
||||
Mögliche Ursachen:
|
||||
- Zu viele Ereignisse werden erfasst
|
||||
- Puffergröße ist zu klein
|
||||
- Synchrone Verarbeitung bei hohem Datenaufkommen
|
||||
|
||||
Lösung:
|
||||
- Reduzieren Sie die Sampling-Rate
|
||||
- Erhöhen Sie die Puffergröße
|
||||
- Verwenden Sie asynchrone Verarbeitung
|
||||
|
||||
## Weiterführende Informationen
|
||||
|
||||
- [Analytics Konfiguration](configuration.md)
|
||||
- [Analytics Beispiele](examples.md)
|
||||
- [Performance-Monitoring](/components/performance/index.md)
|
||||
430
docs/components/auth/configuration.md
Normal file
430
docs/components/auth/configuration.md
Normal file
@@ -0,0 +1,430 @@
|
||||
# Auth Module Configuration
|
||||
|
||||
**Konfiguration und Setup** für das Auth Module des Custom PHP Frameworks.
|
||||
|
||||
## Dependency Injection Setup
|
||||
|
||||
### Container Bindings
|
||||
|
||||
```php
|
||||
use App\Framework\Auth\PasswordHasher;
|
||||
use App\Framework\Auth\AuthenticationService;
|
||||
use App\Framework\Cryptography\KeyDerivationFunction;
|
||||
|
||||
// services.php oder Container Initialization
|
||||
$container->singleton(PasswordHasher::class, function(Container $container) {
|
||||
return new PasswordHasher(
|
||||
kdf: $container->get(KeyDerivationFunction::class),
|
||||
defaultAlgorithm: 'argon2id',
|
||||
defaultSecurityLevel: PasswordHasher::LEVEL_STANDARD
|
||||
);
|
||||
});
|
||||
|
||||
$container->singleton(AuthenticationService::class, function(Container $container) {
|
||||
return new AuthenticationService(
|
||||
passwordHasher: $container->get(PasswordHasher::class),
|
||||
sessionIdGenerator: $container->get(SessionIdGenerator::class),
|
||||
repository: $container->get(AuthenticationRepository::class),
|
||||
rateLimiter: $container->get(RateLimitService::class)
|
||||
);
|
||||
});
|
||||
```
|
||||
|
||||
### Environment Configuration
|
||||
|
||||
```php
|
||||
// .env Konfiguration
|
||||
AUTH_SESSION_TIMEOUT=3600 # Session Timeout in Sekunden (1 Stunde)
|
||||
AUTH_REMEMBER_TOKEN_EXPIRY=2592000 # Remember Token Expiry (30 Tage)
|
||||
AUTH_MAX_LOGIN_ATTEMPTS=5 # Maximale Login-Versuche
|
||||
AUTH_LOCKOUT_DURATION=900 # Account Lockout Duration (15 Minuten)
|
||||
AUTH_RATE_LIMIT_WINDOW=300 # Rate Limit Window (5 Minuten)
|
||||
|
||||
# Password Hashing Configuration
|
||||
AUTH_DEFAULT_ALGORITHM=argon2id # Standard Hash-Algorithmus
|
||||
AUTH_DEFAULT_SECURITY_LEVEL=standard # low|standard|high
|
||||
AUTH_PASSWORD_MIN_LENGTH=8 # Minimale Passwort-Länge
|
||||
AUTH_PASSWORD_MAX_LENGTH=4096 # Maximale Passwort-Länge
|
||||
|
||||
# Session Security
|
||||
AUTH_SESSION_REGENERATE_ON_LOGIN=true # Session ID bei Login regenerieren
|
||||
AUTH_CHECK_IP_CONSISTENCY=false # IP-Konsistenz-Prüfung (optional)
|
||||
AUTH_REMEMBER_TOKEN_LENGTH=32 # Remember Token Länge (Bytes)
|
||||
```
|
||||
|
||||
### Typed Configuration Class
|
||||
|
||||
```php
|
||||
final readonly class AuthConfig
|
||||
{
|
||||
public function __construct(
|
||||
// Session Configuration
|
||||
public int $sessionTimeout = 3600,
|
||||
public int $rememberTokenExpiry = 2592000,
|
||||
public bool $sessionRegenerateOnLogin = true,
|
||||
public bool $checkIpConsistency = false,
|
||||
public int $rememberTokenLength = 32,
|
||||
|
||||
// Security Configuration
|
||||
public int $maxLoginAttempts = 5,
|
||||
public int $lockoutDuration = 900,
|
||||
public int $rateLimitWindow = 300,
|
||||
|
||||
// Password Configuration
|
||||
public string $defaultAlgorithm = 'argon2id',
|
||||
public string $defaultSecurityLevel = 'standard',
|
||||
public int $passwordMinLength = 8,
|
||||
public int $passwordMaxLength = 4096,
|
||||
|
||||
// Validation Configuration
|
||||
public bool $enforcePasswordComplexity = true,
|
||||
public bool $checkCommonPasswords = true,
|
||||
public bool $preventSequentialChars = true,
|
||||
public int $minPasswordScore = 50
|
||||
) {}
|
||||
|
||||
public static function fromEnvironment(Environment $env): self
|
||||
{
|
||||
return new self(
|
||||
sessionTimeout: $env->getInt(EnvKey::AUTH_SESSION_TIMEOUT, 3600),
|
||||
rememberTokenExpiry: $env->getInt(EnvKey::AUTH_REMEMBER_TOKEN_EXPIRY, 2592000),
|
||||
sessionRegenerateOnLogin: $env->getBool(EnvKey::AUTH_SESSION_REGENERATE_ON_LOGIN, true),
|
||||
checkIpConsistency: $env->getBool(EnvKey::AUTH_CHECK_IP_CONSISTENCY, false),
|
||||
rememberTokenLength: $env->getInt(EnvKey::AUTH_REMEMBER_TOKEN_LENGTH, 32),
|
||||
|
||||
maxLoginAttempts: $env->getInt(EnvKey::AUTH_MAX_LOGIN_ATTEMPTS, 5),
|
||||
lockoutDuration: $env->getInt(EnvKey::AUTH_LOCKOUT_DURATION, 900),
|
||||
rateLimitWindow: $env->getInt(EnvKey::AUTH_RATE_LIMIT_WINDOW, 300),
|
||||
|
||||
defaultAlgorithm: $env->get(EnvKey::AUTH_DEFAULT_ALGORITHM, 'argon2id'),
|
||||
defaultSecurityLevel: $env->get(EnvKey::AUTH_DEFAULT_SECURITY_LEVEL, 'standard'),
|
||||
passwordMinLength: $env->getInt(EnvKey::AUTH_PASSWORD_MIN_LENGTH, 8),
|
||||
passwordMaxLength: $env->getInt(EnvKey::AUTH_PASSWORD_MAX_LENGTH, 4096),
|
||||
|
||||
enforcePasswordComplexity: $env->getBool(EnvKey::AUTH_ENFORCE_PASSWORD_COMPLEXITY, true),
|
||||
checkCommonPasswords: $env->getBool(EnvKey::AUTH_CHECK_COMMON_PASSWORDS, true),
|
||||
preventSequentialChars: $env->getBool(EnvKey::AUTH_PREVENT_SEQUENTIAL_CHARS, true),
|
||||
minPasswordScore: $env->getInt(EnvKey::AUTH_MIN_PASSWORD_SCORE, 50)
|
||||
);
|
||||
}
|
||||
|
||||
public function getSecurityLevelParameters(string $algorithm): array
|
||||
{
|
||||
return match ([$algorithm, $this->defaultSecurityLevel]) {
|
||||
['argon2id', 'low'] => [
|
||||
'memory_cost' => 32768, // 32 MB
|
||||
'time_cost' => 2,
|
||||
'threads' => 2
|
||||
],
|
||||
['argon2id', 'standard'] => [
|
||||
'memory_cost' => 65536, // 64 MB
|
||||
'time_cost' => 4,
|
||||
'threads' => 3
|
||||
],
|
||||
['argon2id', 'high'] => [
|
||||
'memory_cost' => 131072, // 128 MB
|
||||
'time_cost' => 6,
|
||||
'threads' => 4
|
||||
],
|
||||
['pbkdf2-sha256', 'low'] => ['iterations' => 50000],
|
||||
['pbkdf2-sha256', 'standard'] => ['iterations' => 100000],
|
||||
['pbkdf2-sha256', 'high'] => ['iterations' => 200000],
|
||||
default => []
|
||||
};
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Database Schema
|
||||
|
||||
### Required Tables
|
||||
|
||||
```sql
|
||||
-- Benutzer-Tabelle (beispielhaft)
|
||||
CREATE TABLE users (
|
||||
id VARCHAR(255) PRIMARY KEY,
|
||||
email VARCHAR(255) UNIQUE NOT NULL,
|
||||
username VARCHAR(100) UNIQUE,
|
||||
password_hash TEXT NOT NULL,
|
||||
password_algorithm VARCHAR(50) NOT NULL DEFAULT 'argon2id',
|
||||
password_parameters JSON,
|
||||
created_at DATETIME NOT NULL,
|
||||
updated_at DATETIME NOT NULL,
|
||||
last_login_at DATETIME NULL,
|
||||
is_active BOOLEAN DEFAULT TRUE,
|
||||
|
||||
INDEX idx_email (email),
|
||||
INDEX idx_username (username),
|
||||
INDEX idx_active (is_active)
|
||||
);
|
||||
|
||||
-- Session-Tabelle
|
||||
CREATE TABLE auth_sessions (
|
||||
id VARCHAR(255) PRIMARY KEY,
|
||||
user_id VARCHAR(255) NOT NULL,
|
||||
ip_address VARCHAR(45),
|
||||
user_agent TEXT,
|
||||
created_at DATETIME NOT NULL,
|
||||
expires_at DATETIME NOT NULL,
|
||||
last_activity DATETIME NOT NULL,
|
||||
|
||||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
|
||||
INDEX idx_user_id (user_id),
|
||||
INDEX idx_expires_at (expires_at),
|
||||
INDEX idx_last_activity (last_activity)
|
||||
);
|
||||
|
||||
-- Remember Token Tabelle
|
||||
CREATE TABLE auth_remember_tokens (
|
||||
token_hash VARCHAR(64) PRIMARY KEY,
|
||||
user_id VARCHAR(255) NOT NULL,
|
||||
created_at DATETIME NOT NULL,
|
||||
expires_at DATETIME NOT NULL,
|
||||
|
||||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
|
||||
INDEX idx_user_id (user_id),
|
||||
INDEX idx_expires_at (expires_at)
|
||||
);
|
||||
|
||||
-- Failed Login Attempts Tabelle
|
||||
CREATE TABLE auth_failed_attempts (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
user_id VARCHAR(255),
|
||||
identifier VARCHAR(255),
|
||||
ip_address VARCHAR(45),
|
||||
attempted_at DATETIME NOT NULL,
|
||||
reason VARCHAR(100),
|
||||
|
||||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
|
||||
INDEX idx_user_id (user_id),
|
||||
INDEX idx_identifier (identifier),
|
||||
INDEX idx_ip_address (ip_address),
|
||||
INDEX idx_attempted_at (attempted_at)
|
||||
);
|
||||
|
||||
-- Security Events Tabelle (optional für Logging)
|
||||
CREATE TABLE auth_security_events (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
event_type VARCHAR(50) NOT NULL,
|
||||
user_id VARCHAR(255),
|
||||
session_id VARCHAR(255),
|
||||
ip_address VARCHAR(45),
|
||||
user_agent TEXT,
|
||||
event_data JSON,
|
||||
created_at DATETIME NOT NULL,
|
||||
|
||||
INDEX idx_event_type (event_type),
|
||||
INDEX idx_user_id (user_id),
|
||||
INDEX idx_created_at (created_at),
|
||||
INDEX idx_ip_address (ip_address)
|
||||
);
|
||||
```
|
||||
|
||||
### Migration Commands
|
||||
|
||||
```bash
|
||||
# Migration erstellen
|
||||
php console.php make:migration CreateAuthTables Auth
|
||||
|
||||
# Migration ausführen
|
||||
php console.php db:migrate
|
||||
|
||||
# Migration Status prüfen
|
||||
php console.php db:status
|
||||
```
|
||||
|
||||
## Security Level Configuration
|
||||
|
||||
### Password Hashing Levels
|
||||
|
||||
```php
|
||||
// Niedrige Sicherheit (Development, Testing)
|
||||
$lowSecurity = new PasswordHasher(
|
||||
kdf: $kdf,
|
||||
defaultAlgorithm: 'argon2id',
|
||||
defaultSecurityLevel: PasswordHasher::LEVEL_LOW
|
||||
);
|
||||
|
||||
// Standard Sicherheit (Production Default)
|
||||
$standardSecurity = new PasswordHasher(
|
||||
kdf: $kdf,
|
||||
defaultAlgorithm: 'argon2id',
|
||||
defaultSecurityLevel: PasswordHasher::LEVEL_STANDARD
|
||||
);
|
||||
|
||||
// Hohe Sicherheit (Banking, Healthcare)
|
||||
$highSecurity = new PasswordHasher(
|
||||
kdf: $kdf,
|
||||
defaultAlgorithm: 'argon2id',
|
||||
defaultSecurityLevel: PasswordHasher::LEVEL_HIGH
|
||||
);
|
||||
```
|
||||
|
||||
### Algorithm Performance Comparison
|
||||
|
||||
| Algorithm | Level | Memory | Time | Iterations | Performance | Security |
|
||||
|-----------|-------|---------|------|------------|-------------|----------|
|
||||
| Argon2ID | Low | 32 MB | 2 | - | Fast | Good |
|
||||
| Argon2ID | Standard | 64 MB | 4 | - | Medium | Excellent |
|
||||
| Argon2ID | High | 128 MB | 6 | - | Slow | Maximum |
|
||||
| PBKDF2-SHA256 | Low | - | - | 50,000 | Fast | Good |
|
||||
| PBKDF2-SHA256 | Standard | - | - | 100,000 | Fast | Good |
|
||||
| PBKDF2-SHA256 | High | - | - | 200,000 | Medium | Good |
|
||||
| Scrypt | Standard | 16 MB | - | - | Medium | Good |
|
||||
|
||||
## Rate Limiting Configuration
|
||||
|
||||
### Redis-based Rate Limiting
|
||||
|
||||
```php
|
||||
use App\Framework\Auth\RateLimit\RedisRateLimitService;
|
||||
|
||||
$rateLimiter = new RedisRateLimitService(
|
||||
redis: $redis,
|
||||
config: new RateLimitConfig(
|
||||
maxAttempts: 5,
|
||||
windowSeconds: 300,
|
||||
lockoutDuration: 900
|
||||
)
|
||||
);
|
||||
```
|
||||
|
||||
### File-based Rate Limiting
|
||||
|
||||
```php
|
||||
use App\Framework\Auth\RateLimit\FileRateLimitService;
|
||||
|
||||
$rateLimiter = new FileRateLimitService(
|
||||
cacheDir: '/tmp/auth_rate_limits',
|
||||
config: new RateLimitConfig(
|
||||
maxAttempts: 5,
|
||||
windowSeconds: 300,
|
||||
lockoutDuration: 900,
|
||||
cleanupInterval: 3600 // Cleanup alte Einträge jede Stunde
|
||||
)
|
||||
);
|
||||
```
|
||||
|
||||
## Monitoring & Logging
|
||||
|
||||
### Security Event Configuration
|
||||
|
||||
```php
|
||||
// Security Event Handler
|
||||
final readonly class SecurityEventHandler
|
||||
{
|
||||
public function __construct(
|
||||
private Logger $logger,
|
||||
private ?AlertingService $alerting = null
|
||||
) {}
|
||||
|
||||
public function handle(SecurityEvent $event): void
|
||||
{
|
||||
// Log alle Security Events
|
||||
$this->logger->warning('Security Event', [
|
||||
'event_type' => $event->getType(),
|
||||
'user_id' => $event->getUserId(),
|
||||
'ip_address' => (string) $event->getIpAddress(),
|
||||
'data' => $event->getData()
|
||||
]);
|
||||
|
||||
// Kritische Events alarmieren
|
||||
if ($event->isCritical()) {
|
||||
$this->alerting?->sendAlert($event);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Performance Monitoring
|
||||
|
||||
```php
|
||||
// Performance Metrics für Password Hashing
|
||||
$start = microtime(true);
|
||||
$hashedPassword = $passwordHasher->hash($password);
|
||||
$hashTime = microtime(true) - $start;
|
||||
|
||||
$this->metrics->histogram('auth.password_hash_duration', $hashTime, [
|
||||
'algorithm' => $hashedPassword->getAlgorithm(),
|
||||
'security_level' => $securityLevel
|
||||
]);
|
||||
```
|
||||
|
||||
## Production Deployment
|
||||
|
||||
### Environment-specific Configuration
|
||||
|
||||
```php
|
||||
// Production
|
||||
AUTH_DEFAULT_SECURITY_LEVEL=high
|
||||
AUTH_SESSION_TIMEOUT=1800 # 30 Minuten
|
||||
AUTH_CHECK_IP_CONSISTENCY=true # Striktere IP-Prüfung
|
||||
AUTH_MAX_LOGIN_ATTEMPTS=3 # Weniger Versuche
|
||||
AUTH_LOCKOUT_DURATION=3600 # 1 Stunde Lockout
|
||||
|
||||
// Development
|
||||
AUTH_DEFAULT_SECURITY_LEVEL=low
|
||||
AUTH_SESSION_TIMEOUT=86400 # 24 Stunden
|
||||
AUTH_CHECK_IP_CONSISTENCY=false
|
||||
AUTH_MAX_LOGIN_ATTEMPTS=10
|
||||
AUTH_LOCKOUT_DURATION=300 # 5 Minuten
|
||||
|
||||
// Testing
|
||||
AUTH_DEFAULT_SECURITY_LEVEL=low
|
||||
AUTH_SESSION_TIMEOUT=3600
|
||||
AUTH_MAX_LOGIN_ATTEMPTS=5
|
||||
AUTH_LOCKOUT_DURATION=60 # 1 Minute
|
||||
```
|
||||
|
||||
### Security Headers Configuration
|
||||
|
||||
```php
|
||||
// Middleware für Auth-bezogene Security Headers
|
||||
final readonly class AuthSecurityHeadersMiddleware
|
||||
{
|
||||
public function handle(HttpRequest $request, callable $next): HttpResponse
|
||||
{
|
||||
$response = $next($request);
|
||||
|
||||
if ($request->getUri()->getPath() === '/login') {
|
||||
$response = $response->withHeader('X-Frame-Options', 'DENY');
|
||||
$response = $response->withHeader('X-Content-Type-Options', 'nosniff');
|
||||
$response = $response->withHeader('Referrer-Policy', 'strict-origin-when-cross-origin');
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Backup & Recovery
|
||||
|
||||
### Session Cleanup
|
||||
|
||||
```bash
|
||||
# Cron Job für Session Cleanup (täglich)
|
||||
0 2 * * * php /path/to/console.php auth:cleanup-expired-sessions
|
||||
|
||||
# Manual Cleanup
|
||||
php console.php auth:cleanup-expired-sessions
|
||||
php console.php auth:cleanup-expired-tokens
|
||||
```
|
||||
|
||||
### Data Retention
|
||||
|
||||
```php
|
||||
// Cleanup Commands
|
||||
final readonly class CleanupExpiredSessionsCommand
|
||||
{
|
||||
public function execute(): void
|
||||
{
|
||||
$expiredCount = $this->repository->deleteExpiredSessions();
|
||||
$this->output->writeln("Deleted {$expiredCount} expired sessions");
|
||||
|
||||
$tokenCount = $this->repository->deleteExpiredRememberTokens();
|
||||
$this->output->writeln("Deleted {$tokenCount} expired remember tokens");
|
||||
|
||||
$attemptCount = $this->repository->cleanupOldFailedAttempts(days: 30);
|
||||
$this->output->writeln("Cleaned up {$attemptCount} old failed attempts");
|
||||
}
|
||||
}
|
||||
797
docs/components/auth/examples.md
Normal file
797
docs/components/auth/examples.md
Normal file
@@ -0,0 +1,797 @@
|
||||
# Auth Module Examples
|
||||
|
||||
**Praktische Implementierungsbeispiele** für das Auth Module des Custom PHP Frameworks.
|
||||
|
||||
## Basic Authentication Flow
|
||||
|
||||
### User Registration
|
||||
|
||||
```php
|
||||
use App\Framework\Auth\PasswordHasher;
|
||||
use App\Framework\Auth\PasswordValidationResult;
|
||||
|
||||
final readonly class UserRegistrationService
|
||||
{
|
||||
public function __construct(
|
||||
private PasswordHasher $passwordHasher,
|
||||
private UserRepository $userRepository
|
||||
) {}
|
||||
|
||||
public function register(
|
||||
string $email,
|
||||
#[SensitiveParameter] string $password,
|
||||
string $username = null
|
||||
): RegistrationResult {
|
||||
// Validate password strength
|
||||
$validation = $this->passwordHasher->validatePasswordStrength($password);
|
||||
if (!$validation->isValid) {
|
||||
return RegistrationResult::passwordValidationFailed($validation);
|
||||
}
|
||||
|
||||
// Check if user exists
|
||||
if ($this->userRepository->findByEmail($email)) {
|
||||
return RegistrationResult::failed('User with this email already exists');
|
||||
}
|
||||
|
||||
// Hash password
|
||||
$hashedPassword = $this->passwordHasher->hash($password);
|
||||
|
||||
// Create user
|
||||
$user = new User(
|
||||
id: Uuid::generate(),
|
||||
email: new Email($email),
|
||||
username: $username,
|
||||
hashedPassword: $hashedPassword,
|
||||
createdAt: new \DateTimeImmutable()
|
||||
);
|
||||
|
||||
$this->userRepository->save($user);
|
||||
|
||||
return RegistrationResult::success($user);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Login Implementation
|
||||
|
||||
```php
|
||||
use App\Framework\Auth\AuthenticationService;
|
||||
use App\Framework\Http\IpAddress;
|
||||
use App\Framework\Http\Session\SessionManager;
|
||||
|
||||
#[Route(path: '/login', method: Method::POST)]
|
||||
final readonly class LoginController
|
||||
{
|
||||
public function __construct(
|
||||
private AuthenticationService $authService,
|
||||
private SessionManager $sessionManager
|
||||
) {}
|
||||
|
||||
public function login(LoginRequest $request): JsonResult
|
||||
{
|
||||
$ipAddress = IpAddress::fromRequest();
|
||||
|
||||
$result = $this->authService->authenticate(
|
||||
identifier: $request->email,
|
||||
password: $request->password,
|
||||
ipAddress: $ipAddress,
|
||||
remember: $request->remember ?? false
|
||||
);
|
||||
|
||||
if ($result->isSuccess()) {
|
||||
$user = $result->getUser();
|
||||
$session = $result->getSession();
|
||||
|
||||
// Store session in session manager
|
||||
$this->sessionManager->start($session->getId());
|
||||
$this->sessionManager->set('user_id', $user->getId());
|
||||
$this->sessionManager->set('authenticated_at', time());
|
||||
|
||||
$responseData = [
|
||||
'success' => true,
|
||||
'user' => [
|
||||
'id' => $user->getId(),
|
||||
'email' => (string) $user->getEmail(),
|
||||
'username' => $user->getUsername()
|
||||
],
|
||||
'session' => [
|
||||
'id' => $session->getId()->toString(),
|
||||
'expires_at' => $session->getExpiresAt()->format(\DateTimeInterface::ATOM)
|
||||
]
|
||||
];
|
||||
|
||||
// Add remember token to response if requested
|
||||
if ($rememberToken = $result->getRememberToken()) {
|
||||
$responseData['remember_token'] = $rememberToken->getPlainTextValue();
|
||||
|
||||
// Set secure HTTP-only cookie
|
||||
setcookie(
|
||||
'remember_token',
|
||||
$rememberToken->getPlainTextValue(),
|
||||
[
|
||||
'expires' => $rememberToken->getExpiresAt()->getTimestamp(),
|
||||
'path' => '/',
|
||||
'secure' => true,
|
||||
'httponly' => true,
|
||||
'samesite' => 'Strict'
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
return new JsonResult($responseData);
|
||||
}
|
||||
|
||||
// Handle different failure types
|
||||
return match (true) {
|
||||
$result->isRateLimited() => new JsonResult([
|
||||
'success' => false,
|
||||
'error' => 'Too many attempts',
|
||||
'retry_after' => $result->getRetryAfter()
|
||||
], 429),
|
||||
|
||||
$result->isAccountLocked() => new JsonResult([
|
||||
'success' => false,
|
||||
'error' => 'Account temporarily locked',
|
||||
'locked_until' => $result->getLockoutExpiresAt()?->format(\DateTimeInterface::ATOM)
|
||||
], 423),
|
||||
|
||||
default => new JsonResult([
|
||||
'success' => false,
|
||||
'error' => 'Invalid credentials'
|
||||
], 401)
|
||||
};
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Auto-Login with Remember Token
|
||||
|
||||
```php
|
||||
#[Route(path: '/auth/check', method: Method::GET)]
|
||||
final readonly class AuthCheckController
|
||||
{
|
||||
public function __construct(
|
||||
private AuthenticationService $authService,
|
||||
private SessionManager $sessionManager
|
||||
) {}
|
||||
|
||||
public function check(HttpRequest $request): JsonResult
|
||||
{
|
||||
$sessionId = $this->sessionManager->getCurrentSessionId();
|
||||
|
||||
// Try session authentication first
|
||||
if ($sessionId) {
|
||||
$result = $this->authService->authenticateWithSession(
|
||||
$sessionId,
|
||||
IpAddress::fromRequest()
|
||||
);
|
||||
|
||||
if ($result->isSuccess()) {
|
||||
return new JsonResult([
|
||||
'authenticated' => true,
|
||||
'user' => $this->formatUser($result->getUser()),
|
||||
'session' => $this->formatSession($result->getSession())
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
// Try remember token authentication
|
||||
$rememberToken = $request->getCookie('remember_token');
|
||||
if ($rememberToken) {
|
||||
$result = $this->authService->authenticateWithRememberToken(
|
||||
$rememberToken,
|
||||
IpAddress::fromRequest()
|
||||
);
|
||||
|
||||
if ($result->isSuccess()) {
|
||||
// Start new session
|
||||
$session = $result->getSession();
|
||||
$this->sessionManager->start($session->getId());
|
||||
$this->sessionManager->set('user_id', $result->getUser()->getId());
|
||||
|
||||
// Update remember token cookie
|
||||
$newRememberToken = $result->getRememberToken();
|
||||
if ($newRememberToken) {
|
||||
setcookie(
|
||||
'remember_token',
|
||||
$newRememberToken->getPlainTextValue(),
|
||||
[
|
||||
'expires' => $newRememberToken->getExpiresAt()->getTimestamp(),
|
||||
'path' => '/',
|
||||
'secure' => true,
|
||||
'httponly' => true,
|
||||
'samesite' => 'Strict'
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
return new JsonResult([
|
||||
'authenticated' => true,
|
||||
'user' => $this->formatUser($result->getUser()),
|
||||
'session' => $this->formatSession($session)
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
return new JsonResult(['authenticated' => false]);
|
||||
}
|
||||
|
||||
private function formatUser(User $user): array
|
||||
{
|
||||
return [
|
||||
'id' => $user->getId(),
|
||||
'email' => (string) $user->getEmail(),
|
||||
'username' => $user->getUsername(),
|
||||
'last_login' => $user->getLastLoginAt()?->format(\DateTimeInterface::ATOM)
|
||||
];
|
||||
}
|
||||
|
||||
private function formatSession(AuthenticationSession $session): array
|
||||
{
|
||||
return [
|
||||
'id' => $session->getId()->toString(),
|
||||
'created_at' => $session->getCreatedAt()->format(\DateTimeInterface::ATOM),
|
||||
'expires_at' => $session->getExpiresAt()->format(\DateTimeInterface::ATOM),
|
||||
'last_activity' => $session->getLastActivity()->format(\DateTimeInterface::ATOM)
|
||||
];
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Password Management
|
||||
|
||||
### Password Change
|
||||
|
||||
```php
|
||||
#[Route(path: '/account/change-password', method: Method::POST)]
|
||||
final readonly class ChangePasswordController
|
||||
{
|
||||
public function __construct(
|
||||
private AuthenticationService $authService,
|
||||
private SessionManager $sessionManager
|
||||
) {}
|
||||
|
||||
public function changePassword(ChangePasswordRequest $request): JsonResult
|
||||
{
|
||||
$userId = $this->sessionManager->get('user_id');
|
||||
if (!$userId) {
|
||||
return new JsonResult(['error' => 'Not authenticated'], 401);
|
||||
}
|
||||
|
||||
$result = $this->authService->changePassword(
|
||||
userId: $userId,
|
||||
currentPassword: $request->currentPassword,
|
||||
newPassword: $request->newPassword
|
||||
);
|
||||
|
||||
if ($result->isSuccess()) {
|
||||
return new JsonResult([
|
||||
'success' => true,
|
||||
'message' => 'Password changed successfully'
|
||||
]);
|
||||
}
|
||||
|
||||
if ($result->hasValidationErrors()) {
|
||||
return new JsonResult([
|
||||
'success' => false,
|
||||
'validation_errors' => $result->getValidation()->toArray()
|
||||
], 400);
|
||||
}
|
||||
|
||||
return new JsonResult([
|
||||
'success' => false,
|
||||
'error' => $result->getErrorMessage()
|
||||
], 400);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Password Reset Flow
|
||||
|
||||
```php
|
||||
final readonly class PasswordResetService
|
||||
{
|
||||
public function __construct(
|
||||
private PasswordHasher $passwordHasher,
|
||||
private UserRepository $userRepository,
|
||||
private TokenGenerator $tokenGenerator,
|
||||
private EmailService $emailService
|
||||
) {}
|
||||
|
||||
public function initiateReset(string $email): PasswordResetResult
|
||||
{
|
||||
$user = $this->userRepository->findByEmail($email);
|
||||
if (!$user) {
|
||||
// Return success even if user not found (security)
|
||||
return PasswordResetResult::success();
|
||||
}
|
||||
|
||||
// Generate secure reset token
|
||||
$token = $this->tokenGenerator->generateSecureToken(32);
|
||||
$tokenHash = Hash::sha256($token);
|
||||
$expiresAt = new \DateTimeImmutable('+1 hour');
|
||||
|
||||
$resetToken = new PasswordResetToken(
|
||||
hash: $tokenHash,
|
||||
userId: $user->getId(),
|
||||
createdAt: new \DateTimeImmutable(),
|
||||
expiresAt: $expiresAt
|
||||
);
|
||||
|
||||
$this->userRepository->storePasswordResetToken($resetToken);
|
||||
|
||||
// Send reset email
|
||||
$this->emailService->sendPasswordResetEmail($user->getEmail(), $token);
|
||||
|
||||
return PasswordResetResult::success();
|
||||
}
|
||||
|
||||
public function resetPassword(
|
||||
string $token,
|
||||
#[SensitiveParameter] string $newPassword
|
||||
): PasswordResetResult {
|
||||
$tokenHash = Hash::sha256($token);
|
||||
$resetToken = $this->userRepository->findPasswordResetToken($tokenHash);
|
||||
|
||||
if (!$resetToken || $resetToken->isExpired()) {
|
||||
return PasswordResetResult::failed('Invalid or expired reset token');
|
||||
}
|
||||
|
||||
$user = $this->userRepository->findById($resetToken->getUserId());
|
||||
if (!$user) {
|
||||
return PasswordResetResult::failed('User not found');
|
||||
}
|
||||
|
||||
// Validate new password
|
||||
$validation = $this->passwordHasher->validatePasswordStrength($newPassword);
|
||||
if (!$validation->isValid) {
|
||||
return PasswordResetResult::validationFailed($validation);
|
||||
}
|
||||
|
||||
// Hash new password
|
||||
$hashedPassword = $this->passwordHasher->hash($newPassword);
|
||||
|
||||
// Update password and cleanup
|
||||
$this->userRepository->updateUserPassword($user->getId(), $hashedPassword);
|
||||
$this->userRepository->deletePasswordResetToken($tokenHash);
|
||||
$this->userRepository->deleteAllUserSessions($user->getId());
|
||||
|
||||
return PasswordResetResult::success();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Advanced Security Features
|
||||
|
||||
### Multi-Factor Authentication Setup
|
||||
|
||||
```php
|
||||
final readonly class MfaService
|
||||
{
|
||||
public function __construct(
|
||||
private PasswordHasher $passwordHasher,
|
||||
private TotpService $totpService,
|
||||
private UserRepository $userRepository
|
||||
) {}
|
||||
|
||||
public function setupTotp(string $userId): MfaSetupResult
|
||||
{
|
||||
$user = $this->userRepository->findById($userId);
|
||||
if (!$user) {
|
||||
return MfaSetupResult::failed('User not found');
|
||||
}
|
||||
|
||||
// Generate TOTP secret
|
||||
$secret = $this->totpService->generateSecret();
|
||||
|
||||
// Create QR code data
|
||||
$qrCodeUri = $this->totpService->getQrCodeUri(
|
||||
secret: $secret,
|
||||
accountName: (string) $user->getEmail(),
|
||||
issuer: 'Your App Name'
|
||||
);
|
||||
|
||||
// Store temporary secret (not activated until verified)
|
||||
$tempSecret = new TempTotpSecret(
|
||||
userId: $userId,
|
||||
secret: $secret,
|
||||
createdAt: new \DateTimeImmutable(),
|
||||
expiresAt: new \DateTimeImmutable('+10 minutes')
|
||||
);
|
||||
|
||||
$this->userRepository->storeTempTotpSecret($tempSecret);
|
||||
|
||||
return MfaSetupResult::success($secret, $qrCodeUri);
|
||||
}
|
||||
|
||||
public function verifyAndActivateTotp(
|
||||
string $userId,
|
||||
string $totpCode
|
||||
): MfaActivationResult {
|
||||
$tempSecret = $this->userRepository->findTempTotpSecret($userId);
|
||||
if (!$tempSecret || $tempSecret->isExpired()) {
|
||||
return MfaActivationResult::failed('Setup expired, please restart');
|
||||
}
|
||||
|
||||
// Verify TOTP code
|
||||
if (!$this->totpService->verify($totpCode, $tempSecret->getSecret())) {
|
||||
return MfaActivationResult::failed('Invalid TOTP code');
|
||||
}
|
||||
|
||||
// Activate TOTP for user
|
||||
$this->userRepository->activateTotpForUser($userId, $tempSecret->getSecret());
|
||||
$this->userRepository->deleteTempTotpSecret($userId);
|
||||
|
||||
// Generate backup codes
|
||||
$backupCodes = $this->generateBackupCodes($userId);
|
||||
|
||||
return MfaActivationResult::success($backupCodes);
|
||||
}
|
||||
|
||||
private function generateBackupCodes(string $userId): array
|
||||
{
|
||||
$codes = [];
|
||||
for ($i = 0; $i < 8; $i++) {
|
||||
$code = $this->generateReadableCode();
|
||||
$codes[] = $code;
|
||||
|
||||
$backupCode = new MfaBackupCode(
|
||||
userId: $userId,
|
||||
code: Hash::sha256($code),
|
||||
createdAt: new \DateTimeImmutable(),
|
||||
usedAt: null
|
||||
);
|
||||
|
||||
$this->userRepository->storeBackupCode($backupCode);
|
||||
}
|
||||
|
||||
return $codes;
|
||||
}
|
||||
|
||||
private function generateReadableCode(): string
|
||||
{
|
||||
// Generate 8-digit backup code
|
||||
return sprintf('%04d-%04d', random_int(1000, 9999), random_int(1000, 9999));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Session Management with Device Tracking
|
||||
|
||||
```php
|
||||
final readonly class DeviceTrackingService
|
||||
{
|
||||
public function __construct(
|
||||
private AuthenticationService $authService,
|
||||
private SessionRepository $sessionRepository
|
||||
) {}
|
||||
|
||||
public function authenticateWithDeviceTracking(
|
||||
string $identifier,
|
||||
#[SensitiveParameter] string $password,
|
||||
HttpRequest $request
|
||||
): AuthenticationResult {
|
||||
$ipAddress = IpAddress::fromRequest();
|
||||
$userAgent = $request->server->getUserAgent();
|
||||
|
||||
$result = $this->authService->authenticate($identifier, $password, $ipAddress);
|
||||
|
||||
if ($result->isSuccess()) {
|
||||
$session = $result->getSession();
|
||||
$user = $result->getUser();
|
||||
|
||||
// Create device fingerprint
|
||||
$deviceInfo = new DeviceInfo(
|
||||
userAgent: $userAgent,
|
||||
ipAddress: $ipAddress,
|
||||
screenResolution: $request->headers->get('X-Screen-Resolution'),
|
||||
timezone: $request->headers->get('X-Timezone'),
|
||||
language: $request->headers->get('Accept-Language')
|
||||
);
|
||||
|
||||
// Check if this is a new device
|
||||
$isNewDevice = !$this->sessionRepository->hasRecentSessionForDevice(
|
||||
userId: $user->getId(),
|
||||
deviceFingerprint: $deviceInfo->getFingerprint(),
|
||||
withinDays: 30
|
||||
);
|
||||
|
||||
if ($isNewDevice) {
|
||||
// Send security notification
|
||||
$this->sendNewDeviceNotification($user, $deviceInfo, $ipAddress);
|
||||
}
|
||||
|
||||
// Update session with device info
|
||||
$this->sessionRepository->updateSessionDeviceInfo($session->getId(), $deviceInfo);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function listActiveSessions(string $userId): array
|
||||
{
|
||||
$sessions = $this->sessionRepository->getActiveSessionsForUser($userId);
|
||||
|
||||
return array_map(function (AuthenticationSession $session) {
|
||||
$deviceInfo = $this->sessionRepository->getSessionDeviceInfo($session->getId());
|
||||
|
||||
return [
|
||||
'id' => $session->getId()->toString(),
|
||||
'created_at' => $session->getCreatedAt()->format(\DateTimeInterface::ATOM),
|
||||
'last_activity' => $session->getLastActivity()->format(\DateTimeInterface::ATOM),
|
||||
'ip_address' => (string) $session->getIpAddress(),
|
||||
'device_info' => $deviceInfo?->toArray(),
|
||||
'is_current' => $this->isCurrentSession($session->getId())
|
||||
];
|
||||
}, $sessions);
|
||||
}
|
||||
|
||||
public function revokeSession(string $userId, SessionId $sessionId): bool
|
||||
{
|
||||
// Verify session belongs to user
|
||||
$session = $this->sessionRepository->findSessionById($sessionId);
|
||||
if (!$session || $session->getUserId() !== $userId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->authService->logout($sessionId);
|
||||
}
|
||||
|
||||
private function sendNewDeviceNotification(
|
||||
User $user,
|
||||
DeviceInfo $deviceInfo,
|
||||
IpAddress $ipAddress
|
||||
): void {
|
||||
// Implementation would send email/SMS notification
|
||||
// about login from new device
|
||||
}
|
||||
|
||||
private function isCurrentSession(SessionId $sessionId): bool
|
||||
{
|
||||
// Check if this is the current session
|
||||
return session_id() === $sessionId->toString();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Rate Limiting & Security
|
||||
|
||||
### Custom Rate Limiting Implementation
|
||||
|
||||
```php
|
||||
final readonly class CustomRateLimitService implements RateLimitService
|
||||
{
|
||||
public function __construct(
|
||||
private Cache $cache,
|
||||
private int $maxAttempts = 5,
|
||||
private int $windowSeconds = 300,
|
||||
private int $lockoutDuration = 900
|
||||
) {}
|
||||
|
||||
public function isRateLimited(IpAddress $ipAddress, string $action): bool
|
||||
{
|
||||
$key = $this->getRateLimitKey($ipAddress, $action);
|
||||
$attempts = $this->cache->get($key, 0);
|
||||
|
||||
return $attempts >= $this->maxAttempts;
|
||||
}
|
||||
|
||||
public function recordAttempt(IpAddress $ipAddress, string $action): void
|
||||
{
|
||||
$key = $this->getRateLimitKey($ipAddress, $action);
|
||||
$attempts = $this->cache->get($key, 0);
|
||||
|
||||
$this->cache->set($key, $attempts + 1, $this->windowSeconds);
|
||||
|
||||
if ($attempts + 1 >= $this->maxAttempts) {
|
||||
$lockoutKey = $this->getLockoutKey($ipAddress, $action);
|
||||
$this->cache->set($lockoutKey, true, $this->lockoutDuration);
|
||||
}
|
||||
}
|
||||
|
||||
public function clearAttempts(IpAddress $ipAddress, string $action): void
|
||||
{
|
||||
$key = $this->getRateLimitKey($ipAddress, $action);
|
||||
$lockoutKey = $this->getLockoutKey($ipAddress, $action);
|
||||
|
||||
$this->cache->forget($key);
|
||||
$this->cache->forget($lockoutKey);
|
||||
}
|
||||
|
||||
public function getRetryAfter(IpAddress $ipAddress, string $action): int
|
||||
{
|
||||
$lockoutKey = $this->getLockoutKey($ipAddress, $action);
|
||||
$lockoutExpiry = $this->cache->get($lockoutKey);
|
||||
|
||||
if (!$lockoutExpiry) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return max(0, $lockoutExpiry - time());
|
||||
}
|
||||
|
||||
private function getRateLimitKey(IpAddress $ipAddress, string $action): string
|
||||
{
|
||||
return sprintf('rate_limit:%s:%s', $action, (string) $ipAddress);
|
||||
}
|
||||
|
||||
private function getLockoutKey(IpAddress $ipAddress, string $action): string
|
||||
{
|
||||
return sprintf('lockout:%s:%s', $action, (string) $ipAddress);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Testing Examples
|
||||
|
||||
### Authentication Service Testing
|
||||
|
||||
```php
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use App\Framework\Auth\AuthenticationService;
|
||||
|
||||
final class AuthenticationServiceTest extends TestCase
|
||||
{
|
||||
private AuthenticationService $authService;
|
||||
private PasswordHasher $passwordHasher;
|
||||
private MockAuthenticationRepository $repository;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->passwordHasher = new PasswordHasher(
|
||||
kdf: new MockKeyDerivationFunction(),
|
||||
defaultAlgorithm: 'argon2id',
|
||||
defaultSecurityLevel: PasswordHasher::LEVEL_LOW // Fast for testing
|
||||
);
|
||||
|
||||
$this->repository = new MockAuthenticationRepository();
|
||||
|
||||
$this->authService = new AuthenticationService(
|
||||
passwordHasher: $this->passwordHasher,
|
||||
sessionIdGenerator: new MockSessionIdGenerator(),
|
||||
repository: $this->repository
|
||||
);
|
||||
}
|
||||
|
||||
public function test_successful_authentication(): void
|
||||
{
|
||||
$password = 'SecurePassword123!';
|
||||
$hashedPassword = $this->passwordHasher->hash($password);
|
||||
|
||||
$user = new User(
|
||||
id: 'user-1',
|
||||
email: new Email('user@example.com'),
|
||||
username: 'testuser',
|
||||
hashedPassword: $hashedPassword
|
||||
);
|
||||
|
||||
$this->repository->addUser($user);
|
||||
|
||||
$result = $this->authService->authenticate(
|
||||
identifier: 'user@example.com',
|
||||
password: $password,
|
||||
ipAddress: IpAddress::localhost()
|
||||
);
|
||||
|
||||
$this->assertTrue($result->isSuccess());
|
||||
$this->assertEquals($user->getId(), $result->getUser()->getId());
|
||||
$this->assertInstanceOf(AuthenticationSession::class, $result->getSession());
|
||||
}
|
||||
|
||||
public function test_failed_authentication_with_invalid_password(): void
|
||||
{
|
||||
$hashedPassword = $this->passwordHasher->hash('CorrectPassword123!');
|
||||
|
||||
$user = new User(
|
||||
id: 'user-1',
|
||||
email: new Email('user@example.com'),
|
||||
username: 'testuser',
|
||||
hashedPassword: $hashedPassword
|
||||
);
|
||||
|
||||
$this->repository->addUser($user);
|
||||
|
||||
$result = $this->authService->authenticate(
|
||||
identifier: 'user@example.com',
|
||||
password: 'WrongPassword',
|
||||
ipAddress: IpAddress::localhost()
|
||||
);
|
||||
|
||||
$this->assertFalse($result->isSuccess());
|
||||
$this->assertEquals('Invalid credentials', $result->getErrorMessage());
|
||||
}
|
||||
|
||||
public function test_account_lockout_after_max_attempts(): void
|
||||
{
|
||||
$hashedPassword = $this->passwordHasher->hash('CorrectPassword123!');
|
||||
|
||||
$user = new User(
|
||||
id: 'user-1',
|
||||
email: new Email('user@example.com'),
|
||||
username: 'testuser',
|
||||
hashedPassword: $hashedPassword
|
||||
);
|
||||
|
||||
$this->repository->addUser($user);
|
||||
|
||||
// Simulate failed attempts
|
||||
for ($i = 0; $i < AuthenticationService::MAX_LOGIN_ATTEMPTS; $i++) {
|
||||
$this->authService->authenticate(
|
||||
identifier: 'user@example.com',
|
||||
password: 'WrongPassword',
|
||||
ipAddress: IpAddress::localhost()
|
||||
);
|
||||
}
|
||||
|
||||
// Next attempt should be locked
|
||||
$result = $this->authService->authenticate(
|
||||
identifier: 'user@example.com',
|
||||
password: 'CorrectPassword123!',
|
||||
ipAddress: IpAddress::localhost()
|
||||
);
|
||||
|
||||
$this->assertTrue($result->isAccountLocked());
|
||||
$this->assertInstanceOf(\DateTimeImmutable::class, $result->getLockoutExpiresAt());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Password Hashing Testing
|
||||
|
||||
```php
|
||||
final class PasswordHasherTest extends TestCase
|
||||
{
|
||||
private PasswordHasher $passwordHasher;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->passwordHasher = new PasswordHasher(
|
||||
kdf: new MockKeyDerivationFunction(),
|
||||
defaultAlgorithm: 'argon2id',
|
||||
defaultSecurityLevel: PasswordHasher::LEVEL_LOW
|
||||
);
|
||||
}
|
||||
|
||||
public function test_password_hashing_and_verification(): void
|
||||
{
|
||||
$password = 'TestPassword123!';
|
||||
|
||||
$hashedPassword = $this->passwordHasher->hash($password);
|
||||
|
||||
$this->assertInstanceOf(HashedPassword::class, $hashedPassword);
|
||||
$this->assertEquals('argon2id', $hashedPassword->getAlgorithm());
|
||||
$this->assertTrue($this->passwordHasher->verify($password, $hashedPassword));
|
||||
$this->assertFalse($this->passwordHasher->verify('WrongPassword', $hashedPassword));
|
||||
}
|
||||
|
||||
public function test_password_strength_validation(): void
|
||||
{
|
||||
$weakPassword = 'weak';
|
||||
$strongPassword = 'StrongPassword123!@#';
|
||||
|
||||
$weakValidation = $this->passwordHasher->validatePasswordStrength($weakPassword);
|
||||
$strongValidation = $this->passwordHasher->validatePasswordStrength($strongPassword);
|
||||
|
||||
$this->assertFalse($weakValidation->isValid);
|
||||
$this->assertNotEmpty($weakValidation->errors);
|
||||
$this->assertEquals(PasswordStrength::WEAK, $weakValidation->strength);
|
||||
|
||||
$this->assertTrue($strongValidation->isValid);
|
||||
$this->assertEmpty($strongValidation->errors);
|
||||
$this->assertContains($strongValidation->strength, [
|
||||
PasswordStrength::STRONG,
|
||||
PasswordStrength::VERY_STRONG
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_secure_password_generation(): void
|
||||
{
|
||||
$password = $this->passwordHasher->generateSecurePassword(16);
|
||||
|
||||
$this->assertEquals(16, strlen($password));
|
||||
|
||||
$validation = $this->passwordHasher->validatePasswordStrength($password);
|
||||
$this->assertTrue($validation->isValid);
|
||||
$this->assertGreaterThanOrEqual(70, $validation->strengthScore);
|
||||
}
|
||||
}
|
||||
310
docs/components/auth/index.md
Normal file
310
docs/components/auth/index.md
Normal file
@@ -0,0 +1,310 @@
|
||||
# Auth Module
|
||||
|
||||
**Sichere Authentifizierungs- und Autorisierungskomponenten** für das Custom PHP Framework.
|
||||
|
||||
## Überblick
|
||||
|
||||
Das Auth Module bietet umfassende Sicherheitsfeatures für Benutzerauthentifizierung, Passwort-Management und Session-Verwaltung. Es integriert sich nahtlos mit dem Cryptography Module für maximale Sicherheit.
|
||||
|
||||
## Kernkomponenten
|
||||
|
||||
### PasswordHasher Service
|
||||
Sichere Passwort-Hashing und -Verifizierung mit automatischem Rehashing.
|
||||
|
||||
```php
|
||||
use App\Framework\Auth\PasswordHasher;
|
||||
use App\Framework\Cryptography\KeyDerivationFunction;
|
||||
|
||||
$passwordHasher = new PasswordHasher(
|
||||
kdf: $keyDerivationFunction,
|
||||
defaultAlgorithm: 'argon2id',
|
||||
defaultSecurityLevel: PasswordHasher::LEVEL_STANDARD
|
||||
);
|
||||
|
||||
// Passwort hashen
|
||||
$hashedPassword = $passwordHasher->hash('userPassword123');
|
||||
|
||||
// Passwort verifizieren
|
||||
$isValid = $passwordHasher->verify('userPassword123', $hashedPassword);
|
||||
|
||||
// Passwort-Stärke validieren
|
||||
$validation = $passwordHasher->validatePasswordStrength('userPassword123');
|
||||
if (!$validation->isValid) {
|
||||
echo implode(', ', $validation->errors);
|
||||
}
|
||||
```
|
||||
|
||||
### HashedPassword Value Object
|
||||
Immutables Value Object für gehashte Passwörter mit Metadaten.
|
||||
|
||||
```php
|
||||
use App\Framework\Auth\HashedPassword;
|
||||
use App\Framework\Auth\PasswordStrength;
|
||||
|
||||
// Aus DerivedKey erstellen
|
||||
$hashedPassword = HashedPassword::fromDerivedKey($derivedKey);
|
||||
|
||||
// Eigenschaften abrufen
|
||||
$algorithm = $hashedPassword->getAlgorithm(); // 'argon2id'
|
||||
$parameters = $hashedPassword->getParameters(); // ['memory_cost' => 65536, ...]
|
||||
$strength = $hashedPassword->getStrength(); // PasswordStrength::STRONG
|
||||
$createdAt = $hashedPassword->getCreatedAt(); // DateTimeImmutable
|
||||
|
||||
// Rehashing-Prüfung
|
||||
$needsRehash = $hashedPassword->needsRehash('argon2id', [
|
||||
'memory_cost' => 131072, // Höhere Sicherheitsanforderungen
|
||||
'time_cost' => 6
|
||||
]);
|
||||
|
||||
// Sicherheits-Bewertung
|
||||
$assessment = $hashedPassword->assessSecurity();
|
||||
echo $assessment->getSummary(); // "Strong security with Argon2ID (2024 standards)"
|
||||
```
|
||||
|
||||
### AuthenticationService
|
||||
Zentrale Authentifizierungslogik mit erweiterten Sicherheitsfeatures.
|
||||
|
||||
```php
|
||||
use App\Framework\Auth\AuthenticationService;
|
||||
use App\Framework\Http\IpAddress;
|
||||
|
||||
$authService = new AuthenticationService(
|
||||
passwordHasher: $passwordHasher,
|
||||
sessionIdGenerator: $sessionIdGenerator,
|
||||
repository: $authRepository
|
||||
);
|
||||
|
||||
// Benutzer authentifizieren
|
||||
$result = $authService->authenticate(
|
||||
identifier: 'user@example.com',
|
||||
password: 'userPassword123',
|
||||
ipAddress: IpAddress::from('192.168.1.1'),
|
||||
remember: true
|
||||
);
|
||||
|
||||
if ($result->isSuccess()) {
|
||||
$user = $result->getUser();
|
||||
$session = $result->getSession();
|
||||
$rememberToken = $result->getRememberToken(); // nullable
|
||||
}
|
||||
|
||||
// Mit Session authentifizieren
|
||||
$result = $authService->authenticateWithSession(
|
||||
$sessionId,
|
||||
IpAddress::from('192.168.1.1')
|
||||
);
|
||||
|
||||
// Mit Remember Token authentifizieren
|
||||
$result = $authService->authenticateWithRememberToken(
|
||||
$tokenValue,
|
||||
IpAddress::from('192.168.1.1')
|
||||
);
|
||||
```
|
||||
|
||||
### PasswordValidationResult Value Object
|
||||
Detaillierte Passwort-Validierungsergebnisse.
|
||||
|
||||
```php
|
||||
use App\Framework\Auth\PasswordValidationResult;
|
||||
|
||||
$validation = $passwordHasher->validatePasswordStrength('weakpass');
|
||||
|
||||
// Validierungsstatus prüfen
|
||||
$isValid = $validation->isValid; // false
|
||||
$errors = $validation->errors; // ['Password must be at least 8 characters long']
|
||||
$warnings = $validation->warnings; // ['Consider using more character types']
|
||||
$score = $validation->strengthScore; // 45
|
||||
$strength = $validation->strength; // PasswordStrength::WEAK
|
||||
|
||||
// Sicherheitslevel prüfen
|
||||
$meetsMinimum = $validation->meetsMinimumRequirements(); // false
|
||||
$isRecommended = $validation->isRecommended(); // false
|
||||
|
||||
// Zusammenfassung
|
||||
echo $validation->getSummary();
|
||||
// "Password does not meet requirements: Password must be at least 8 characters long"
|
||||
|
||||
// API-Response Format
|
||||
$apiResponse = $validation->toArray();
|
||||
```
|
||||
|
||||
## Sicherheitsfeatures
|
||||
|
||||
### Rate Limiting & Account Lockout
|
||||
Schutz vor Brute-Force-Angriffen durch intelligente Rate-Limitierung.
|
||||
|
||||
```php
|
||||
// Automatisches Rate Limiting in AuthenticationService
|
||||
const MAX_LOGIN_ATTEMPTS = 5;
|
||||
const LOCKOUT_DURATION = 900; // 15 Minuten
|
||||
const RATE_LIMIT_WINDOW = 300; // 5 Minuten
|
||||
|
||||
// Rate Limiting wird automatisch angewendet
|
||||
$result = $authService->authenticate($email, $password, $ipAddress);
|
||||
|
||||
if ($result->isRateLimited()) {
|
||||
$retryAfter = $result->getRetryAfter();
|
||||
echo "Rate limit exceeded. Retry after {$retryAfter} seconds.";
|
||||
}
|
||||
|
||||
if ($result->isAccountLocked()) {
|
||||
$expiresAt = $result->getLockoutExpiresAt();
|
||||
echo "Account locked until {$expiresAt->format('Y-m-d H:i:s')}";
|
||||
}
|
||||
```
|
||||
|
||||
### Session-Sicherheit
|
||||
Sichere Session-Verwaltung mit IP-Tracking und automatischer Rotation.
|
||||
|
||||
```php
|
||||
// Sessions werden automatisch erstellt und verwaltet
|
||||
const SESSION_TIMEOUT = 3600; // 1 Stunde
|
||||
const REMEMBER_TOKEN_LENGTH = 32; // 256-bit Token
|
||||
|
||||
// Session-Features:
|
||||
// - Automatische IP-Konsistenz-Prüfung
|
||||
// - Session-Timeout-Management
|
||||
// - Aktivitäts-Tracking
|
||||
// - Sichere Session-IDs über SessionIdGenerator
|
||||
```
|
||||
|
||||
### Token-Hashing mit Hash Value Object
|
||||
Sichere Token-Behandlung mit typisierter Hash-Verwaltung.
|
||||
|
||||
```php
|
||||
// Remember Tokens werden sicher gehasht
|
||||
private function hashToken(string $token): Hash
|
||||
{
|
||||
return Hash::sha256($token);
|
||||
}
|
||||
|
||||
// Vorteile:
|
||||
// - Typsicherheit für Hash-Werte
|
||||
// - Explizite Algorithmus-Angabe (SHA-256)
|
||||
// - Timing-safe Vergleiche mit hash_equals()
|
||||
// - Framework-konsistente Hash-Behandlung
|
||||
// - Automatische Hash-Validierung
|
||||
```
|
||||
|
||||
### Passwort-Sicherheit
|
||||
Umfassende Passwort-Validierung und -Bewertung.
|
||||
|
||||
```php
|
||||
// Automatische Passwort-Stärke-Bewertung
|
||||
$validation = $passwordHasher->validatePasswordStrength($password);
|
||||
|
||||
// Validierungsregeln:
|
||||
// - Minimale Länge (8+ Zeichen)
|
||||
// - Komplexitätsanforderungen (2+ Zeichentypen)
|
||||
// - Häufige Muster-Erkennung
|
||||
// - Sequenzielle Zeichen-Prüfung
|
||||
// - Exzessive Wiederholungen
|
||||
|
||||
// Sichere Passwort-Generierung
|
||||
$securePassword = $passwordHasher->generateSecurePassword(
|
||||
length: 16,
|
||||
includeUppercase: true,
|
||||
includeLowercase: true,
|
||||
includeNumbers: true,
|
||||
includeSpecialChars: true,
|
||||
excludeChars: '0O1l' // Mehrdeutige Zeichen ausschließen
|
||||
);
|
||||
```
|
||||
|
||||
## Integration mit Framework
|
||||
|
||||
### Abhängigkeiten
|
||||
Das Auth Module nutzt vorhandene Framework-Komponenten:
|
||||
|
||||
- **Cryptography Module**: Für sichere Key Derivation Functions
|
||||
- **SessionId/SessionIdGenerator**: Für Session-Management
|
||||
- **IpAddress Value Object**: Für IP-basierte Sicherheitsfeatures
|
||||
- **Hash Value Object**: Für typisierte Hash-Operationen
|
||||
|
||||
### Repository Pattern
|
||||
Flexible Datenpersistierung durch Repository-Abstraktion.
|
||||
|
||||
```php
|
||||
interface AuthenticationRepository
|
||||
{
|
||||
public function findUserByIdentifier(string $identifier): ?User;
|
||||
public function findUserById(string $userId): ?User;
|
||||
public function updateUserPassword(string $userId, HashedPassword $password): bool;
|
||||
|
||||
public function storeSession(AuthenticationSession $session): void;
|
||||
public function findSessionById(SessionId $sessionId): ?AuthenticationSession;
|
||||
public function updateSessionActivity(SessionId $sessionId, ?IpAddress $ipAddress): void;
|
||||
public function deleteSession(SessionId $sessionId): bool;
|
||||
public function deleteAllUserSessions(string $userId): void;
|
||||
|
||||
public function storeRememberToken(RememberToken $token): void;
|
||||
public function findRememberToken(Hash $tokenHash): ?RememberToken;
|
||||
public function deleteRememberToken(Hash $tokenHash): bool;
|
||||
public function deleteAllUserRememberTokens(string $userId): void;
|
||||
|
||||
public function getFailedLoginAttempts(string $userId): int;
|
||||
public function incrementFailedLoginAttempts(string $userId): void;
|
||||
public function clearFailedLoginAttempts(string $userId): void;
|
||||
public function getLastFailedAttemptTime(string $userId): ?\DateTimeImmutable;
|
||||
}
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Sichere Implementation
|
||||
```php
|
||||
// ✅ Sensitive Parameter verwenden
|
||||
public function authenticate(
|
||||
string $identifier,
|
||||
#[SensitiveParameter] string $password,
|
||||
?IpAddress $ipAddress = null
|
||||
): AuthenticationResult
|
||||
|
||||
// ✅ Automatisches Rehashing
|
||||
if ($this->passwordHasher->needsRehash($user->getHashedPassword())) {
|
||||
$newHash = $this->passwordHasher->hash($password);
|
||||
$this->repository->updateUserPassword($user->getId(), $newHash);
|
||||
}
|
||||
|
||||
// ✅ Timing-safe Token-Vergleiche
|
||||
$tokenHash = $this->hashToken($tokenValue);
|
||||
$rememberToken = $this->repository->findRememberToken($tokenHash);
|
||||
```
|
||||
|
||||
### Error Handling
|
||||
```php
|
||||
// ✅ Generische Fehlermeldungen für Sicherheit
|
||||
if (!$user || !$this->passwordHasher->verify($password, $user->getHashedPassword())) {
|
||||
return AuthenticationResult::failed('Invalid credentials');
|
||||
}
|
||||
|
||||
// ✅ Security Event Logging
|
||||
$this->recordSecurityEvent('authentication_failed', [
|
||||
'identifier' => $identifier,
|
||||
'ip_address' => $ipAddress ? (string) $ipAddress : null,
|
||||
'reason' => $reason
|
||||
]);
|
||||
```
|
||||
|
||||
### Performance Optimierung
|
||||
```php
|
||||
// ✅ Konfigurierbare Sicherheitslevel
|
||||
$passwordHasher = new PasswordHasher(
|
||||
kdf: $keyDerivationFunction,
|
||||
defaultAlgorithm: 'argon2id',
|
||||
defaultSecurityLevel: PasswordHasher::LEVEL_STANDARD // vs HIGH für höhere Sicherheit
|
||||
);
|
||||
|
||||
// ✅ Bulk-Operationen für bessere Performance
|
||||
$this->repository->deleteAllUserSessions($userId);
|
||||
$this->repository->deleteAllUserRememberTokens($userId);
|
||||
```
|
||||
|
||||
## Nächste Schritte
|
||||
|
||||
- [ ] AuthenticationRepository Interface implementieren
|
||||
- [ ] Authentication Exceptions definieren
|
||||
- [ ] Unit Tests für alle Komponenten schreiben
|
||||
- [ ] Rate Limiting Service implementieren
|
||||
- [ ] Integration Tests für Authentication Flow
|
||||
- [ ] Performance Benchmarks für Passwort-Hashing
|
||||
904
docs/components/auth/security.md
Normal file
904
docs/components/auth/security.md
Normal file
@@ -0,0 +1,904 @@
|
||||
# Auth Module Security Guidelines
|
||||
|
||||
**Sicherheitsrichtlinien und Best Practices** für das Auth Module des Custom PHP Frameworks.
|
||||
|
||||
## Security Architecture
|
||||
|
||||
### Defense in Depth Strategy
|
||||
|
||||
Das Auth Module implementiert mehrschichtige Sicherheitsmaßnahmen:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Application Layer │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ • Rate Limiting & Account Lockout │
|
||||
│ • Session Security & IP Tracking │
|
||||
│ • Multi-Factor Authentication │
|
||||
│ • Password Strength Validation │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Framework Layer │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ • Secure Password Hashing (Argon2ID) │
|
||||
│ • Timing-Safe Comparisons │
|
||||
│ • Cryptographically Secure Random Generation │
|
||||
│ • Hash Value Object with Algorithm Validation │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Infrastructure Layer │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ • HTTPS Enforcement │
|
||||
│ • Secure Headers (HSTS, CSP) │
|
||||
│ • Database Security & Prepared Statements │
|
||||
│ • Logging & Monitoring │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Password Security
|
||||
|
||||
### Secure Password Hashing
|
||||
|
||||
```php
|
||||
// ✅ Recommended: Argon2ID with high memory cost
|
||||
$passwordHasher = new PasswordHasher(
|
||||
kdf: $keyDerivationFunction,
|
||||
defaultAlgorithm: 'argon2id',
|
||||
defaultSecurityLevel: PasswordHasher::LEVEL_HIGH // Production
|
||||
);
|
||||
|
||||
// Security parameters for different levels:
|
||||
// LEVEL_HIGH: 128 MB memory, 6 iterations, 4 threads
|
||||
// LEVEL_STANDARD: 64 MB memory, 4 iterations, 3 threads
|
||||
// LEVEL_LOW: 32 MB memory, 2 iterations, 2 threads
|
||||
|
||||
// ❌ Avoid: Weak algorithms or low security levels in production
|
||||
$weakHasher = new PasswordHasher(
|
||||
kdf: $kdf,
|
||||
defaultAlgorithm: 'pbkdf2-sha256', // Less secure than Argon2ID
|
||||
defaultSecurityLevel: PasswordHasher::LEVEL_LOW
|
||||
);
|
||||
```
|
||||
|
||||
### Password Policy Enforcement
|
||||
|
||||
```php
|
||||
final readonly class PasswordPolicy
|
||||
{
|
||||
public const int MIN_LENGTH = 12; // Increased from 8
|
||||
public const int MIN_COMPLEXITY_TYPES = 3; // Upper, lower, numbers, special
|
||||
public const int MIN_STRENGTH_SCORE = 70; // High security threshold
|
||||
|
||||
public function validatePassword(string $password): PasswordPolicyResult
|
||||
{
|
||||
$violations = [];
|
||||
|
||||
// Length requirement
|
||||
if (mb_strlen($password) < self::MIN_LENGTH) {
|
||||
$violations[] = "Password must be at least " . self::MIN_LENGTH . " characters";
|
||||
}
|
||||
|
||||
// Complexity requirements
|
||||
$complexityScore = $this->calculateComplexity($password);
|
||||
if ($complexityScore < self::MIN_COMPLEXITY_TYPES) {
|
||||
$violations[] = "Password must include at least " . self::MIN_COMPLEXITY_TYPES . " character types";
|
||||
}
|
||||
|
||||
// Entropy check
|
||||
if ($this->calculateEntropy($password) < 50) {
|
||||
$violations[] = "Password lacks sufficient entropy";
|
||||
}
|
||||
|
||||
// Dictionary check
|
||||
if ($this->isCommonPassword($password)) {
|
||||
$violations[] = "Password is too common";
|
||||
}
|
||||
|
||||
// Personal information check
|
||||
if ($this->containsPersonalInfo($password)) {
|
||||
$violations[] = "Password should not contain personal information";
|
||||
}
|
||||
|
||||
return new PasswordPolicyResult(
|
||||
isValid: empty($violations),
|
||||
violations: $violations,
|
||||
strengthScore: $this->calculateStrengthScore($password)
|
||||
);
|
||||
}
|
||||
|
||||
private function calculateEntropy(string $password): float
|
||||
{
|
||||
$length = strlen($password);
|
||||
$charsetSize = 0;
|
||||
|
||||
if (preg_match('/[a-z]/', $password)) $charsetSize += 26;
|
||||
if (preg_match('/[A-Z]/', $password)) $charsetSize += 26;
|
||||
if (preg_match('/[0-9]/', $password)) $charsetSize += 10;
|
||||
if (preg_match('/[^A-Za-z0-9]/', $password)) $charsetSize += 32;
|
||||
|
||||
return $length * log($charsetSize, 2);
|
||||
}
|
||||
|
||||
private function isCommonPassword(string $password): bool
|
||||
{
|
||||
// Check against common password lists (e.g., Have I Been Pwned)
|
||||
$commonPasswords = [
|
||||
'password', '123456', 'password123', 'admin', 'qwerty',
|
||||
'letmein', 'welcome', 'monkey', 'dragon', '123456789'
|
||||
];
|
||||
|
||||
return in_array(strtolower($password), $commonPasswords, true);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Automatic Password Rehashing
|
||||
|
||||
```php
|
||||
// ✅ Implement automatic rehashing on authentication
|
||||
public function authenticate(string $identifier, string $password): AuthenticationResult
|
||||
{
|
||||
$user = $this->repository->findByIdentifier($identifier);
|
||||
if (!$user || !$this->passwordHasher->verify($password, $user->getHashedPassword())) {
|
||||
return AuthenticationResult::failed('Invalid credentials');
|
||||
}
|
||||
|
||||
// Critical: Check for rehashing need
|
||||
if ($this->passwordHasher->needsRehash($user->getHashedPassword())) {
|
||||
$newHash = $this->passwordHasher->hash($password);
|
||||
$this->repository->updateUserPassword($user->getId(), $newHash);
|
||||
|
||||
$this->logger->info('Password automatically rehashed', [
|
||||
'user_id' => $user->getId(),
|
||||
'old_algorithm' => $user->getHashedPassword()->getAlgorithm(),
|
||||
'new_algorithm' => $newHash->getAlgorithm()
|
||||
]);
|
||||
}
|
||||
|
||||
return AuthenticationResult::success($user);
|
||||
}
|
||||
```
|
||||
|
||||
## Session Security
|
||||
|
||||
### Secure Session Management
|
||||
|
||||
```php
|
||||
final readonly class SecureSessionManager
|
||||
{
|
||||
public function __construct(
|
||||
private AuthenticationService $authService,
|
||||
private SessionSecurity $sessionSecurity
|
||||
) {}
|
||||
|
||||
public function createSecureSession(
|
||||
User $user,
|
||||
IpAddress $ipAddress,
|
||||
UserAgent $userAgent
|
||||
): AuthenticationSession {
|
||||
// Generate cryptographically secure session ID
|
||||
$sessionId = $this->generateSecureSessionId();
|
||||
|
||||
// Create session with security attributes
|
||||
$session = new AuthenticationSession(
|
||||
id: $sessionId,
|
||||
userId: $user->getId(),
|
||||
ipAddress: $ipAddress,
|
||||
userAgent: $userAgent,
|
||||
createdAt: new \DateTimeImmutable(),
|
||||
expiresAt: new \DateTimeImmutable('+1 hour'),
|
||||
lastActivity: new \DateTimeImmutable(),
|
||||
securityAttributes: new SessionSecurityAttributes(
|
||||
isSecure: true,
|
||||
isHttpOnly: true,
|
||||
sameSite: 'Strict',
|
||||
fingerprint: $this->generateFingerprint($ipAddress, $userAgent)
|
||||
)
|
||||
);
|
||||
|
||||
// Set secure session cookie
|
||||
$this->setSecureSessionCookie($sessionId, $session->getExpiresAt());
|
||||
|
||||
return $session;
|
||||
}
|
||||
|
||||
private function generateSecureSessionId(): SessionId
|
||||
{
|
||||
// Use framework's SessionIdGenerator for consistency
|
||||
return $this->sessionIdGenerator->generate();
|
||||
}
|
||||
|
||||
private function setSecureSessionCookie(SessionId $sessionId, \DateTimeImmutable $expiresAt): void
|
||||
{
|
||||
setcookie('session_id', $sessionId->toString(), [
|
||||
'expires' => $expiresAt->getTimestamp(),
|
||||
'path' => '/',
|
||||
'domain' => '', // Let browser determine
|
||||
'secure' => true, // HTTPS only
|
||||
'httponly' => true, // No JavaScript access
|
||||
'samesite' => 'Strict' // CSRF protection
|
||||
]);
|
||||
}
|
||||
|
||||
private function generateFingerprint(IpAddress $ipAddress, UserAgent $userAgent): string
|
||||
{
|
||||
return Hash::sha256(
|
||||
$ipAddress->toString() . '|' .
|
||||
$userAgent->toString() . '|' .
|
||||
$_SERVER['HTTP_ACCEPT_LANGUAGE'] ?? ''
|
||||
)->toString();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Session Hijacking Protection
|
||||
|
||||
```php
|
||||
final readonly class SessionHijackingProtection
|
||||
{
|
||||
public function validateSession(
|
||||
SessionId $sessionId,
|
||||
IpAddress $currentIp,
|
||||
UserAgent $currentUserAgent
|
||||
): SessionValidationResult {
|
||||
$session = $this->repository->findSessionById($sessionId);
|
||||
if (!$session) {
|
||||
return SessionValidationResult::invalid('Session not found');
|
||||
}
|
||||
|
||||
// Check session expiration
|
||||
if ($session->isExpired()) {
|
||||
$this->repository->deleteSession($sessionId);
|
||||
return SessionValidationResult::expired();
|
||||
}
|
||||
|
||||
// IP address consistency check (configurable)
|
||||
if ($this->config->checkIpConsistency) {
|
||||
if (!$session->getIpAddress()->equals($currentIp)) {
|
||||
$this->logSecurityEvent('session_ip_mismatch', [
|
||||
'session_id' => $sessionId->toString(),
|
||||
'original_ip' => (string) $session->getIpAddress(),
|
||||
'current_ip' => (string) $currentIp
|
||||
]);
|
||||
|
||||
// Suspicious activity - invalidate session
|
||||
$this->repository->deleteSession($sessionId);
|
||||
return SessionValidationResult::suspicious('IP address mismatch');
|
||||
}
|
||||
}
|
||||
|
||||
// User Agent consistency (less strict)
|
||||
if (!$this->isUserAgentConsistent($session->getUserAgent(), $currentUserAgent)) {
|
||||
$this->logSecurityEvent('session_user_agent_change', [
|
||||
'session_id' => $sessionId->toString(),
|
||||
'original_ua' => (string) $session->getUserAgent(),
|
||||
'current_ua' => (string) $currentUserAgent
|
||||
]);
|
||||
}
|
||||
|
||||
// Update session activity
|
||||
$this->repository->updateSessionActivity($sessionId, $currentIp);
|
||||
|
||||
return SessionValidationResult::valid($session);
|
||||
}
|
||||
|
||||
private function isUserAgentConsistent(UserAgent $original, UserAgent $current): bool
|
||||
{
|
||||
// Allow minor version changes but detect major browser changes
|
||||
$originalBrowser = $this->extractBrowser($original);
|
||||
$currentBrowser = $this->extractBrowser($current);
|
||||
|
||||
return $originalBrowser === $currentBrowser;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Token Security
|
||||
|
||||
### Remember Token Security with Hash Value Object
|
||||
|
||||
```php
|
||||
final readonly class RememberTokenSecurity
|
||||
{
|
||||
public function createRememberToken(string $userId): RememberToken
|
||||
{
|
||||
// Generate cryptographically secure token
|
||||
$tokenValue = bin2hex(random_bytes(32)); // 256-bit token
|
||||
|
||||
// Hash token using Hash Value Object for type safety
|
||||
$tokenHash = Hash::sha256($tokenValue);
|
||||
|
||||
$rememberToken = new RememberToken(
|
||||
hash: $tokenHash,
|
||||
userId: $userId,
|
||||
createdAt: new \DateTimeImmutable(),
|
||||
expiresAt: new \DateTimeImmutable('+30 days'),
|
||||
lastUsed: null
|
||||
);
|
||||
|
||||
$this->repository->storeRememberToken($rememberToken);
|
||||
|
||||
// Return token with plain text value (only shown once)
|
||||
return $rememberToken->withPlainTextValue($tokenValue);
|
||||
}
|
||||
|
||||
public function validateRememberToken(string $tokenValue): RememberTokenValidation
|
||||
{
|
||||
// Hash provided token for comparison
|
||||
$tokenHash = Hash::sha256($tokenValue);
|
||||
|
||||
$rememberToken = $this->repository->findRememberToken($tokenHash);
|
||||
if (!$rememberToken) {
|
||||
$this->logSecurityEvent('invalid_remember_token', [
|
||||
'token_hash' => $tokenHash->toShort(8) // Only log first 8 chars
|
||||
]);
|
||||
|
||||
return RememberTokenValidation::invalid();
|
||||
}
|
||||
|
||||
// Check expiration
|
||||
if ($rememberToken->isExpired()) {
|
||||
$this->repository->deleteRememberToken($tokenHash);
|
||||
return RememberTokenValidation::expired();
|
||||
}
|
||||
|
||||
// Update last used timestamp
|
||||
$this->repository->updateRememberTokenUsage($tokenHash);
|
||||
|
||||
return RememberTokenValidation::valid($rememberToken);
|
||||
}
|
||||
|
||||
public function rotateRememberToken(Hash $oldTokenHash): RememberToken
|
||||
{
|
||||
$oldToken = $this->repository->findRememberToken($oldTokenHash);
|
||||
if (!$oldToken) {
|
||||
throw new SecurityException('Cannot rotate non-existent token');
|
||||
}
|
||||
|
||||
// Create new token
|
||||
$newToken = $this->createRememberToken($oldToken->getUserId());
|
||||
|
||||
// Delete old token
|
||||
$this->repository->deleteRememberToken($oldTokenHash);
|
||||
|
||||
$this->logSecurityEvent('remember_token_rotated', [
|
||||
'user_id' => $oldToken->getUserId(),
|
||||
'old_token_created' => $oldToken->getCreatedAt()->format('Y-m-d H:i:s')
|
||||
]);
|
||||
|
||||
return $newToken;
|
||||
}
|
||||
}
|
||||
|
||||
// Benefits of using Hash Value Object:
|
||||
// ✅ Type safety - tokens can't be confused with other strings
|
||||
// ✅ Algorithm specification - explicit SHA-256 usage
|
||||
// ✅ Timing-safe comparison - uses hash_equals() internally
|
||||
// ✅ Validation - ensures proper hash format and length
|
||||
// ✅ Framework consistency - follows value object patterns
|
||||
```
|
||||
|
||||
## Rate Limiting & Brute Force Protection
|
||||
|
||||
### Advanced Rate Limiting
|
||||
|
||||
```php
|
||||
final readonly class AdvancedRateLimiter implements RateLimitService
|
||||
{
|
||||
public function __construct(
|
||||
private Cache $cache,
|
||||
private Logger $logger,
|
||||
private RateLimitConfig $config
|
||||
) {}
|
||||
|
||||
public function checkRateLimit(
|
||||
IpAddress $ipAddress,
|
||||
string $action,
|
||||
string $identifier = null
|
||||
): RateLimitResult {
|
||||
// Multiple rate limiting strategies
|
||||
$checks = [
|
||||
$this->checkIpBasedLimit($ipAddress, $action),
|
||||
$this->checkIdentifierBasedLimit($identifier, $action),
|
||||
$this->checkGlobalLimit($action),
|
||||
$this->checkAdaptiveLimit($ipAddress, $action)
|
||||
];
|
||||
|
||||
foreach ($checks as $check) {
|
||||
if ($check->isLimited()) {
|
||||
$this->logRateLimitViolation($ipAddress, $action, $check);
|
||||
return $check;
|
||||
}
|
||||
}
|
||||
|
||||
// Record successful attempt
|
||||
$this->recordAttempt($ipAddress, $action, $identifier);
|
||||
|
||||
return RateLimitResult::allowed();
|
||||
}
|
||||
|
||||
private function checkAdaptiveLimit(IpAddress $ipAddress, string $action): RateLimitResult
|
||||
{
|
||||
$suspiciousScore = $this->calculateSuspiciousScore($ipAddress);
|
||||
|
||||
// Adaptive limits based on suspicious activity
|
||||
$maxAttempts = match (true) {
|
||||
$suspiciousScore > 80 => 1, // Highly suspicious
|
||||
$suspiciousScore > 60 => 2, // Suspicious
|
||||
$suspiciousScore > 40 => 3, // Moderately suspicious
|
||||
default => 5 // Normal
|
||||
};
|
||||
|
||||
$key = "adaptive_limit:{$action}:" . $ipAddress->toString();
|
||||
$attempts = $this->cache->get($key, 0);
|
||||
|
||||
if ($attempts >= $maxAttempts) {
|
||||
return RateLimitResult::limited(
|
||||
reason: 'Adaptive rate limit exceeded',
|
||||
retryAfter: 900,
|
||||
metadata: ['suspicious_score' => $suspiciousScore]
|
||||
);
|
||||
}
|
||||
|
||||
return RateLimitResult::allowed();
|
||||
}
|
||||
|
||||
private function calculateSuspiciousScore(IpAddress $ipAddress): int
|
||||
{
|
||||
$score = 0;
|
||||
|
||||
// Check for various suspicious indicators
|
||||
if ($this->isFromTorNetwork($ipAddress)) $score += 30;
|
||||
if ($this->isFromVpnProvider($ipAddress)) $score += 15;
|
||||
if ($this->hasRecentFailures($ipAddress)) $score += 20;
|
||||
if ($this->isFromUncommonGeolocation($ipAddress)) $score += 10;
|
||||
if ($this->hasRapidRequests($ipAddress)) $score += 25;
|
||||
|
||||
return min(100, $score);
|
||||
}
|
||||
|
||||
private function logRateLimitViolation(
|
||||
IpAddress $ipAddress,
|
||||
string $action,
|
||||
RateLimitResult $result
|
||||
): void {
|
||||
$this->logger->warning('Rate limit violation', [
|
||||
'ip_address' => (string) $ipAddress,
|
||||
'action' => $action,
|
||||
'reason' => $result->getReason(),
|
||||
'retry_after' => $result->getRetryAfter(),
|
||||
'metadata' => $result->getMetadata()
|
||||
]);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Account Lockout Protection
|
||||
|
||||
```php
|
||||
final readonly class AccountLockoutService
|
||||
{
|
||||
public function __construct(
|
||||
private UserRepository $repository,
|
||||
private Logger $logger,
|
||||
private NotificationService $notifications
|
||||
) {}
|
||||
|
||||
public function handleFailedLogin(string $identifier, IpAddress $ipAddress): void
|
||||
{
|
||||
$user = $this->repository->findByIdentifier($identifier);
|
||||
if (!$user) {
|
||||
return; // Don't reveal if user exists
|
||||
}
|
||||
|
||||
$attempts = $this->repository->incrementFailedAttempts($user->getId());
|
||||
|
||||
// Progressive lockout strategy
|
||||
$lockoutDuration = $this->calculateLockoutDuration($attempts);
|
||||
|
||||
if ($attempts >= 3) {
|
||||
$this->repository->lockAccount($user->getId(), $lockoutDuration);
|
||||
|
||||
$this->logger->warning('Account locked due to failed attempts', [
|
||||
'user_id' => $user->getId(),
|
||||
'attempts' => $attempts,
|
||||
'lockout_duration' => $lockoutDuration,
|
||||
'ip_address' => (string) $ipAddress
|
||||
]);
|
||||
|
||||
// Notify user of lockout (rate limited to prevent abuse)
|
||||
$this->sendLockoutNotification($user, $attempts, $lockoutDuration);
|
||||
}
|
||||
}
|
||||
|
||||
private function calculateLockoutDuration(int $attempts): int
|
||||
{
|
||||
// Exponential backoff with jitter
|
||||
return match (true) {
|
||||
$attempts >= 10 => 3600 + random_int(0, 1800), // 1-2.5 hours
|
||||
$attempts >= 7 => 1800 + random_int(0, 900), // 30-45 minutes
|
||||
$attempts >= 5 => 900 + random_int(0, 300), // 15-20 minutes
|
||||
$attempts >= 3 => 300 + random_int(0, 120), // 5-7 minutes
|
||||
default => 0
|
||||
};
|
||||
}
|
||||
|
||||
private function sendLockoutNotification(
|
||||
User $user,
|
||||
int $attempts,
|
||||
int $lockoutDuration
|
||||
): void {
|
||||
// Rate limit notifications to prevent spam
|
||||
$notificationKey = "lockout_notification:" . $user->getId();
|
||||
if ($this->cache->has($notificationKey)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->cache->put($notificationKey, true, 300); // 5 minutes
|
||||
|
||||
$this->notifications->sendSecurityAlert($user->getEmail(), [
|
||||
'type' => 'account_lockout',
|
||||
'attempts' => $attempts,
|
||||
'lockout_duration_minutes' => ceil($lockoutDuration / 60),
|
||||
'timestamp' => new \DateTimeImmutable()
|
||||
]);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Multi-Factor Authentication Security
|
||||
|
||||
### TOTP Implementation with Security Features
|
||||
|
||||
```php
|
||||
final readonly class SecureTotpService
|
||||
{
|
||||
private const int SECRET_LENGTH = 32; // 160-bit secret
|
||||
private const int WINDOW_SIZE = 1; // Allow 1 time step tolerance
|
||||
private const int TIME_STEP = 30; // 30-second time steps
|
||||
|
||||
public function generateSecret(): string
|
||||
{
|
||||
return Base32::encode(random_bytes(self::SECRET_LENGTH));
|
||||
}
|
||||
|
||||
public function verify(string $code, string $secret, ?int $timestamp = null): bool
|
||||
{
|
||||
$timestamp = $timestamp ?? time();
|
||||
$timeStep = intval($timestamp / self::TIME_STEP);
|
||||
|
||||
// Check current and adjacent time windows to account for clock drift
|
||||
for ($i = -self::WINDOW_SIZE; $i <= self::WINDOW_SIZE; $i++) {
|
||||
$calculatedCode = $this->calculateTotp($secret, $timeStep + $i);
|
||||
|
||||
if (hash_equals($code, $calculatedCode)) {
|
||||
// Prevent replay attacks by storing used codes
|
||||
$this->markCodeAsUsed($secret, $timeStep + $i);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private function calculateTotp(string $secret, int $timeStep): string
|
||||
{
|
||||
$binarySecret = Base32::decode($secret);
|
||||
$timeBytes = pack('N*', 0) . pack('N*', $timeStep);
|
||||
|
||||
$hash = hash_hmac('sha1', $timeBytes, $binarySecret, true);
|
||||
$offset = ord($hash[19]) & 0xf;
|
||||
|
||||
$code = (
|
||||
((ord($hash[$offset]) & 0x7f) << 24) |
|
||||
((ord($hash[$offset + 1]) & 0xff) << 16) |
|
||||
((ord($hash[$offset + 2]) & 0xff) << 8) |
|
||||
(ord($hash[$offset + 3]) & 0xff)
|
||||
) % 1000000;
|
||||
|
||||
return str_pad((string) $code, 6, '0', STR_PAD_LEFT);
|
||||
}
|
||||
|
||||
private function markCodeAsUsed(string $secret, int $timeStep): void
|
||||
{
|
||||
// Prevent replay attacks
|
||||
$key = 'totp_used:' . Hash::sha256($secret . ':' . $timeStep)->toShort(16);
|
||||
$this->cache->put($key, true, self::TIME_STEP * 2);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Backup Code Security
|
||||
|
||||
```php
|
||||
final readonly class BackupCodeService
|
||||
{
|
||||
private const int CODE_LENGTH = 8;
|
||||
private const int CODE_COUNT = 8;
|
||||
|
||||
public function generateBackupCodes(string $userId): array
|
||||
{
|
||||
$codes = [];
|
||||
|
||||
for ($i = 0; $i < self::CODE_COUNT; $i++) {
|
||||
$code = $this->generateHumanReadableCode();
|
||||
$codes[] = $code;
|
||||
|
||||
// Store hashed version
|
||||
$backupCode = new MfaBackupCode(
|
||||
userId: $userId,
|
||||
codeHash: Hash::sha256($code),
|
||||
createdAt: new \DateTimeImmutable(),
|
||||
usedAt: null
|
||||
);
|
||||
|
||||
$this->repository->storeBackupCode($backupCode);
|
||||
}
|
||||
|
||||
return $codes;
|
||||
}
|
||||
|
||||
public function verifyBackupCode(string $userId, string $code): bool
|
||||
{
|
||||
$codeHash = Hash::sha256($code);
|
||||
$backupCode = $this->repository->findBackupCode($userId, $codeHash);
|
||||
|
||||
if (!$backupCode || $backupCode->isUsed()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Mark as used (one-time use only)
|
||||
$this->repository->markBackupCodeAsUsed($backupCode, new \DateTimeImmutable());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function generateHumanReadableCode(): string
|
||||
{
|
||||
// Generate readable codes avoiding confusing characters
|
||||
$chars = '23456789ABCDEFGHJKLMNPQRSTUVWXYZ'; // No 0, 1, I, O
|
||||
$code = '';
|
||||
|
||||
for ($i = 0; $i < self::CODE_LENGTH; $i++) {
|
||||
$code .= $chars[random_int(0, strlen($chars) - 1)];
|
||||
}
|
||||
|
||||
// Format as XXXX-XXXX for readability
|
||||
return substr($code, 0, 4) . '-' . substr($code, 4, 4);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Monitoring & Incident Response
|
||||
|
||||
### Security Event Monitoring
|
||||
|
||||
```php
|
||||
final readonly class SecurityEventMonitor
|
||||
{
|
||||
public function __construct(
|
||||
private Logger $securityLogger,
|
||||
private AlertingService $alerting,
|
||||
private MetricsCollector $metrics
|
||||
) {}
|
||||
|
||||
public function recordSecurityEvent(SecurityEvent $event): void
|
||||
{
|
||||
// Log all security events
|
||||
$this->securityLogger->warning('Security event recorded', [
|
||||
'event_type' => $event->getType(),
|
||||
'severity' => $event->getSeverity(),
|
||||
'user_id' => $event->getUserId(),
|
||||
'ip_address' => (string) $event->getIpAddress(),
|
||||
'user_agent' => (string) $event->getUserAgent(),
|
||||
'metadata' => $event->getMetadata(),
|
||||
'timestamp' => $event->getTimestamp()->format('Y-m-d H:i:s')
|
||||
]);
|
||||
|
||||
// Update security metrics
|
||||
$this->metrics->increment('auth.security_events', [
|
||||
'type' => $event->getType(),
|
||||
'severity' => $event->getSeverity()
|
||||
]);
|
||||
|
||||
// Send alerts for critical events
|
||||
if ($event->isCritical()) {
|
||||
$this->alerting->sendSecurityAlert($event);
|
||||
}
|
||||
|
||||
// Check for attack patterns
|
||||
$this->analyzeForAttackPatterns($event);
|
||||
}
|
||||
|
||||
private function analyzeForAttackPatterns(SecurityEvent $event): void
|
||||
{
|
||||
$ipAddress = $event->getIpAddress();
|
||||
$recentEvents = $this->getRecentSecurityEvents($ipAddress, minutes: 10);
|
||||
|
||||
// Detect brute force attacks
|
||||
$failedLogins = array_filter($recentEvents,
|
||||
fn($e) => $e->getType() === 'authentication_failed'
|
||||
);
|
||||
|
||||
if (count($failedLogins) >= 5) {
|
||||
$this->alerting->sendAlert(new BruteForceDetectionAlert(
|
||||
ipAddress: $ipAddress,
|
||||
attemptCount: count($failedLogins),
|
||||
timeWindow: 10
|
||||
));
|
||||
}
|
||||
|
||||
// Detect credential stuffing
|
||||
$uniqueIdentifiers = array_unique(array_map(
|
||||
fn($e) => $e->getMetadata()['identifier'] ?? null,
|
||||
$failedLogins
|
||||
));
|
||||
|
||||
if (count($uniqueIdentifiers) >= 10) {
|
||||
$this->alerting->sendAlert(new CredentialStuffingAlert(
|
||||
ipAddress: $ipAddress,
|
||||
uniqueIdentifiers: count($uniqueIdentifiers)
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Automated Response System
|
||||
|
||||
```php
|
||||
final readonly class AutomatedSecurityResponse
|
||||
{
|
||||
public function handleSecurityEvent(SecurityEvent $event): void
|
||||
{
|
||||
match ($event->getType()) {
|
||||
'brute_force_detected' => $this->handleBruteForce($event),
|
||||
'credential_stuffing_detected' => $this->handleCredentialStuffing($event),
|
||||
'session_hijacking_suspected' => $this->handleSessionHijacking($event),
|
||||
'suspicious_login_pattern' => $this->handleSuspiciousLogin($event),
|
||||
default => null
|
||||
};
|
||||
}
|
||||
|
||||
private function handleBruteForce(SecurityEvent $event): void
|
||||
{
|
||||
$ipAddress = $event->getIpAddress();
|
||||
|
||||
// Immediately block IP for 1 hour
|
||||
$this->firewall->blockIpAddress($ipAddress, duration: 3600);
|
||||
|
||||
// Increase monitoring for this IP
|
||||
$this->monitoring->increaseWatchLevel($ipAddress);
|
||||
|
||||
// Notify security team
|
||||
$this->alerting->sendUrgentAlert(new BruteForceBlockAlert($ipAddress));
|
||||
}
|
||||
|
||||
private function handleSessionHijacking(SecurityEvent $event): void
|
||||
{
|
||||
$userId = $event->getUserId();
|
||||
$sessionId = $event->getMetadata()['session_id'];
|
||||
|
||||
// Immediately terminate all user sessions
|
||||
$this->authService->logoutAll($userId);
|
||||
|
||||
// Require password reset
|
||||
$this->userService->requirePasswordReset($userId);
|
||||
|
||||
// Send security notification to user
|
||||
$user = $this->userRepository->findById($userId);
|
||||
$this->notifications->sendSecurityBreach($user->getEmail(), [
|
||||
'incident_type' => 'session_hijacking',
|
||||
'affected_session' => $sessionId,
|
||||
'timestamp' => $event->getTimestamp()
|
||||
]);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Compliance & Audit
|
||||
|
||||
### GDPR Compliance
|
||||
|
||||
```php
|
||||
final readonly class GdprCompliantAuthService
|
||||
{
|
||||
public function processPersonalData(User $user, string $purpose): void
|
||||
{
|
||||
// Log data processing for GDPR audit trail
|
||||
$this->auditLogger->info('Personal data processed', [
|
||||
'user_id' => $user->getId(),
|
||||
'data_types' => ['email', 'login_timestamps', 'ip_addresses'],
|
||||
'processing_purpose' => $purpose,
|
||||
'legal_basis' => 'legitimate_interest', // or 'consent'
|
||||
'retention_period' => '2_years'
|
||||
]);
|
||||
}
|
||||
|
||||
public function exportUserData(string $userId): UserDataExport
|
||||
{
|
||||
$user = $this->repository->findById($userId);
|
||||
if (!$user) {
|
||||
throw new UserNotFoundException($userId);
|
||||
}
|
||||
|
||||
return new UserDataExport([
|
||||
'personal_data' => [
|
||||
'user_id' => $user->getId(),
|
||||
'email' => (string) $user->getEmail(),
|
||||
'created_at' => $user->getCreatedAt()->format('c'),
|
||||
'last_login' => $user->getLastLoginAt()?->format('c')
|
||||
],
|
||||
'authentication_data' => [
|
||||
'password_last_changed' => $user->getPasswordChangedAt()?->format('c'),
|
||||
'mfa_enabled' => $user->hasMfaEnabled(),
|
||||
'failed_login_attempts' => $this->repository->getFailedLoginAttempts($userId)
|
||||
],
|
||||
'session_data' => $this->getSessionHistory($userId),
|
||||
'security_events' => $this->getSecurityEventHistory($userId)
|
||||
]);
|
||||
}
|
||||
|
||||
public function deleteUserData(string $userId): void
|
||||
{
|
||||
// GDPR right to erasure (right to be forgotten)
|
||||
$this->repository->anonymizeUser($userId);
|
||||
$this->repository->deleteAllUserSessions($userId);
|
||||
$this->repository->deleteAllUserRememberTokens($userId);
|
||||
$this->repository->deleteUserSecurityEvents($userId);
|
||||
|
||||
$this->auditLogger->info('User data deleted per GDPR request', [
|
||||
'user_id' => $userId,
|
||||
'deletion_timestamp' => (new \DateTimeImmutable())->format('c')
|
||||
]);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Security Audit Logging
|
||||
|
||||
```php
|
||||
final readonly class SecurityAuditLogger
|
||||
{
|
||||
public function logAuthenticationEvent(string $eventType, array $context): void
|
||||
{
|
||||
$auditRecord = [
|
||||
'event_type' => $eventType,
|
||||
'timestamp' => (new \DateTimeImmutable())->format('c'),
|
||||
'user_id' => $context['user_id'] ?? null,
|
||||
'session_id' => $context['session_id'] ?? null,
|
||||
'ip_address' => $context['ip_address'] ?? null,
|
||||
'user_agent' => $context['user_agent'] ?? null,
|
||||
'success' => $context['success'] ?? false,
|
||||
'failure_reason' => $context['failure_reason'] ?? null,
|
||||
'risk_score' => $this->calculateRiskScore($context)
|
||||
];
|
||||
|
||||
// Store in secure audit log (append-only, tamper-evident)
|
||||
$this->auditStorage->append($auditRecord);
|
||||
|
||||
// Forward to SIEM if configured
|
||||
if ($this->siemForwarder) {
|
||||
$this->siemForwarder->forward($auditRecord);
|
||||
}
|
||||
}
|
||||
|
||||
private function calculateRiskScore(array $context): int
|
||||
{
|
||||
$score = 0;
|
||||
|
||||
// Location-based risk
|
||||
if (isset($context['ip_address'])) {
|
||||
$location = $this->geolocator->locate($context['ip_address']);
|
||||
if ($location->isHighRiskCountry()) $score += 25;
|
||||
if ($location->isFromTor()) $score += 50;
|
||||
}
|
||||
|
||||
// Time-based risk
|
||||
$hour = (int) date('H');
|
||||
if ($hour < 6 || $hour > 22) $score += 10; // Outside business hours
|
||||
|
||||
// Failure patterns
|
||||
if (!($context['success'] ?? true)) $score += 20;
|
||||
|
||||
return min(100, $score);
|
||||
}
|
||||
}
|
||||
397
docs/components/cryptography/configuration.md
Normal file
397
docs/components/cryptography/configuration.md
Normal file
@@ -0,0 +1,397 @@
|
||||
# Cryptography Module - Konfiguration
|
||||
|
||||
Konfigurationsoptionen und Setup für das Cryptography Module.
|
||||
|
||||
## Service-Registrierung
|
||||
|
||||
### Dependency Injection Setup
|
||||
|
||||
```php
|
||||
use App\Framework\DI\Initializer;
|
||||
use App\Framework\DI\Container;
|
||||
use App\Framework\Cryptography\KeyDerivationFunction;
|
||||
use App\Framework\Cryptography\SecureTokenGenerator;
|
||||
use App\Framework\Cryptography\CryptographicUtilities;
|
||||
use App\Framework\Cryptography\DigitalSignature;
|
||||
use App\Framework\Cryptography\AdvancedHash;
|
||||
use App\Framework\Random\SecureRandomGenerator;
|
||||
|
||||
final readonly class CryptographyInitializer implements Initializer
|
||||
{
|
||||
public function initialize(Container $container): void
|
||||
{
|
||||
// Random Generator als Dependency
|
||||
$container->singleton(RandomGenerator::class, new SecureRandomGenerator());
|
||||
|
||||
// Core Cryptography Services
|
||||
$container->singleton(KeyDerivationFunction::class, function(Container $c) {
|
||||
return new KeyDerivationFunction($c->get(RandomGenerator::class));
|
||||
});
|
||||
|
||||
$container->singleton(SecureTokenGenerator::class, function(Container $c) {
|
||||
return new SecureTokenGenerator($c->get(RandomGenerator::class));
|
||||
});
|
||||
|
||||
$container->singleton(CryptographicUtilities::class, function(Container $c) {
|
||||
return new CryptographicUtilities($c->get(RandomGenerator::class));
|
||||
});
|
||||
|
||||
$container->singleton(DigitalSignature::class, function(Container $c) {
|
||||
return new DigitalSignature($c->get(RandomGenerator::class));
|
||||
});
|
||||
|
||||
$container->singleton(AdvancedHash::class, new AdvancedHash());
|
||||
|
||||
// Factory Methods als Alternative
|
||||
$container->bind('kdf.factory', function(Container $c) {
|
||||
return KeyDerivationFunction::create($c->get(RandomGenerator::class));
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### AppBootstrapper Integration
|
||||
|
||||
```php
|
||||
// In src/Framework/Core/AppBootstrapper.php
|
||||
public function bootstrap(): Application
|
||||
{
|
||||
// ... existing bootstrapping
|
||||
|
||||
$initializer = new CryptographyInitializer();
|
||||
$initializer->initialize($this->container);
|
||||
|
||||
return $application;
|
||||
}
|
||||
```
|
||||
|
||||
## Sicherheitsparameter
|
||||
|
||||
### Key Derivation Function Konfiguration
|
||||
|
||||
```php
|
||||
final readonly class CryptographyConfig
|
||||
{
|
||||
public const DEFAULT_KDF_PARAMETERS = [
|
||||
'pbkdf2-sha256' => [
|
||||
'low' => ['iterations' => 50000, 'key_length' => 32],
|
||||
'standard' => ['iterations' => 100000, 'key_length' => 32],
|
||||
'high' => ['iterations' => 200000, 'key_length' => 32],
|
||||
],
|
||||
'argon2id' => [
|
||||
'low' => ['memory_cost' => 32768, 'time_cost' => 3, 'threads' => 2],
|
||||
'standard' => ['memory_cost' => 65536, 'time_cost' => 4, 'threads' => 3],
|
||||
'high' => ['memory_cost' => 131072, 'time_cost' => 6, 'threads' => 4],
|
||||
],
|
||||
'scrypt' => [
|
||||
'low' => ['cost_parameter' => 8192, 'block_size' => 8, 'parallelization' => 1],
|
||||
'standard' => ['cost_parameter' => 16384, 'block_size' => 8, 'parallelization' => 1],
|
||||
'high' => ['cost_parameter' => 32768, 'block_size' => 8, 'parallelization' => 2],
|
||||
]
|
||||
];
|
||||
|
||||
public static function getRecommendedParameters(
|
||||
string $algorithm,
|
||||
string $securityLevel = 'standard'
|
||||
): array {
|
||||
return self::DEFAULT_KDF_PARAMETERS[$algorithm][$securityLevel] ??
|
||||
throw new InvalidArgumentException("Unsupported configuration: {$algorithm}:{$securityLevel}");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Token-Generator Konfiguration
|
||||
|
||||
```php
|
||||
final readonly class TokenConfig
|
||||
{
|
||||
public const DEFAULT_TOKEN_LENGTHS = [
|
||||
SecureTokenGenerator::TYPE_API_KEY => 32, // 256 Bit
|
||||
SecureTokenGenerator::TYPE_SESSION => 32, // 256 Bit
|
||||
SecureTokenGenerator::TYPE_CSRF => 32, // 256 Bit
|
||||
SecureTokenGenerator::TYPE_REFRESH => 64, // 512 Bit
|
||||
SecureTokenGenerator::TYPE_VERIFICATION => 32, // 256 Bit
|
||||
SecureTokenGenerator::TYPE_BEARER => 32, // 256 Bit
|
||||
SecureTokenGenerator::TYPE_WEBHOOK => 32, // 256 Bit
|
||||
];
|
||||
|
||||
public const DEFAULT_TOKEN_FORMATS = [
|
||||
SecureTokenGenerator::TYPE_API_KEY => SecureTokenGenerator::FORMAT_BASE64_URL,
|
||||
SecureTokenGenerator::TYPE_SESSION => SecureTokenGenerator::FORMAT_BASE64_URL,
|
||||
SecureTokenGenerator::TYPE_OTP => SecureTokenGenerator::FORMAT_NUMERIC,
|
||||
];
|
||||
|
||||
public const TOKEN_EXPIRY_TIMES = [
|
||||
SecureTokenGenerator::TYPE_SESSION => 3600 * 24, // 24 Stunden
|
||||
SecureTokenGenerator::TYPE_CSRF => 3600, // 1 Stunde
|
||||
SecureTokenGenerator::TYPE_VERIFICATION => 3600 * 2, // 2 Stunden
|
||||
SecureTokenGenerator::TYPE_OTP => 300, // 5 Minuten
|
||||
SecureTokenGenerator::TYPE_REFRESH => 3600 * 24 * 30, // 30 Tage
|
||||
];
|
||||
}
|
||||
```
|
||||
|
||||
## Environment-basierte Konfiguration
|
||||
|
||||
### .env Konfiguration
|
||||
|
||||
```bash
|
||||
# Cryptography Configuration
|
||||
CRYPTO_DEFAULT_KDF_ALGORITHM=argon2id
|
||||
CRYPTO_DEFAULT_SECURITY_LEVEL=standard
|
||||
|
||||
# Token Configuration
|
||||
CRYPTO_DEFAULT_TOKEN_LENGTH=32
|
||||
CRYPTO_DEFAULT_TOKEN_FORMAT=base64_url
|
||||
|
||||
# Digital Signature Configuration
|
||||
CRYPTO_DEFAULT_RSA_KEY_SIZE=2048
|
||||
CRYPTO_DEFAULT_EC_CURVE=secp256r1
|
||||
|
||||
# Performance Configuration
|
||||
CRYPTO_BATCH_SIZE_LIMIT=1000
|
||||
CRYPTO_ENTROPY_THRESHOLD=7.0
|
||||
```
|
||||
|
||||
### Environment-aware Service
|
||||
|
||||
```php
|
||||
use App\Framework\Core\Environment;
|
||||
use App\Framework\Core\EnvKey;
|
||||
|
||||
final readonly class CryptographyConfigService
|
||||
{
|
||||
public function __construct(
|
||||
private Environment $environment
|
||||
) {}
|
||||
|
||||
public function getDefaultKdfAlgorithm(): string
|
||||
{
|
||||
return $this->environment->get(
|
||||
EnvKey::from('CRYPTO_DEFAULT_KDF_ALGORITHM'),
|
||||
'argon2id'
|
||||
);
|
||||
}
|
||||
|
||||
public function getDefaultSecurityLevel(): string
|
||||
{
|
||||
return $this->environment->get(
|
||||
EnvKey::from('CRYPTO_DEFAULT_SECURITY_LEVEL'),
|
||||
'standard'
|
||||
);
|
||||
}
|
||||
|
||||
public function getDefaultTokenLength(): int
|
||||
{
|
||||
return $this->environment->getInt(
|
||||
EnvKey::from('CRYPTO_DEFAULT_TOKEN_LENGTH'),
|
||||
32
|
||||
);
|
||||
}
|
||||
|
||||
public function getEntropyThreshold(): float
|
||||
{
|
||||
return $this->environment->getFloat(
|
||||
EnvKey::from('CRYPTO_ENTROPY_THRESHOLD'),
|
||||
7.0
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Advanced Configuration
|
||||
|
||||
### Custom RandomGenerator
|
||||
|
||||
```php
|
||||
// Eigener RandomGenerator für spezielle Anforderungen
|
||||
final readonly class CustomSecureRandomGenerator implements RandomGenerator
|
||||
{
|
||||
public function bytes(int $length): string
|
||||
{
|
||||
// Custom implementation mit Hardware-RNG
|
||||
return $this->getHardwareRandomBytes($length);
|
||||
}
|
||||
|
||||
public function int(int $min = 0, int $max = PHP_INT_MAX): int
|
||||
{
|
||||
return random_int($min, $max);
|
||||
}
|
||||
|
||||
private function getHardwareRandomBytes(int $length): string
|
||||
{
|
||||
// Implementation für Hardware-RNG
|
||||
// z.B. über /dev/hwrng oder externe HSM
|
||||
}
|
||||
}
|
||||
|
||||
// In Initializer registrieren
|
||||
$container->singleton(RandomGenerator::class, new CustomSecureRandomGenerator());
|
||||
```
|
||||
|
||||
### Performance-optimierte Konfiguration
|
||||
|
||||
```php
|
||||
final readonly class PerformanceOptimizedCryptoInitializer implements Initializer
|
||||
{
|
||||
public function initialize(Container $container): void
|
||||
{
|
||||
// Cached Services für bessere Performance
|
||||
$container->singleton(KeyDerivationFunction::class, function(Container $c) {
|
||||
$kdf = new KeyDerivationFunction($c->get(RandomGenerator::class));
|
||||
return new CachedKeyDerivationFunction($kdf, $c->get(Cache::class));
|
||||
});
|
||||
|
||||
// Batch-optimierte Token-Generierung
|
||||
$container->singleton(SecureTokenGenerator::class, function(Container $c) {
|
||||
return new BatchOptimizedTokenGenerator(
|
||||
$c->get(RandomGenerator::class),
|
||||
batchSize: 100 // Tokens in Batches vorgenerieren
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Test-Konfiguration
|
||||
|
||||
```php
|
||||
// Für Unit Tests - deterministic aber sichere Werte
|
||||
final readonly class TestCryptographyInitializer implements Initializer
|
||||
{
|
||||
public function initialize(Container $container): void
|
||||
{
|
||||
if (!$this->isTestEnvironment()) {
|
||||
throw new InvalidArgumentException('TestCryptographyInitializer nur in Test-Umgebung verwenden');
|
||||
}
|
||||
|
||||
// Deterministischer aber sicherer Generator für Tests
|
||||
$container->singleton(RandomGenerator::class, new TestRandomGenerator());
|
||||
|
||||
// Reduzierte Parameter für schnellere Tests
|
||||
$container->singleton(KeyDerivationFunction::class, function(Container $c) {
|
||||
return new KeyDerivationFunction($c->get(RandomGenerator::class));
|
||||
});
|
||||
}
|
||||
|
||||
private function isTestEnvironment(): bool
|
||||
{
|
||||
return defined('PHPUNIT_RUNNING') ||
|
||||
(isset($_ENV['APP_ENV']) && $_ENV['APP_ENV'] === 'testing');
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Middleware-Integration
|
||||
|
||||
### Automatic Token Validation
|
||||
|
||||
```php
|
||||
final readonly class CryptoTokenValidationMiddleware
|
||||
{
|
||||
public function __construct(
|
||||
private CryptographicUtilities $utils,
|
||||
private CacheInterface $cache
|
||||
) {}
|
||||
|
||||
public function handle(HttpRequest $request, RequestHandler $handler): HttpResponse
|
||||
{
|
||||
$authHeader = $request->server->getAuthorizationHeader();
|
||||
|
||||
if ($authHeader && str_starts_with($authHeader, 'Bearer ')) {
|
||||
$token = substr($authHeader, 7);
|
||||
|
||||
// Token-Format validieren
|
||||
if (!$this->utils->timingSafeEquals($token, trim($token))) {
|
||||
throw new AuthenticationException('Invalid token format');
|
||||
}
|
||||
|
||||
// Cache für Token-Validierung
|
||||
$cacheKey = 'token_valid_' . hash('sha256', $token);
|
||||
if (!$this->cache->has($cacheKey)) {
|
||||
// Token in Datenbank validieren
|
||||
$isValid = $this->validateTokenInDatabase($token);
|
||||
$this->cache->set($cacheKey, $isValid, 300); // 5 Min Cache
|
||||
}
|
||||
}
|
||||
|
||||
return $handler->handle($request);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Monitoring und Logging
|
||||
|
||||
### Security Event Integration
|
||||
|
||||
```php
|
||||
final readonly class CryptographyEventLogger
|
||||
{
|
||||
public function __construct(
|
||||
private Logger $logger,
|
||||
private SecurityEventLogger $securityLogger
|
||||
) {}
|
||||
|
||||
public function logTokenGeneration(SecureToken $token): void
|
||||
{
|
||||
$this->logger->info('Token generated', [
|
||||
'type' => $token->getType(),
|
||||
'format' => $token->getFormat(),
|
||||
'length' => $token->getLength(),
|
||||
'fingerprint' => $token->getShortFingerprint()
|
||||
]);
|
||||
}
|
||||
|
||||
public function logSuspiciousTokenActivity(string $token, string $reason): void
|
||||
{
|
||||
$this->securityLogger->logSecurityEvent(
|
||||
new SuspiciousTokenActivityEvent(
|
||||
tokenFingerprint: hash('sha256', $token),
|
||||
reason: $reason,
|
||||
timestamp: new DateTimeImmutable()
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Validierung der Konfiguration
|
||||
|
||||
```php
|
||||
final readonly class CryptographyConfigValidator
|
||||
{
|
||||
public static function validateConfiguration(Container $container): void
|
||||
{
|
||||
// RandomGenerator verfügbar?
|
||||
if (!$container->has(RandomGenerator::class)) {
|
||||
throw new ConfigurationException('RandomGenerator nicht registriert');
|
||||
}
|
||||
|
||||
// Cryptography Services verfügbar?
|
||||
$requiredServices = [
|
||||
KeyDerivationFunction::class,
|
||||
SecureTokenGenerator::class,
|
||||
CryptographicUtilities::class
|
||||
];
|
||||
|
||||
foreach ($requiredServices as $service) {
|
||||
if (!$container->has($service)) {
|
||||
throw new ConfigurationException("Service nicht registriert: {$service}");
|
||||
}
|
||||
}
|
||||
|
||||
// Sicherheitsparameter validieren
|
||||
$kdf = $container->get(KeyDerivationFunction::class);
|
||||
try {
|
||||
$params = $kdf->getRecommendedParameters('pbkdf2-sha256', 'standard');
|
||||
if ($params['iterations'] < 50000) {
|
||||
throw new ConfigurationException('PBKDF2 Iterationen zu niedrig (Minimum: 50000)');
|
||||
}
|
||||
} catch (InvalidArgumentException $e) {
|
||||
throw new ConfigurationException('Ungültige KDF-Konfiguration: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Diese Konfiguration ermöglicht es, das Cryptography Module flexibel an verschiedene Umgebungen und Sicherheitsanforderungen anzupassen, während Best Practices für Sicherheit und Performance eingehalten werden.
|
||||
703
docs/components/cryptography/examples.md
Normal file
703
docs/components/cryptography/examples.md
Normal file
@@ -0,0 +1,703 @@
|
||||
# Cryptography Module - Praktische Beispiele
|
||||
|
||||
Umfassende Sammlung praktischer Anwendungsfälle für das Cryptography Module.
|
||||
|
||||
## Authentifizierung und Session-Management
|
||||
|
||||
### User Password System
|
||||
|
||||
```php
|
||||
final readonly class UserAuthenticationService
|
||||
{
|
||||
public function __construct(
|
||||
private KeyDerivationFunction $kdf,
|
||||
private SecureTokenGenerator $tokenGenerator,
|
||||
private UserRepository $userRepository
|
||||
) {}
|
||||
|
||||
public function registerUser(string $email, string $password): User
|
||||
{
|
||||
// Password hashen mit Argon2ID
|
||||
$hashedPassword = $this->kdf->hashPassword($password, 'argon2id', [
|
||||
'memory_cost' => 65536, // 64 MB
|
||||
'time_cost' => 4, // 4 Iterationen
|
||||
'threads' => 3 // 3 Threads
|
||||
]);
|
||||
|
||||
$user = new User(
|
||||
id: Ulid::generate(),
|
||||
email: new Email($email),
|
||||
passwordHash: $hashedPassword,
|
||||
createdAt: new DateTimeImmutable()
|
||||
);
|
||||
|
||||
return $this->userRepository->save($user);
|
||||
}
|
||||
|
||||
public function authenticateUser(string $email, string $password): ?SessionToken
|
||||
{
|
||||
$user = $this->userRepository->findByEmail(new Email($email));
|
||||
|
||||
if (!$user || !$this->kdf->verify($password, $user->getPasswordHash())) {
|
||||
// Timing-Angriffe verhindern durch konstante Ausführungszeit
|
||||
$this->kdf->hashPassword('dummy-password', 'argon2id');
|
||||
return null;
|
||||
}
|
||||
|
||||
// Session Token generieren
|
||||
$sessionToken = $this->tokenGenerator->generateSessionToken(48); // 384 Bit
|
||||
|
||||
// In Session Store speichern
|
||||
$this->storeSessionToken($user, $sessionToken);
|
||||
|
||||
return $sessionToken;
|
||||
}
|
||||
|
||||
private function storeSessionToken(User $user, SecureToken $token): void
|
||||
{
|
||||
$session = new UserSession(
|
||||
tokenHash: hash('sha256', $token->getValue()),
|
||||
userId: $user->getId(),
|
||||
expiresAt: (new DateTimeImmutable())->add(new DateInterval('PT24H')),
|
||||
metadata: $token->getMetadata()
|
||||
);
|
||||
|
||||
// Session in Datenbank speichern (nur Hash, nie Klartext!)
|
||||
$this->sessionRepository->save($session);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### API Key Management System
|
||||
|
||||
```php
|
||||
final readonly class ApiKeyManagementService
|
||||
{
|
||||
public function __construct(
|
||||
private SecureTokenGenerator $tokenGenerator,
|
||||
private CryptographicUtilities $utils,
|
||||
private ApiKeyRepository $repository
|
||||
) {}
|
||||
|
||||
public function createApiKey(
|
||||
User $user,
|
||||
string $name,
|
||||
array $scopes = [],
|
||||
?DateTimeImmutable $expiresAt = null
|
||||
): ApiKeyResult {
|
||||
// API Key mit Prefix generieren
|
||||
$apiKey = $this->tokenGenerator->generateApiKey(
|
||||
prefix: 'ak',
|
||||
length: 32
|
||||
);
|
||||
|
||||
// API Key Entity erstellen (Hash speichern, nicht Klartext)
|
||||
$apiKeyEntity = new ApiKey(
|
||||
id: Ulid::generate(),
|
||||
userId: $user->getId(),
|
||||
name: $name,
|
||||
keyHash: hash('sha256', $apiKey->getValue()),
|
||||
fingerprint: $apiKey->getFingerprint(),
|
||||
scopes: $scopes,
|
||||
expiresAt: $expiresAt,
|
||||
createdAt: new DateTimeImmutable()
|
||||
);
|
||||
|
||||
$this->repository->save($apiKeyEntity);
|
||||
|
||||
return new ApiKeyResult(
|
||||
apiKey: $apiKey->getValue(), // Nur einmal zurückgeben
|
||||
keyId: $apiKeyEntity->getId(),
|
||||
fingerprint: $apiKey->getShortFingerprint(),
|
||||
expiresAt: $expiresAt
|
||||
);
|
||||
}
|
||||
|
||||
public function validateApiKey(string $apiKey): ?ApiKey
|
||||
{
|
||||
$keyHash = hash('sha256', $apiKey);
|
||||
$storedKey = $this->repository->findByHash($keyHash);
|
||||
|
||||
if (!$storedKey) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Timing-sicherer Vergleich
|
||||
if (!$this->utils->timingSafeEquals($keyHash, $storedKey->getKeyHash())) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Expiration prüfen
|
||||
if ($storedKey->getExpiresAt() && $storedKey->getExpiresAt() < new DateTimeImmutable()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $storedKey;
|
||||
}
|
||||
|
||||
public function rotateApiKey(string $currentApiKey): ApiKeyResult
|
||||
{
|
||||
$oldKey = $this->validateApiKey($currentApiKey);
|
||||
|
||||
if (!$oldKey) {
|
||||
throw new ApiKeyException('Invalid API key for rotation');
|
||||
}
|
||||
|
||||
// Neuen Key mit gleichen Eigenschaften erstellen
|
||||
$newApiKey = $this->createApiKey(
|
||||
user: $oldKey->getUser(),
|
||||
name: $oldKey->getName() . ' (rotated)',
|
||||
scopes: $oldKey->getScopes(),
|
||||
expiresAt: $oldKey->getExpiresAt()
|
||||
);
|
||||
|
||||
// Alten Key deaktivieren (nicht löschen für Audit-Trail)
|
||||
$oldKey->deactivate();
|
||||
$this->repository->save($oldKey);
|
||||
|
||||
return $newApiKey;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## CSRF Protection
|
||||
|
||||
### CSRF Token System
|
||||
|
||||
```php
|
||||
final readonly class CsrfProtectionService
|
||||
{
|
||||
public function __construct(
|
||||
private SecureTokenGenerator $tokenGenerator,
|
||||
private CacheInterface $cache
|
||||
) {}
|
||||
|
||||
public function generateCsrfToken(string $sessionId): SecureToken
|
||||
{
|
||||
$csrfToken = $this->tokenGenerator->generateCsrfToken();
|
||||
|
||||
// Token mit Session verknüpfen
|
||||
$cacheKey = "csrf_token_{$sessionId}";
|
||||
$this->cache->set($cacheKey, $csrfToken->getValue(), 3600); // 1 Stunde
|
||||
|
||||
return $csrfToken;
|
||||
}
|
||||
|
||||
public function validateCsrfToken(string $sessionId, string $providedToken): bool
|
||||
{
|
||||
$cacheKey = "csrf_token_{$sessionId}";
|
||||
$storedToken = $this->cache->get($cacheKey);
|
||||
|
||||
if (!$storedToken) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Timing-sicherer Vergleich
|
||||
$isValid = hash_equals($storedToken, $providedToken);
|
||||
|
||||
// Token nach Verwendung löschen (Single-Use)
|
||||
if ($isValid) {
|
||||
$this->cache->delete($cacheKey);
|
||||
}
|
||||
|
||||
return $isValid;
|
||||
}
|
||||
}
|
||||
|
||||
// CSRF Middleware
|
||||
final readonly class CsrfProtectionMiddleware
|
||||
{
|
||||
public function __construct(
|
||||
private CsrfProtectionService $csrfService
|
||||
) {}
|
||||
|
||||
public function handle(HttpRequest $request, RequestHandler $handler): HttpResponse
|
||||
{
|
||||
// Nur bei state-changing Operations prüfen
|
||||
if (in_array($request->method, [Method::POST, Method::PUT, Method::DELETE, Method::PATCH])) {
|
||||
$sessionId = $request->session->getId();
|
||||
$csrfToken = $request->parsedBody->get('_token') ?? $request->headers->get('X-CSRF-Token');
|
||||
|
||||
if (!$csrfToken || !$this->csrfService->validateCsrfToken($sessionId, $csrfToken)) {
|
||||
throw new CsrfTokenMismatchException('CSRF token mismatch');
|
||||
}
|
||||
}
|
||||
|
||||
return $handler->handle($request);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Webhook Security
|
||||
|
||||
### Webhook Signature System
|
||||
|
||||
```php
|
||||
final readonly class WebhookSecurityService
|
||||
{
|
||||
public function __construct(
|
||||
private SecureTokenGenerator $tokenGenerator,
|
||||
private CryptographicUtilities $utils
|
||||
) {}
|
||||
|
||||
public function generateWebhookSecret(): SecureToken
|
||||
{
|
||||
return $this->tokenGenerator->generateWebhookToken();
|
||||
}
|
||||
|
||||
public function signWebhookPayload(string $payload, SecureToken $secret): string
|
||||
{
|
||||
$signature = hash_hmac('sha256', $payload, $secret->getValue());
|
||||
return 'sha256=' . $signature;
|
||||
}
|
||||
|
||||
public function validateWebhookSignature(
|
||||
string $payload,
|
||||
string $signature,
|
||||
SecureToken $secret
|
||||
): bool {
|
||||
$expectedSignature = $this->signWebhookPayload($payload, $secret);
|
||||
|
||||
// Timing-sicherer Vergleich
|
||||
return $this->utils->timingSafeEquals($signature, $expectedSignature);
|
||||
}
|
||||
}
|
||||
|
||||
// Webhook Handler
|
||||
final readonly class PaymentWebhookHandler
|
||||
{
|
||||
public function __construct(
|
||||
private WebhookSecurityService $security,
|
||||
private WebhookConfigRepository $configRepository
|
||||
) {}
|
||||
|
||||
#[Route(path: '/webhooks/payment', method: Method::POST)]
|
||||
public function handlePaymentWebhook(HttpRequest $request): JsonResult
|
||||
{
|
||||
$signature = $request->headers->get('X-Hub-Signature-256');
|
||||
$payload = $request->rawBody();
|
||||
|
||||
if (!$signature) {
|
||||
throw new WebhookException('Missing signature header');
|
||||
}
|
||||
|
||||
// Webhook Secret für den Sender abrufen
|
||||
$senderId = $request->headers->get('X-Sender-ID');
|
||||
$config = $this->configRepository->findBySenderId($senderId);
|
||||
|
||||
if (!$config) {
|
||||
throw new WebhookException('Unknown sender');
|
||||
}
|
||||
|
||||
// Signatur validieren
|
||||
if (!$this->security->validateWebhookSignature($payload, $signature, $config->getSecret())) {
|
||||
throw new WebhookException('Invalid signature');
|
||||
}
|
||||
|
||||
// Payload verarbeiten
|
||||
$data = json_decode($payload, true);
|
||||
$this->processPaymentEvent($data);
|
||||
|
||||
return new JsonResult(['status' => 'processed']);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Two-Factor Authentication (2FA)
|
||||
|
||||
### OTP-basiertes 2FA System
|
||||
|
||||
```php
|
||||
final readonly class TwoFactorAuthService
|
||||
{
|
||||
public function __construct(
|
||||
private SecureTokenGenerator $tokenGenerator,
|
||||
private CacheInterface $cache,
|
||||
private NotificationService $notificationService
|
||||
) {}
|
||||
|
||||
public function generateOtpCode(User $user): string
|
||||
{
|
||||
$otpToken = $this->tokenGenerator->generateOtpToken(6); // 6-stelliger Code
|
||||
|
||||
// OTP mit User-ID und Expiration speichern
|
||||
$cacheKey = "otp_{$user->getId()}";
|
||||
$otpData = [
|
||||
'code' => $otpToken->getValue(),
|
||||
'attempts' => 0,
|
||||
'created_at' => time()
|
||||
];
|
||||
|
||||
$this->cache->set($cacheKey, $otpData, 300); // 5 Minuten gültig
|
||||
|
||||
return $otpToken->getValue();
|
||||
}
|
||||
|
||||
public function sendOtpCode(User $user): void
|
||||
{
|
||||
$otpCode = $this->generateOtpCode($user);
|
||||
|
||||
// SMS oder Email versenden
|
||||
$this->notificationService->sendOtpCode(
|
||||
recipient: $user->getPhoneNumber() ?? $user->getEmail(),
|
||||
code: $otpCode,
|
||||
expiresInMinutes: 5
|
||||
);
|
||||
}
|
||||
|
||||
public function verifyOtpCode(User $user, string $providedCode): bool
|
||||
{
|
||||
$cacheKey = "otp_{$user->getId()}";
|
||||
$otpData = $this->cache->get($cacheKey);
|
||||
|
||||
if (!$otpData) {
|
||||
return false; // Kein OTP vorhanden oder abgelaufen
|
||||
}
|
||||
|
||||
// Rate-Limiting: Max 3 Versuche
|
||||
if ($otpData['attempts'] >= 3) {
|
||||
$this->cache->delete($cacheKey);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Timing-sicherer Vergleich
|
||||
$isValid = hash_equals($otpData['code'], $providedCode);
|
||||
|
||||
if ($isValid) {
|
||||
// Erfolgreiche Verifikation - OTP löschen
|
||||
$this->cache->delete($cacheKey);
|
||||
} else {
|
||||
// Fehlversuch - Counter erhöhen
|
||||
$otpData['attempts']++;
|
||||
$this->cache->set($cacheKey, $otpData, 300);
|
||||
}
|
||||
|
||||
return $isValid;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Digitale Signaturen für Dokumente
|
||||
|
||||
### Document Signing Service
|
||||
|
||||
```php
|
||||
final readonly class DocumentSigningService
|
||||
{
|
||||
public function __construct(
|
||||
private DigitalSignature $signature,
|
||||
private DocumentRepository $documentRepository
|
||||
) {}
|
||||
|
||||
public function generateSigningKeyPair(): KeyPair
|
||||
{
|
||||
// RSA 4096 Bit für langfristige Dokumentensignaturen
|
||||
return $this->signature->generateRsaKeyPair(4096);
|
||||
}
|
||||
|
||||
public function signDocument(
|
||||
Document $document,
|
||||
PrivateKey $privateKey,
|
||||
User $signer
|
||||
): SignedDocument {
|
||||
$documentContent = $document->getContent();
|
||||
$metadata = [
|
||||
'document_id' => $document->getId(),
|
||||
'signer_id' => $signer->getId(),
|
||||
'signed_at' => (new DateTimeImmutable())->format('c'),
|
||||
'algorithm' => 'RSA-SHA256'
|
||||
];
|
||||
|
||||
// Dokument + Metadaten signieren
|
||||
$dataToSign = $documentContent . json_encode($metadata);
|
||||
$signatureResult = $this->signature->sign($dataToSign, $privateKey, 'sha256');
|
||||
|
||||
$signedDocument = new SignedDocument(
|
||||
originalDocument: $document,
|
||||
signature: $signatureResult,
|
||||
signerPublicKey: $privateKey->getPublicKey(),
|
||||
metadata: $metadata,
|
||||
signedAt: new DateTimeImmutable()
|
||||
);
|
||||
|
||||
return $this->documentRepository->saveSignedDocument($signedDocument);
|
||||
}
|
||||
|
||||
public function verifyDocumentSignature(SignedDocument $signedDocument): bool
|
||||
{
|
||||
$document = $signedDocument->getOriginalDocument();
|
||||
$metadata = $signedDocument->getMetadata();
|
||||
|
||||
$dataToVerify = $document->getContent() . json_encode($metadata);
|
||||
|
||||
return $this->signature->verify(
|
||||
$dataToVerify,
|
||||
$signedDocument->getSignature(),
|
||||
$signedDocument->getSignerPublicKey()
|
||||
);
|
||||
}
|
||||
|
||||
public function createSignatureChain(array $documents, array $signers): SignatureChain
|
||||
{
|
||||
$chain = new SignatureChain();
|
||||
|
||||
foreach ($documents as $index => $document) {
|
||||
$signer = $signers[$index];
|
||||
$signedDocument = $this->signDocument($document, $signer->getPrivateKey(), $signer);
|
||||
$chain->addSignedDocument($signedDocument);
|
||||
}
|
||||
|
||||
// Chain-Signatur erstellen (Hash der gesamten Kette)
|
||||
$chainHash = $chain->calculateChainHash();
|
||||
$chainSignature = $this->signature->sign($chainHash, $signers[0]->getPrivateKey());
|
||||
$chain->setChainSignature($chainSignature);
|
||||
|
||||
return $chain;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Passwort-Reset System
|
||||
|
||||
### Secure Password Reset
|
||||
|
||||
```php
|
||||
final readonly class PasswordResetService
|
||||
{
|
||||
public function __construct(
|
||||
private SecureTokenGenerator $tokenGenerator,
|
||||
private KeyDerivationFunction $kdf,
|
||||
private CacheInterface $cache,
|
||||
private EmailService $emailService
|
||||
) {}
|
||||
|
||||
public function initiatePasswordReset(User $user): void
|
||||
{
|
||||
// Sicheren Reset-Token generieren
|
||||
$resetToken = $this->tokenGenerator->generateVerificationToken('password_reset');
|
||||
|
||||
// Token hashen und speichern (nie Klartext speichern)
|
||||
$tokenHash = hash('sha256', $resetToken->getValue());
|
||||
$resetData = [
|
||||
'user_id' => $user->getId(),
|
||||
'token_hash' => $tokenHash,
|
||||
'created_at' => time(),
|
||||
'used' => false
|
||||
];
|
||||
|
||||
// 2 Stunden gültig
|
||||
$cacheKey = "password_reset_{$user->getId()}";
|
||||
$this->cache->set($cacheKey, $resetData, 7200);
|
||||
|
||||
// Reset-Link per Email senden
|
||||
$resetLink = "https://app.example.com/reset-password?token=" .
|
||||
urlencode($resetToken->getValue());
|
||||
|
||||
$this->emailService->send(
|
||||
to: $user->getEmail(),
|
||||
subject: 'Password Reset Request',
|
||||
template: 'password_reset',
|
||||
data: ['reset_link' => $resetLink, 'expires_in' => '2 hours']
|
||||
);
|
||||
}
|
||||
|
||||
public function validateResetToken(string $token): ?User
|
||||
{
|
||||
$tokenHash = hash('sha256', $token);
|
||||
|
||||
// Token in allen aktiven Reset-Anfragen suchen
|
||||
$cacheKeys = $this->cache->getKeysByPattern('password_reset_*');
|
||||
|
||||
foreach ($cacheKeys as $cacheKey) {
|
||||
$resetData = $this->cache->get($cacheKey);
|
||||
|
||||
if ($resetData &&
|
||||
!$resetData['used'] &&
|
||||
hash_equals($resetData['token_hash'], $tokenHash)) {
|
||||
|
||||
return $this->userRepository->find($resetData['user_id']);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function resetPassword(string $token, string $newPassword): bool
|
||||
{
|
||||
$user = $this->validateResetToken($token);
|
||||
|
||||
if (!$user) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Neues Password hashen
|
||||
$hashedPassword = $this->kdf->hashPassword($newPassword, 'argon2id');
|
||||
|
||||
// User Password aktualisieren
|
||||
$user->updatePassword($hashedPassword);
|
||||
$this->userRepository->save($user);
|
||||
|
||||
// Reset-Token als verwendet markieren
|
||||
$cacheKey = "password_reset_{$user->getId()}";
|
||||
$resetData = $this->cache->get($cacheKey);
|
||||
if ($resetData) {
|
||||
$resetData['used'] = true;
|
||||
$this->cache->set($cacheKey, $resetData, 7200);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Daten-Verschlüsselung mit Schlüssel-Ableitung
|
||||
|
||||
### Encrypted Data Storage
|
||||
|
||||
```php
|
||||
final readonly class EncryptedDataService
|
||||
{
|
||||
public function __construct(
|
||||
private KeyDerivationFunction $kdf,
|
||||
private CryptographicUtilities $utils,
|
||||
private EncryptionService $encryption
|
||||
) {}
|
||||
|
||||
public function encryptPersonalData(
|
||||
array $personalData,
|
||||
string $userPassword
|
||||
): EncryptedPersonalData {
|
||||
// Benutzer-spezifischen Schlüssel aus Password ableiten
|
||||
$salt = $this->utils->generateNonce(32);
|
||||
$derivedKey = $this->kdf->pbkdf2($userPassword, $salt, 100000, 32);
|
||||
|
||||
// Daten serialisieren und verschlüsseln
|
||||
$serializedData = json_encode($personalData);
|
||||
$encryptedData = $this->encryption->encrypt($serializedData, $derivedKey->getKey());
|
||||
|
||||
return new EncryptedPersonalData(
|
||||
encryptedData: $encryptedData,
|
||||
salt: $salt,
|
||||
keyDerivation: [
|
||||
'algorithm' => $derivedKey->getAlgorithm(),
|
||||
'iterations' => $derivedKey->getIterations()
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function decryptPersonalData(
|
||||
EncryptedPersonalData $encryptedData,
|
||||
string $userPassword
|
||||
): array {
|
||||
// Gleichen Schlüssel aus Password ableiten
|
||||
$derivedKey = $this->kdf->pbkdf2(
|
||||
$userPassword,
|
||||
$encryptedData->getSalt(),
|
||||
$encryptedData->getKeyDerivation()['iterations'],
|
||||
32
|
||||
);
|
||||
|
||||
// Daten entschlüsseln
|
||||
$decryptedData = $this->encryption->decrypt(
|
||||
$encryptedData->getEncryptedData(),
|
||||
$derivedKey->getKey()
|
||||
);
|
||||
|
||||
return json_decode($decryptedData, true);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Audit-Trail mit Kryptographischen Fingerprints
|
||||
|
||||
### Secure Audit Trail
|
||||
|
||||
```php
|
||||
final readonly class AuditTrailService
|
||||
{
|
||||
public function __construct(
|
||||
private CryptographicUtilities $utils,
|
||||
private DigitalSignature $signature,
|
||||
private AuditLogRepository $repository
|
||||
) {}
|
||||
|
||||
public function logUserAction(
|
||||
User $user,
|
||||
string $action,
|
||||
array $context = []
|
||||
): AuditLogEntry {
|
||||
$timestamp = new DateTimeImmutable();
|
||||
$logData = [
|
||||
'user_id' => $user->getId(),
|
||||
'action' => $action,
|
||||
'context' => $context,
|
||||
'timestamp' => $timestamp->format('c'),
|
||||
'ip_address' => $this->getClientIpHash(),
|
||||
'user_agent_hash' => $this->getUserAgentHash()
|
||||
];
|
||||
|
||||
// Kryptographischen Fingerprint erstellen
|
||||
$dataString = json_encode($logData, JSON_SORT_KEYS);
|
||||
$fingerprint = hash('sha256', $dataString);
|
||||
|
||||
// Chain-Hash (verhindert Manipulation der Historie)
|
||||
$previousEntry = $this->repository->getLatestEntry();
|
||||
$chainHash = $previousEntry
|
||||
? hash('sha256', $previousEntry->getChainHash() . $fingerprint)
|
||||
: $fingerprint;
|
||||
|
||||
$auditEntry = new AuditLogEntry(
|
||||
id: Ulid::generate(),
|
||||
fingerprint: $fingerprint,
|
||||
chainHash: $chainHash,
|
||||
logData: $logData,
|
||||
createdAt: $timestamp
|
||||
);
|
||||
|
||||
return $this->repository->save($auditEntry);
|
||||
}
|
||||
|
||||
public function verifyAuditTrailIntegrity(): bool
|
||||
{
|
||||
$entries = $this->repository->getAllEntriesOrdered();
|
||||
$previousChainHash = null;
|
||||
|
||||
foreach ($entries as $entry) {
|
||||
// Fingerprint verifizieren
|
||||
$dataString = json_encode($entry->getLogData(), JSON_SORT_KEYS);
|
||||
$expectedFingerprint = hash('sha256', $dataString);
|
||||
|
||||
if (!hash_equals($entry->getFingerprint(), $expectedFingerprint)) {
|
||||
return false; // Entry wurde manipuliert
|
||||
}
|
||||
|
||||
// Chain-Hash verifizieren
|
||||
$expectedChainHash = $previousChainHash
|
||||
? hash('sha256', $previousChainHash . $entry->getFingerprint())
|
||||
: $entry->getFingerprint();
|
||||
|
||||
if (!hash_equals($entry->getChainHash(), $expectedChainHash)) {
|
||||
return false; // Chain wurde unterbrochen
|
||||
}
|
||||
|
||||
$previousChainHash = $entry->getChainHash();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function getClientIpHash(): string
|
||||
{
|
||||
$clientIp = $_SERVER['REMOTE_ADDR'] ?? 'unknown';
|
||||
return hash('sha256', $clientIp); // IP nicht im Klartext speichern
|
||||
}
|
||||
|
||||
private function getUserAgentHash(): string
|
||||
{
|
||||
$userAgent = $_SERVER['HTTP_USER_AGENT'] ?? 'unknown';
|
||||
return hash('sha256', $userAgent);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Diese Beispiele zeigen die praktische Anwendung des Cryptography Modules in realen Szenarien und demonstrieren Best Practices für sichere Implementierungen.
|
||||
365
docs/components/cryptography/index.md
Normal file
365
docs/components/cryptography/index.md
Normal file
@@ -0,0 +1,365 @@
|
||||
# Cryptography Module
|
||||
|
||||
Das Cryptography Module erweitert die bestehenden Verschlüsselungsfunktionen des Frameworks um moderne kryptographische Primitive und bietet sichere Implementierungen für Schlüsselableitung, digitale Signaturen, erweiterte Hash-Funktionen und sichere Token-Generierung.
|
||||
|
||||
## Überblick
|
||||
|
||||
Das Modul folgt den Framework-Prinzipien:
|
||||
- **Immutable Value Objects** für sichere Datenrepräsentation
|
||||
- **Service Pattern** für Business Logic
|
||||
- **Dependency Injection** für RandomGenerator Integration
|
||||
- **Security-First Design** mit timing-sicheren Operationen
|
||||
|
||||
## Core Services
|
||||
|
||||
### KeyDerivationFunction Service
|
||||
|
||||
Sichere Schlüsselableitung für Password-Hashing und Key-Stretching.
|
||||
|
||||
```php
|
||||
use App\Framework\Cryptography\KeyDerivationFunction;
|
||||
use App\Framework\Random\SecureRandomGenerator;
|
||||
|
||||
$kdf = new KeyDerivationFunction(new SecureRandomGenerator());
|
||||
|
||||
// PBKDF2 (Standard)
|
||||
$derivedKey = $kdf->pbkdf2('password', $salt, 100000, 32);
|
||||
|
||||
// Argon2ID (Empfohlen)
|
||||
$derivedKey = $kdf->argon2id('password', $salt, 65536, 4, 3, 32);
|
||||
|
||||
// Password mit automatischer Salt-Generierung hashen
|
||||
$derivedKey = $kdf->hashPassword('password', 'argon2id');
|
||||
|
||||
// Password verifizieren
|
||||
$isValid = $kdf->verify('password', $derivedKey);
|
||||
```
|
||||
|
||||
**Unterstützte Algorithmen:**
|
||||
- **PBKDF2** mit SHA-256, SHA-512, SHA-384, SHA-224
|
||||
- **Argon2ID** (empfohlen für neue Implementierungen)
|
||||
- **scrypt** für spezielle Anwendungsfälle
|
||||
|
||||
### DigitalSignature Service
|
||||
|
||||
Digitale Signaturen für Datenintegrität und Authentifizierung.
|
||||
|
||||
```php
|
||||
use App\Framework\Cryptography\DigitalSignature;
|
||||
|
||||
$signature = new DigitalSignature(new SecureRandomGenerator());
|
||||
|
||||
// RSA Schlüsselpaar generieren
|
||||
$keyPair = $signature->generateRsaKeyPair(2048);
|
||||
|
||||
// Daten signieren
|
||||
$signatureResult = $signature->sign('data to sign', $keyPair->getPrivateKey());
|
||||
|
||||
// Signatur verifizieren
|
||||
$isValid = $signature->verify('data to sign', $signatureResult, $keyPair->getPublicKey());
|
||||
|
||||
// ECDSA mit secp256r1
|
||||
$ecKeyPair = $signature->generateEcdsaKeyPair('secp256r1');
|
||||
$ecSignature = $signature->signWithEcdsa('data', $ecKeyPair->getPrivateKey());
|
||||
```
|
||||
|
||||
### AdvancedHash Service
|
||||
|
||||
Erweiterte Hash-Funktionen über grundlegende Algorithmen hinaus.
|
||||
|
||||
```php
|
||||
use App\Framework\Cryptography\AdvancedHash;
|
||||
|
||||
$hash = new AdvancedHash();
|
||||
|
||||
// SHA-3 Familie
|
||||
$sha3 = $hash->sha3('data', 256);
|
||||
$shake = $hash->shake('data', 32, 128); // Extendable-output function
|
||||
|
||||
// BLAKE2
|
||||
$blake2b = $hash->blake2b('data', 32, 'optional-key');
|
||||
$blake2s = $hash->blake2s('data', 16);
|
||||
|
||||
// Hash-Ketten für komplexe Szenarien
|
||||
$chainResult = $hash->hashChain('data', ['sha3-256', 'blake2b']);
|
||||
```
|
||||
|
||||
### SecureTokenGenerator Service
|
||||
|
||||
Kryptographisch sichere Token-Generierung für APIs, Sessions und mehr.
|
||||
|
||||
```php
|
||||
use App\Framework\Cryptography\SecureTokenGenerator;
|
||||
|
||||
$generator = new SecureTokenGenerator(new SecureRandomGenerator());
|
||||
|
||||
// API Keys mit Prefix
|
||||
$apiKey = $generator->generateApiKey('myapp'); // myapp_...
|
||||
|
||||
// Session Tokens
|
||||
$sessionToken = $generator->generateSessionToken();
|
||||
|
||||
// CSRF Tokens
|
||||
$csrfToken = $generator->generateCsrfToken();
|
||||
|
||||
// OTP Tokens
|
||||
$otp = $generator->generateOtpToken(6); // 6-stelliger numerischer Code
|
||||
|
||||
// Custom Tokens mit eigenem Alphabet
|
||||
$customToken = $generator->generateCustom('0123456789ABCDEF', 16);
|
||||
|
||||
// Batch-Generierung
|
||||
$tokens = $generator->generateBatch('session', 10, 32);
|
||||
```
|
||||
|
||||
**Token-Formate:**
|
||||
- `FORMAT_BASE64_URL` (Standard, URL-sicher)
|
||||
- `FORMAT_BASE64` (Standard Base64)
|
||||
- `FORMAT_HEX` (Hexadezimal)
|
||||
- `FORMAT_BASE32` (Base32)
|
||||
- `FORMAT_ALPHANUMERIC` (Buchstaben + Zahlen)
|
||||
|
||||
### CryptographicUtilities Service
|
||||
|
||||
Sammlung kryptographischer Utility-Funktionen.
|
||||
|
||||
```php
|
||||
use App\Framework\Cryptography\CryptographicUtilities;
|
||||
|
||||
$utils = new CryptographicUtilities(new SecureRandomGenerator());
|
||||
|
||||
// Timing-sichere String-Vergleiche
|
||||
$isEqual = $utils->timingSafeEquals($string1, $string2);
|
||||
|
||||
// Entropie-Validierung
|
||||
$isHighEntropy = $utils->validateEntropy($data, 7.0);
|
||||
$entropy = $utils->calculateShannonEntropy($data);
|
||||
|
||||
// Sichere UUIDs
|
||||
$uuid4 = $utils->generateUuid4(); // Zufällige UUID
|
||||
$uuid5 = $utils->generateUuid5($namespace, 'name'); // Deterministische UUID
|
||||
|
||||
// Key-Stretching
|
||||
$stretched = $utils->stretchKey('password', 'salt', 10000, 32);
|
||||
|
||||
// Speicher sicher löschen
|
||||
$utils->secureWipe($sensitiveData);
|
||||
```
|
||||
|
||||
## Value Objects
|
||||
|
||||
### DerivedKey
|
||||
|
||||
Unveränderliche Repräsentation abgeleiteter Schlüssel.
|
||||
|
||||
```php
|
||||
// Eigenschaften abrufen
|
||||
$algorithm = $derivedKey->getAlgorithm(); // 'pbkdf2-sha256'
|
||||
$keyLength = $derivedKey->getKeyLength(); // 32
|
||||
$iterations = $derivedKey->getIterations(); // 100000
|
||||
|
||||
// Verschiedene Formate
|
||||
$hex = $derivedKey->toHex();
|
||||
$base64 = $derivedKey->toBase64();
|
||||
|
||||
// Serialisierung
|
||||
$array = $derivedKey->toArray();
|
||||
$restored = DerivedKey::fromArray($array);
|
||||
|
||||
// Gleichheit prüfen (timing-sicher)
|
||||
$isEqual = $derivedKey->equals($otherKey);
|
||||
```
|
||||
|
||||
### SecureToken
|
||||
|
||||
Token mit Metadaten und sicherheitsfokussierten Funktionen.
|
||||
|
||||
```php
|
||||
// Token-Eigenschaften
|
||||
$type = $token->getType(); // 'api_key'
|
||||
$format = $token->getFormat(); // 'base64_url'
|
||||
$hasPrefix = $token->hasPrefix(); // true/false
|
||||
|
||||
// Sichere Operationen
|
||||
$isEqual = $token->equals($otherToken); // timing-sicher
|
||||
$isValid = $token->verify($candidateToken); // timing-sicher
|
||||
|
||||
// Sicherheit und Logging
|
||||
$masked = $token->getMaskedValue(); // myap****_Ab...fG
|
||||
$fingerprint = $token->getFingerprint(); // SHA-256 Hash
|
||||
$safeSummary = $token->getSafeSummary(); // Ohne Token-Wert
|
||||
```
|
||||
|
||||
### KeyPair, PrivateKey, PublicKey
|
||||
|
||||
Sichere Verwaltung asymmetrischer Schlüssel.
|
||||
|
||||
```php
|
||||
$keyPair = $signature->generateRsaKeyPair(2048);
|
||||
|
||||
$privateKey = $keyPair->getPrivateKey();
|
||||
$publicKey = $keyPair->getPublicKey();
|
||||
|
||||
// Schlüssel-Export
|
||||
$privatePem = $privateKey->toPem();
|
||||
$publicPem = $publicKey->toPem();
|
||||
|
||||
// Schlüssel-Import
|
||||
$privateKey = PrivateKey::fromPem($privatePem);
|
||||
$publicKey = PublicKey::fromPem($publicPem);
|
||||
```
|
||||
|
||||
## Sicherheitsfeatures
|
||||
|
||||
### Timing-Attack Schutz
|
||||
|
||||
Alle kritischen Vergleichsoperationen verwenden timing-sichere Implementierungen:
|
||||
|
||||
```php
|
||||
// Timing-sichere String-Vergleiche
|
||||
$utils->timingSafeEquals($secret1, $secret2);
|
||||
|
||||
// Timing-sichere Token-Verifikation
|
||||
$token->verify($candidateToken);
|
||||
|
||||
// Timing-sichere Array-Suche
|
||||
$utils->constantTimeArraySearch($haystack, $needle);
|
||||
```
|
||||
|
||||
### Entropie-Validierung
|
||||
|
||||
Automatische Validierung der Schlüsselstärke:
|
||||
|
||||
```php
|
||||
// Shannon-Entropie berechnen
|
||||
$entropy = $utils->calculateShannonEntropy($data);
|
||||
|
||||
// Mindest-Entropie validieren
|
||||
$isStrong = $utils->validateEntropy($data, 7.0);
|
||||
|
||||
// Schlüsselstärke prüfen
|
||||
$isValidKey = $utils->validateKeyStrength($key, 128); // Minimum 128 Bits
|
||||
```
|
||||
|
||||
### Sichere Speicher-Verwaltung
|
||||
|
||||
```php
|
||||
// Sensitive Daten sicher löschen
|
||||
$utils->secureWipe($password); // Überschreibt Speicher
|
||||
|
||||
// Automatische Metadaten-Bereinigung in Token-Logs
|
||||
$safeSummary = $token->getSafeSummary(); // Ohne sensitive Werte
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Empfohlene Parameter
|
||||
|
||||
```php
|
||||
// PBKDF2 (Minimum)
|
||||
$kdf->pbkdf2($password, $salt, 100000, 32, 'sha256');
|
||||
|
||||
// Argon2ID (Empfohlen)
|
||||
$kdf->argon2id($password, $salt, 65536, 4, 3, 32);
|
||||
|
||||
// RSA Schlüssel (Minimum 2048 Bit)
|
||||
$signature->generateRsaKeyPair(2048);
|
||||
|
||||
// Token-Längen
|
||||
$generator->generateApiKey('prefix', 32); // 256 Bit
|
||||
$generator->generateSessionToken(48); // 384 Bit für langlebige Sessions
|
||||
```
|
||||
|
||||
### Sichere Implementierung
|
||||
|
||||
```php
|
||||
// ✅ Gute Praxis
|
||||
final readonly class AuthService
|
||||
{
|
||||
public function __construct(
|
||||
private readonly KeyDerivationFunction $kdf,
|
||||
private readonly SecureTokenGenerator $tokenGenerator
|
||||
) {}
|
||||
|
||||
public function hashPassword(string $password): DerivedKey
|
||||
{
|
||||
return $this->kdf->hashPassword($password, 'argon2id');
|
||||
}
|
||||
|
||||
public function generateApiKey(string $prefix): SecureToken
|
||||
{
|
||||
return $this->tokenGenerator->generateApiKey($prefix, 32);
|
||||
}
|
||||
}
|
||||
|
||||
// ❌ Schlechte Praxis - Nicht verwenden
|
||||
// $hashedPassword = md5($password); // Unsicher
|
||||
// $token = bin2hex(random_bytes(16)); // Zu kurz, keine Metadaten
|
||||
```
|
||||
|
||||
## Integration mit Framework
|
||||
|
||||
### Dependency Injection
|
||||
|
||||
```php
|
||||
final readonly class CryptographyServiceInitializer implements Initializer
|
||||
{
|
||||
public function initialize(Container $container): void
|
||||
{
|
||||
$randomGenerator = $container->get(RandomGenerator::class);
|
||||
|
||||
$container->singleton(KeyDerivationFunction::class,
|
||||
new KeyDerivationFunction($randomGenerator));
|
||||
|
||||
$container->singleton(SecureTokenGenerator::class,
|
||||
new SecureTokenGenerator($randomGenerator));
|
||||
|
||||
$container->singleton(CryptographicUtilities::class,
|
||||
new CryptographicUtilities($randomGenerator));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Controller Integration
|
||||
|
||||
```php
|
||||
final readonly class ApiKeyController
|
||||
{
|
||||
#[Route(path: '/admin/api-keys', method: Method::POST)]
|
||||
#[Auth(strategy: 'ip', allowedIps: ['127.0.0.1'])]
|
||||
public function createApiKey(
|
||||
CreateApiKeyRequest $request,
|
||||
SecureTokenGenerator $tokenGenerator
|
||||
): JsonResult {
|
||||
$apiKey = $tokenGenerator->generateApiKey(
|
||||
prefix: $request->prefix,
|
||||
length: 32
|
||||
);
|
||||
|
||||
// Token sicher in Datenbank speichern
|
||||
// Nur Hash oder verschlüsselte Version speichern, nie Klartext
|
||||
|
||||
return new JsonResult([
|
||||
'api_key' => $apiKey->getValue(),
|
||||
'fingerprint' => $apiKey->getShortFingerprint(),
|
||||
'created_at' => $apiKey->getCreatedAt()->format('c')
|
||||
]);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Performance
|
||||
|
||||
Das Cryptography Module ist für Production-Einsatz optimiert:
|
||||
|
||||
- **Cached Reflection Provider** für Dependency Injection
|
||||
- **Batch-Operationen** für Token-Generierung
|
||||
- **Effiziente Algorithmen** mit modernen kryptographischen Standards
|
||||
- **Minimale Speicher-Allokation** durch readonly Value Objects
|
||||
|
||||
## Weiterführende Dokumentation
|
||||
|
||||
- [Konfiguration](configuration.md) - Setup und Anpassung
|
||||
- [Beispiele](examples.md) - Praktische Anwendungsfälle
|
||||
- [Migration](migration.md) - Upgrade von älteren Krypto-Implementierungen
|
||||
- [Sicherheit](security.md) - Detaillierte Sicherheitsrichtlinien
|
||||
728
docs/components/cryptography/security.md
Normal file
728
docs/components/cryptography/security.md
Normal file
@@ -0,0 +1,728 @@
|
||||
# Cryptography Module - Sicherheitsrichtlinien
|
||||
|
||||
Detaillierte Sicherheitsrichtlinien und Best Practices für das Cryptography Module.
|
||||
|
||||
## Grundlegende Sicherheitsprinzipien
|
||||
|
||||
### Defense in Depth
|
||||
|
||||
Das Cryptography Module implementiert mehrschichtigen Schutz:
|
||||
|
||||
1. **Algorithmus-Ebene**: Moderne, peer-reviewed Algorithmen (Argon2ID, SHA-3, ECDSA)
|
||||
2. **Implementation-Ebene**: Timing-sichere Operationen und sichere Speicherverwaltung
|
||||
3. **Application-Ebene**: Sichere Integration und Konfiguration
|
||||
4. **System-Ebene**: Sicherer Random Number Generator und Entropie-Validierung
|
||||
|
||||
### Zero-Trust Architektur
|
||||
|
||||
```php
|
||||
// ✅ Vertraue niemals Eingaben - validiere alles
|
||||
final readonly class SecureTokenValidator
|
||||
{
|
||||
public function validateApiKey(string $providedKey): ?ApiKeyContext
|
||||
{
|
||||
// Eingabe-Validierung
|
||||
if (empty($providedKey) || strlen($providedKey) < 32) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Format-Validierung
|
||||
if (!$this->tokenGenerator->isValidFormat($providedKey, 'base64_url')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Timing-sicherer Vergleich mit gespeichertem Hash
|
||||
$keyHash = hash('sha256', $providedKey);
|
||||
$storedKey = $this->repository->findByHash($keyHash);
|
||||
|
||||
if (!$storedKey || !hash_equals($keyHash, $storedKey->getHash())) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Zusätzliche Sicherheitsprüfungen
|
||||
return $this->performSecurityChecks($storedKey);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Kryptographische Best Practices
|
||||
|
||||
### Sichere Schlüssel-Ableitung
|
||||
|
||||
```php
|
||||
// ✅ Empfohlene Parameter für verschiedene Szenarien
|
||||
final readonly class SecureKeyDerivation
|
||||
{
|
||||
private const SECURITY_LEVELS = [
|
||||
// Hohe Sicherheit für sensitive Daten
|
||||
'high_security' => [
|
||||
'algorithm' => 'argon2id',
|
||||
'memory_cost' => 131072, // 128 MB
|
||||
'time_cost' => 6, // 6 Iterationen
|
||||
'threads' => 4, // 4 Threads
|
||||
'key_length' => 32 // 256 Bit
|
||||
],
|
||||
|
||||
// Standard für normale Anwendungen
|
||||
'standard' => [
|
||||
'algorithm' => 'argon2id',
|
||||
'memory_cost' => 65536, // 64 MB
|
||||
'time_cost' => 4, // 4 Iterationen
|
||||
'threads' => 3, // 3 Threads
|
||||
'key_length' => 32 // 256 Bit
|
||||
],
|
||||
|
||||
// Kompatibilität mit älteren Systemen
|
||||
'legacy_compatible' => [
|
||||
'algorithm' => 'pbkdf2-sha256',
|
||||
'iterations' => 200000, // 200k Iterationen
|
||||
'key_length' => 32 // 256 Bit
|
||||
]
|
||||
];
|
||||
|
||||
public function hashPassword(string $password, string $level = 'standard'): DerivedKey
|
||||
{
|
||||
$params = self::SECURITY_LEVELS[$level] ?? self::SECURITY_LEVELS['standard'];
|
||||
|
||||
return match ($params['algorithm']) {
|
||||
'argon2id' => $this->kdf->argon2id(
|
||||
$password,
|
||||
$this->kdf->generateSalt(32),
|
||||
$params['memory_cost'],
|
||||
$params['time_cost'],
|
||||
$params['threads'],
|
||||
$params['key_length']
|
||||
),
|
||||
'pbkdf2-sha256' => $this->kdf->pbkdf2(
|
||||
$password,
|
||||
$this->kdf->generateSalt(32),
|
||||
$params['iterations'],
|
||||
$params['key_length']
|
||||
)
|
||||
};
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Sichere Token-Generierung
|
||||
|
||||
```php
|
||||
// ✅ Token-Sicherheit nach Verwendungszweck
|
||||
final readonly class SecurityAwareTokenGenerator
|
||||
{
|
||||
private const TOKEN_SECURITY_REQUIREMENTS = [
|
||||
'api_key' => [
|
||||
'length' => 32, // 256 Bit
|
||||
'format' => 'base64_url',
|
||||
'entropy_min' => 7.0,
|
||||
'expires' => false, // Langlebig
|
||||
'single_use' => false
|
||||
],
|
||||
|
||||
'session' => [
|
||||
'length' => 48, // 384 Bit (höhere Sicherheit)
|
||||
'format' => 'base64_url',
|
||||
'entropy_min' => 7.5,
|
||||
'expires' => true,
|
||||
'single_use' => false
|
||||
],
|
||||
|
||||
'csrf' => [
|
||||
'length' => 32, // 256 Bit
|
||||
'format' => 'base64_url',
|
||||
'entropy_min' => 7.0,
|
||||
'expires' => true, // Kurze Lebensdauer
|
||||
'single_use' => true // Einmalverwendung
|
||||
],
|
||||
|
||||
'password_reset' => [
|
||||
'length' => 64, // 512 Bit (maximale Sicherheit)
|
||||
'format' => 'base64_url',
|
||||
'entropy_min' => 7.8,
|
||||
'expires' => true,
|
||||
'single_use' => true,
|
||||
'max_lifetime' => 3600 * 2 // 2 Stunden
|
||||
]
|
||||
];
|
||||
|
||||
public function generateSecureToken(string $type): SecureToken
|
||||
{
|
||||
$requirements = self::TOKEN_SECURITY_REQUIREMENTS[$type] ??
|
||||
throw new InvalidArgumentException("Unknown token type: {$type}");
|
||||
|
||||
do {
|
||||
$token = $this->tokenGenerator->generate(
|
||||
$type,
|
||||
$requirements['length'],
|
||||
$requirements['format']
|
||||
);
|
||||
|
||||
// Entropie validieren
|
||||
$entropy = $this->utils->calculateShannonEntropy($token->getRawBytes());
|
||||
} while ($entropy < $requirements['entropy_min']);
|
||||
|
||||
return $token;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Timing-Attack Prevention
|
||||
|
||||
### Konstante Ausführungszeit
|
||||
|
||||
```php
|
||||
// ✅ Timing-sichere Implementierung
|
||||
final readonly class TimingSafeOperations
|
||||
{
|
||||
public function authenticateUser(string $email, string $password): ?User
|
||||
{
|
||||
$user = $this->userRepository->findByEmail(new Email($email));
|
||||
|
||||
// WICHTIG: Immer Hashing durchführen, auch bei ungültigem User
|
||||
$providedHash = $user
|
||||
? $user->getPasswordHash()
|
||||
: $this->createDummyHash(); // Konstante Ausführungszeit
|
||||
|
||||
$isValid = $this->kdf->verify($password, $providedHash);
|
||||
|
||||
// Zusätzliche konstante Verzögerung
|
||||
$this->enforceConstantTime();
|
||||
|
||||
return $isValid && $user ? $user : null;
|
||||
}
|
||||
|
||||
private function createDummyHash(): DerivedKey
|
||||
{
|
||||
// Dummy-Hash mit gleichen Parametern wie echte Hashes
|
||||
static $dummyHash = null;
|
||||
|
||||
if ($dummyHash === null) {
|
||||
$dummyHash = $this->kdf->hashPassword('dummy-password', 'argon2id');
|
||||
}
|
||||
|
||||
return $dummyHash;
|
||||
}
|
||||
|
||||
private function enforceConstantTime(): void
|
||||
{
|
||||
// Minimale zusätzliche Verzögerung (1-5ms)
|
||||
usleep(random_int(1000, 5000));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Side-Channel Resistance
|
||||
|
||||
```php
|
||||
// ✅ Schutz vor Side-Channel-Angriffen
|
||||
final readonly class SideChannelResistantComparison
|
||||
{
|
||||
public function compareSecrets(string $secret1, string $secret2): bool
|
||||
{
|
||||
// PHP's hash_equals ist timing-sicher implementiert
|
||||
return hash_equals($secret1, $secret2);
|
||||
}
|
||||
|
||||
public function compareArrays(array $array1, array $array2): bool
|
||||
{
|
||||
// Arrays zu Strings serialisieren für timing-sicheren Vergleich
|
||||
$string1 = json_encode($array1, JSON_SORT_KEYS);
|
||||
$string2 = json_encode($array2, JSON_SORT_KEYS);
|
||||
|
||||
return hash_equals($string1, $string2);
|
||||
}
|
||||
|
||||
public function searchInArray(array $haystack, mixed $needle): bool
|
||||
{
|
||||
$found = false;
|
||||
|
||||
// Alle Elemente durchgehen (konstante Zeit)
|
||||
foreach ($haystack as $value) {
|
||||
$isMatch = is_string($value) && is_string($needle)
|
||||
? hash_equals($value, $needle)
|
||||
: ($value === $needle);
|
||||
|
||||
$found = $found || $isMatch; // Kein frühes Beenden
|
||||
}
|
||||
|
||||
return $found;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Sichere Speicherverwaltung
|
||||
|
||||
### Sensitive Data Handling
|
||||
|
||||
```php
|
||||
// ✅ Sicherer Umgang mit sensitiven Daten
|
||||
final readonly class SecureMemoryManagement
|
||||
{
|
||||
public function processSecretData(string $secretData): ProcessedResult
|
||||
{
|
||||
try {
|
||||
// Daten verarbeiten
|
||||
$result = $this->doSecretProcessing($secretData);
|
||||
|
||||
// Sensitive Daten aus Speicher löschen
|
||||
$this->utils->secureWipe($secretData);
|
||||
|
||||
return $result;
|
||||
|
||||
} catch (Throwable $e) {
|
||||
// Auch bei Fehlern Speicher löschen
|
||||
$this->utils->secureWipe($secretData);
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
public function createSecureTemporaryFile(string $data): SecureTemporaryFile
|
||||
{
|
||||
// Temporary file mit restriktiven Berechtigungen erstellen
|
||||
$tempFile = tempnam(sys_get_temp_dir(), 'secure_');
|
||||
chmod($tempFile, 0600); // Nur Owner kann lesen/schreiben
|
||||
|
||||
// Daten schreiben und sofort aus Speicher löschen
|
||||
file_put_contents($tempFile, $data);
|
||||
$this->utils->secureWipe($data);
|
||||
|
||||
// Auto-cleanup mit Destruktor
|
||||
return new SecureTemporaryFile($tempFile);
|
||||
}
|
||||
}
|
||||
|
||||
// Auto-cleanup für temporäre Dateien
|
||||
final class SecureTemporaryFile
|
||||
{
|
||||
public function __construct(private string $filePath) {}
|
||||
|
||||
public function __destruct()
|
||||
{
|
||||
if (file_exists($this->filePath)) {
|
||||
// Datei mehrfach überschreiben vor dem Löschen
|
||||
$fileSize = filesize($this->filePath);
|
||||
|
||||
for ($i = 0; $i < 3; $i++) {
|
||||
file_put_contents($this->filePath, random_bytes($fileSize));
|
||||
}
|
||||
|
||||
unlink($this->filePath);
|
||||
}
|
||||
}
|
||||
|
||||
public function getPath(): string
|
||||
{
|
||||
return $this->filePath;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Input Validation und Sanitization
|
||||
|
||||
### Comprehensive Input Validation
|
||||
|
||||
```php
|
||||
// ✅ Umfassende Eingabe-Validierung
|
||||
final readonly class CryptographicInputValidator
|
||||
{
|
||||
public function validatePasswordStrength(string $password): ValidationResult
|
||||
{
|
||||
$errors = [];
|
||||
|
||||
// Länge prüfen
|
||||
if (strlen($password) < 8) {
|
||||
$errors[] = 'Password must be at least 8 characters long';
|
||||
}
|
||||
|
||||
// Komplexität prüfen
|
||||
if (!preg_match('/[A-Z]/', $password)) {
|
||||
$errors[] = 'Password must contain uppercase letters';
|
||||
}
|
||||
|
||||
if (!preg_match('/[a-z]/', $password)) {
|
||||
$errors[] = 'Password must contain lowercase letters';
|
||||
}
|
||||
|
||||
if (!preg_match('/[0-9]/', $password)) {
|
||||
$errors[] = 'Password must contain numbers';
|
||||
}
|
||||
|
||||
if (!preg_match('/[^A-Za-z0-9]/', $password)) {
|
||||
$errors[] = 'Password must contain special characters';
|
||||
}
|
||||
|
||||
// Entropie prüfen
|
||||
$entropy = $this->utils->calculateShannonEntropy($password);
|
||||
if ($entropy < 3.5) {
|
||||
$errors[] = 'Password has insufficient entropy';
|
||||
}
|
||||
|
||||
// Gegen Common-Passwords prüfen
|
||||
if ($this->isCommonPassword($password)) {
|
||||
$errors[] = 'Password is too common';
|
||||
}
|
||||
|
||||
return new ValidationResult(empty($errors), $errors);
|
||||
}
|
||||
|
||||
public function validateApiKeyFormat(string $apiKey): bool
|
||||
{
|
||||
// Länge prüfen (32-64 Zeichen)
|
||||
if (strlen($apiKey) < 32 || strlen($apiKey) > 64) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Format prüfen (Base64URL)
|
||||
if (!preg_match('/^[A-Za-z0-9_-]+$/', $apiKey)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Entropie validieren
|
||||
$entropy = $this->utils->calculateShannonEntropy($apiKey);
|
||||
return $entropy >= 6.0;
|
||||
}
|
||||
|
||||
public function sanitizeUserInput(string $input): string
|
||||
{
|
||||
// Whitespace normalisieren
|
||||
$sanitized = trim($input);
|
||||
|
||||
// Null-Bytes entfernen (verhindern Directory Traversal)
|
||||
$sanitized = str_replace("\0", '', $sanitized);
|
||||
|
||||
// Control Characters entfernen
|
||||
$sanitized = preg_replace('/[\x00-\x1F\x7F]/', '', $sanitized);
|
||||
|
||||
return $sanitized;
|
||||
}
|
||||
|
||||
private function isCommonPassword(string $password): bool
|
||||
{
|
||||
$commonPasswords = [
|
||||
'password', '123456', 'password123', 'admin', 'qwerty',
|
||||
'letmein', 'welcome', 'monkey', '1234567890', 'password1'
|
||||
];
|
||||
|
||||
$lowercase = strtolower($password);
|
||||
|
||||
foreach ($commonPasswords as $common) {
|
||||
if ($lowercase === $common ||
|
||||
levenshtein($lowercase, $common) <= 2) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Rate Limiting und Abuse Prevention
|
||||
|
||||
### Cryptographic Rate Limiting
|
||||
|
||||
```php
|
||||
// ✅ Rate-Limiting für kryptographische Operationen
|
||||
final readonly class CryptographicRateLimiter
|
||||
{
|
||||
private const LIMITS = [
|
||||
'password_attempts' => ['count' => 5, 'window' => 900], // 5 in 15 Min
|
||||
'token_generation' => ['count' => 100, 'window' => 3600], // 100 in 1 Stunde
|
||||
'password_reset' => ['count' => 3, 'window' => 3600], // 3 in 1 Stunde
|
||||
'otp_generation' => ['count' => 5, 'window' => 300], // 5 in 5 Min
|
||||
];
|
||||
|
||||
public function __construct(
|
||||
private CacheInterface $cache,
|
||||
private SecurityEventLogger $securityLogger
|
||||
) {}
|
||||
|
||||
public function checkLimit(string $operation, string $identifier): bool
|
||||
{
|
||||
$limit = self::LIMITS[$operation] ?? null;
|
||||
|
||||
if (!$limit) {
|
||||
return true; // Kein Limit definiert
|
||||
}
|
||||
|
||||
$cacheKey = "ratelimit_{$operation}_{$identifier}";
|
||||
$attempts = $this->cache->get($cacheKey, []);
|
||||
$now = time();
|
||||
|
||||
// Abgelaufene Versuche entfernen
|
||||
$attempts = array_filter($attempts, fn($timestamp) =>
|
||||
$now - $timestamp < $limit['window']);
|
||||
|
||||
if (count($attempts) >= $limit['count']) {
|
||||
// Rate-Limit erreicht
|
||||
$this->securityLogger->logRateLimitExceeded($operation, $identifier);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Neuen Versuch hinzufügen
|
||||
$attempts[] = $now;
|
||||
$this->cache->set($cacheKey, $attempts, $limit['window']);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function recordFailedAttempt(string $operation, string $identifier): void
|
||||
{
|
||||
$cacheKey = "failed_{$operation}_{$identifier}";
|
||||
$failures = $this->cache->get($cacheKey, 0);
|
||||
$failures++;
|
||||
|
||||
$this->cache->set($cacheKey, $failures, 3600); // 1 Stunde
|
||||
|
||||
// Bei vielen Fehlversuchen Sicherheitsteam benachrichtigen
|
||||
if ($failures >= 10) {
|
||||
$this->securityLogger->logSuspiciousActivity(
|
||||
$operation,
|
||||
$identifier,
|
||||
"Multiple failed attempts: {$failures}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Error Handling und Information Leakage Prevention
|
||||
|
||||
### Secure Error Handling
|
||||
|
||||
```php
|
||||
// ✅ Sichere Fehlerbehandlung ohne Information Leakage
|
||||
final readonly class SecureErrorHandler
|
||||
{
|
||||
public function handleCryptographicError(Throwable $error): ErrorResponse
|
||||
{
|
||||
// Detaillierte Logs für interne Analyse
|
||||
$this->logger->error('Cryptographic error occurred', [
|
||||
'error_type' => get_class($error),
|
||||
'message' => $error->getMessage(),
|
||||
'trace' => $error->getTraceAsString(),
|
||||
'context' => $this->getSecureContext()
|
||||
]);
|
||||
|
||||
// Generische Antwort für Client (keine Details)
|
||||
$publicMessage = match (true) {
|
||||
$error instanceof InvalidPasswordException => 'Authentication failed',
|
||||
$error instanceof TokenExpiredException => 'Token has expired',
|
||||
$error instanceof TokenInvalidException => 'Invalid token',
|
||||
$error instanceof CryptographicException => 'Security operation failed',
|
||||
default => 'Internal error occurred'
|
||||
};
|
||||
|
||||
return new ErrorResponse(
|
||||
message: $publicMessage,
|
||||
code: $this->getPublicErrorCode($error),
|
||||
timestamp: new DateTimeImmutable()
|
||||
);
|
||||
}
|
||||
|
||||
private function getSecureContext(): array
|
||||
{
|
||||
return [
|
||||
'request_id' => $this->generateRequestId(),
|
||||
'user_agent_hash' => hash('sha256', $_SERVER['HTTP_USER_AGENT'] ?? ''),
|
||||
'ip_hash' => hash('sha256', $_SERVER['REMOTE_ADDR'] ?? ''),
|
||||
'timestamp' => time()
|
||||
];
|
||||
}
|
||||
|
||||
private function generateRequestId(): string
|
||||
{
|
||||
return bin2hex(random_bytes(16));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Monitoring und Alerting
|
||||
|
||||
### Security Monitoring
|
||||
|
||||
```php
|
||||
// ✅ Sicherheits-Monitoring für kryptographische Operationen
|
||||
final readonly class CryptographicSecurityMonitor
|
||||
{
|
||||
public function __construct(
|
||||
private MetricsCollector $metrics,
|
||||
private AlertManager $alerts,
|
||||
private SecurityEventLogger $securityLogger
|
||||
) {}
|
||||
|
||||
public function monitorOperation(string $operation, callable $callback): mixed
|
||||
{
|
||||
$startTime = microtime(true);
|
||||
$operationId = bin2hex(random_bytes(8));
|
||||
|
||||
try {
|
||||
$result = $callback();
|
||||
|
||||
// Erfolgreiche Operation protokollieren
|
||||
$duration = microtime(true) - $startTime;
|
||||
$this->metrics->increment("crypto.{$operation}.success");
|
||||
$this->metrics->timing("crypto.{$operation}.duration", $duration);
|
||||
|
||||
// Ungewöhnlich lange Operationen melden
|
||||
if ($duration > $this->getExpectedDuration($operation) * 2) {
|
||||
$this->alerts->sendAlert(
|
||||
level: 'warning',
|
||||
message: "Slow cryptographic operation: {$operation}",
|
||||
context: ['duration' => $duration, 'operation_id' => $operationId]
|
||||
);
|
||||
}
|
||||
|
||||
return $result;
|
||||
|
||||
} catch (Throwable $e) {
|
||||
// Fehlgeschlagene Operation protokollieren
|
||||
$this->metrics->increment("crypto.{$operation}.failure");
|
||||
$this->securityLogger->logCryptographicFailure($operation, $e, $operationId);
|
||||
|
||||
// Bei kritischen Fehlern sofort alarmieren
|
||||
if ($this->isCriticalError($e)) {
|
||||
$this->alerts->sendAlert(
|
||||
level: 'critical',
|
||||
message: "Critical cryptographic failure: {$operation}",
|
||||
context: [
|
||||
'error' => $e->getMessage(),
|
||||
'operation_id' => $operationId
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
public function detectAnomalousPatterns(): void
|
||||
{
|
||||
// Erhöhte Fehlerrate erkennen
|
||||
$errorRate = $this->metrics->getRate('crypto.*.failure', '5m');
|
||||
if ($errorRate > 0.1) { // >10% Fehlerrate
|
||||
$this->alerts->sendAlert(
|
||||
level: 'warning',
|
||||
message: 'High cryptographic error rate detected',
|
||||
context: ['error_rate' => $errorRate]
|
||||
);
|
||||
}
|
||||
|
||||
// Ungewöhnliche Aktivitätsmuster
|
||||
$operations = $this->metrics->getCount('crypto.*.success', '1h');
|
||||
$baseline = $this->getHistoricalBaseline();
|
||||
|
||||
if ($operations > $baseline * 5) {
|
||||
$this->alerts->sendAlert(
|
||||
level: 'warning',
|
||||
message: 'Unusual spike in cryptographic operations',
|
||||
context: ['operations' => $operations, 'baseline' => $baseline]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private function getExpectedDuration(string $operation): float
|
||||
{
|
||||
return match ($operation) {
|
||||
'token_generation' => 0.001, // 1ms
|
||||
'password_hash' => 0.5, // 500ms (Argon2ID)
|
||||
'password_verify' => 0.5, // 500ms
|
||||
'signature_create' => 0.01, // 10ms
|
||||
'signature_verify' => 0.005, // 5ms
|
||||
default => 0.1 // 100ms
|
||||
};
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Compliance und Audit
|
||||
|
||||
### Compliance Framework
|
||||
|
||||
```php
|
||||
// ✅ Compliance-Unterstützung für Regulierungen
|
||||
final readonly class CryptographicComplianceValidator
|
||||
{
|
||||
private const COMPLIANCE_REQUIREMENTS = [
|
||||
'GDPR' => [
|
||||
'encryption_required' => true,
|
||||
'key_length_min' => 256,
|
||||
'algorithm_approved' => ['AES-256-GCM', 'ChaCha20-Poly1305'],
|
||||
'key_rotation_days' => 365,
|
||||
'audit_trail_required' => true
|
||||
],
|
||||
|
||||
'PCI_DSS' => [
|
||||
'encryption_required' => true,
|
||||
'key_length_min' => 256,
|
||||
'algorithm_approved' => ['AES-256', 'RSA-2048'],
|
||||
'key_rotation_days' => 365,
|
||||
'secure_key_storage' => true
|
||||
],
|
||||
|
||||
'FIPS_140_2' => [
|
||||
'approved_algorithms_only' => true,
|
||||
'algorithm_approved' => ['AES', 'SHA-256', 'RSA', 'ECDSA'],
|
||||
'random_number_generator' => 'FIPS_approved',
|
||||
'key_length_min' => 256
|
||||
]
|
||||
];
|
||||
|
||||
public function validateCompliance(string $standard): ComplianceReport
|
||||
{
|
||||
$requirements = self::COMPLIANCE_REQUIREMENTS[$standard] ??
|
||||
throw new InvalidArgumentException("Unknown standard: {$standard}");
|
||||
|
||||
$violations = [];
|
||||
$report = new ComplianceReport($standard);
|
||||
|
||||
// Algorithmus-Compliance prüfen
|
||||
if (isset($requirements['algorithm_approved'])) {
|
||||
$usedAlgorithms = $this->getUsedAlgorithms();
|
||||
$approvedAlgorithms = $requirements['algorithm_approved'];
|
||||
|
||||
foreach ($usedAlgorithms as $algorithm) {
|
||||
if (!in_array($algorithm, $approvedAlgorithms)) {
|
||||
$violations[] = "Non-approved algorithm in use: {$algorithm}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Schlüssel-Längen prüfen
|
||||
if (isset($requirements['key_length_min'])) {
|
||||
$keyLengths = $this->getKeyLengths();
|
||||
$minLength = $requirements['key_length_min'];
|
||||
|
||||
foreach ($keyLengths as $context => $length) {
|
||||
if ($length < $minLength) {
|
||||
$violations[] = "Key length too short in {$context}: {$length} < {$minLength}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Schlüssel-Rotation prüfen
|
||||
if (isset($requirements['key_rotation_days'])) {
|
||||
$rotationViolations = $this->checkKeyRotation($requirements['key_rotation_days']);
|
||||
$violations = array_merge($violations, $rotationViolations);
|
||||
}
|
||||
|
||||
$report->setViolations($violations);
|
||||
$report->setCompliant(empty($violations));
|
||||
|
||||
return $report;
|
||||
}
|
||||
|
||||
public function generateAuditReport(): AuditReport
|
||||
{
|
||||
return new AuditReport([
|
||||
'cryptographic_operations' => $this->getCryptoOperationStats(),
|
||||
'key_management' => $this->getKeyManagementStats(),
|
||||
'algorithm_usage' => $this->getAlgorithmUsageStats(),
|
||||
'security_events' => $this->getSecurityEventSummary(),
|
||||
'compliance_status' => $this->getComplianceStatus()
|
||||
]);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Diese Sicherheitsrichtlinien stellen sicher, dass das Cryptography Module höchste Sicherheitsstandards erfüllt und gegen moderne Angriffsvektoren geschützt ist.
|
||||
417
docs/components/security/csrf-protection.md
Normal file
417
docs/components/security/csrf-protection.md
Normal file
@@ -0,0 +1,417 @@
|
||||
# CSRF-Schutz
|
||||
|
||||
> **Dokumentationshinweis:** Diese Dokumentation ist vollständig aktualisiert und stellt die aktuelle Implementierung des CSRF-Schutzes korrekt dar.
|
||||
|
||||
## Übersicht
|
||||
|
||||
Cross-Site Request Forgery (CSRF) ist eine Art von Angriff, bei dem ein Angreifer einen Benutzer dazu bringt, unbeabsichtigt Aktionen auf einer Website auszuführen, bei der er bereits authentifiziert ist. Das Framework bietet einen robusten CSRF-Schutzmechanismus, der solche Angriffe verhindert, indem es einzigartige Token generiert und validiert.
|
||||
|
||||
## Hauptkomponenten
|
||||
|
||||
### CsrfToken
|
||||
|
||||
Die `CsrfToken`-Klasse ist ein Value Object, das einen CSRF-Token repräsentiert:
|
||||
|
||||
```php
|
||||
use App\Framework\Security\CsrfToken;
|
||||
|
||||
// Token aus einem String erstellen
|
||||
$token = CsrfToken::fromString($tokenString);
|
||||
|
||||
// Token-Wert als String abrufen
|
||||
$tokenString = $token->toString();
|
||||
// oder
|
||||
$tokenString = (string)$token;
|
||||
```
|
||||
|
||||
Die Klasse stellt sicher, dass CSRF-Token immer gültige 64-Zeichen lange hexadezimale Strings sind und bietet Typsicherheit für CSRF-Token-Operationen.
|
||||
|
||||
### CsrfTokenGenerator
|
||||
|
||||
Die `CsrfTokenGenerator`-Klasse ist verantwortlich für die Generierung neuer CSRF-Token:
|
||||
|
||||
```php
|
||||
use App\Framework\Security\CsrfTokenGenerator;
|
||||
|
||||
// Generator initialisieren
|
||||
$generator = new CsrfTokenGenerator($randomGenerator);
|
||||
|
||||
// Neues Token generieren
|
||||
$token = $generator->generate();
|
||||
```
|
||||
|
||||
Die Klasse verwendet einen kryptografisch sicheren Zufallszahlengenerator, um Token zu erzeugen, die nicht vorhersehbar sind.
|
||||
|
||||
### CsrfTokenData
|
||||
|
||||
Die `CsrfTokenData`-Klasse erweitert das einfache Token um zusätzliche Metadaten:
|
||||
|
||||
```php
|
||||
use App\Framework\Security\CsrfTokenData;
|
||||
|
||||
// Token-Daten mit Metadaten erstellen
|
||||
$tokenData = new CsrfTokenData(
|
||||
$token,
|
||||
$clientIp,
|
||||
$targetPath,
|
||||
$expiresAt
|
||||
);
|
||||
|
||||
// Prüfen, ob das Token abgelaufen ist
|
||||
if ($tokenData->isExpired($clock)) {
|
||||
// Token ist abgelaufen
|
||||
}
|
||||
|
||||
// Prüfen, ob das Token bereits verwendet wurde
|
||||
if ($tokenData->isUsed()) {
|
||||
// Token wurde bereits verwendet
|
||||
}
|
||||
```
|
||||
|
||||
Diese Klasse ermöglicht erweiterte Sicherheitsfunktionen wie Token-Ablauf, Verwendungsverfolgung und Bindung an bestimmte Pfade oder IP-Adressen.
|
||||
|
||||
## Verwendung
|
||||
|
||||
### Token generieren und in Formularen verwenden
|
||||
|
||||
```php
|
||||
// In einem Controller
|
||||
public function showForm(): Response
|
||||
{
|
||||
// Token generieren
|
||||
$csrfToken = $this->csrfTokenGenerator->generate();
|
||||
|
||||
// Token in der Session speichern
|
||||
$this->session->set('csrf_token', $csrfToken->toString());
|
||||
|
||||
// Token im Formular einbinden
|
||||
return $this->render('form.twig', [
|
||||
'csrf_token' => $csrfToken->toString(),
|
||||
]);
|
||||
}
|
||||
```
|
||||
|
||||
In der Formularvorlage:
|
||||
|
||||
```html
|
||||
<form method="post" action="/submit">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
|
||||
<!-- Weitere Formularfelder -->
|
||||
<button type="submit">Absenden</button>
|
||||
</form>
|
||||
```
|
||||
|
||||
### Token validieren
|
||||
|
||||
```php
|
||||
// In einem Controller
|
||||
public function handleForm(Request $request): Response
|
||||
{
|
||||
// Gespeichertes Token abrufen
|
||||
$storedToken = CsrfToken::fromString($this->session->get('csrf_token'));
|
||||
|
||||
// Übermitteltes Token abrufen
|
||||
$submittedToken = $request->getPostParam('csrf_token');
|
||||
|
||||
// Token vergleichen (zeitsichere Vergleichsmethode)
|
||||
if (!$storedToken->equalsString($submittedToken)) {
|
||||
throw new SecurityException('Ungültiges CSRF-Token');
|
||||
}
|
||||
|
||||
// Token ist gültig, Formular verarbeiten
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### Erweiterte Token-Validierung mit Metadaten
|
||||
|
||||
```php
|
||||
// In einem Controller
|
||||
public function showForm(): Response
|
||||
{
|
||||
// Token mit Metadaten generieren
|
||||
$token = $this->csrfTokenGenerator->generate();
|
||||
$tokenData = new CsrfTokenData(
|
||||
$token,
|
||||
$this->request->getClientIp(),
|
||||
$this->request->path,
|
||||
new \DateTimeImmutable('+1 hour')
|
||||
);
|
||||
|
||||
// Token-Daten in der Session speichern
|
||||
$this->session->set('csrf_token_data', $tokenData->toArray());
|
||||
|
||||
// Token im Formular einbinden
|
||||
return $this->render('form.twig', [
|
||||
'csrf_token' => $token->toString(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function handleForm(Request $request): Response
|
||||
{
|
||||
// Token-Daten aus der Session abrufen
|
||||
$tokenDataArray = $this->session->get('csrf_token_data');
|
||||
$tokenData = CsrfTokenData::fromArray($tokenDataArray);
|
||||
|
||||
// Übermitteltes Token abrufen
|
||||
$submittedToken = $request->getPostParam('csrf_token');
|
||||
|
||||
// Umfassende Validierung durchführen
|
||||
if ($tokenData->isExpired($this->clock)) {
|
||||
throw new SecurityException('CSRF-Token ist abgelaufen');
|
||||
}
|
||||
|
||||
if ($tokenData->isUsed()) {
|
||||
throw new SecurityException('CSRF-Token wurde bereits verwendet');
|
||||
}
|
||||
|
||||
if ($tokenData->clientIp !== $request->getClientIp()) {
|
||||
throw new SecurityException('CSRF-Token ist an eine andere IP-Adresse gebunden');
|
||||
}
|
||||
|
||||
if (!$tokenData->token->equalsString($submittedToken)) {
|
||||
throw new SecurityException('Ungültiges CSRF-Token');
|
||||
}
|
||||
|
||||
// Token als verwendet markieren
|
||||
$tokenData->markAsUsed();
|
||||
$this->session->set('csrf_token_data', $tokenData->toArray());
|
||||
|
||||
// Token ist gültig, Formular verarbeiten
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
## Integration mit dem Framework
|
||||
|
||||
### CSRF-Middleware
|
||||
|
||||
Das Framework bietet eine `CsrfProtectionMiddleware`, die automatisch CSRF-Schutz für alle POST, PUT, DELETE und PATCH-Anfragen implementiert:
|
||||
|
||||
```php
|
||||
// In der Bootstrap-Datei oder Router-Konfiguration
|
||||
$app->addMiddleware(CsrfProtectionMiddleware::class);
|
||||
```
|
||||
|
||||
Die Middleware:
|
||||
1. Generiert automatisch CSRF-Token für alle Formulare
|
||||
2. Validiert Token für alle Anfragen, die den Zustand ändern
|
||||
3. Wirft eine Exception oder gibt eine Fehlerantwort zurück, wenn die Validierung fehlschlägt
|
||||
|
||||
### Konfiguration
|
||||
|
||||
Die CSRF-Schutzfunktionen können in der Konfigurationsdatei angepasst werden:
|
||||
|
||||
```php
|
||||
// config/security.php
|
||||
return [
|
||||
'csrf' => [
|
||||
'enabled' => true,
|
||||
'token_lifetime' => 3600, // Sekunden
|
||||
'exclude_routes' => [
|
||||
'/api/webhook',
|
||||
'/api/external/callback',
|
||||
],
|
||||
'exclude_patterns' => [
|
||||
'#^/api/public/#', // Regulärer Ausdruck für auszuschließende Routen
|
||||
],
|
||||
'header_name' => 'X-CSRF-Token', // Name des HTTP-Headers für API-Anfragen
|
||||
'parameter_name' => 'csrf_token', // Name des Formularfelds
|
||||
'strict_mode' => true, // Strikte Validierung (IP-Bindung, Einmalverwendung)
|
||||
],
|
||||
];
|
||||
```
|
||||
|
||||
### Template-Integration
|
||||
|
||||
Das Framework bietet eine einfache Möglichkeit, CSRF-Token in Templates einzubinden:
|
||||
|
||||
```php
|
||||
// In einem Controller
|
||||
public function showForm(): Response
|
||||
{
|
||||
return $this->render('form.twig', [
|
||||
'csrf_token' => $this->csrfProtection->getToken()->toString(),
|
||||
]);
|
||||
}
|
||||
```
|
||||
|
||||
Mit der Template-Funktion:
|
||||
|
||||
```twig
|
||||
{# In einer Twig-Vorlage #}
|
||||
<form method="post" action="/submit">
|
||||
{{ csrf_field() }}
|
||||
<!-- Weitere Formularfelder -->
|
||||
<button type="submit">Absenden</button>
|
||||
</form>
|
||||
```
|
||||
|
||||
Die `csrf_field()`-Funktion generiert automatisch ein verstecktes Formularfeld mit dem aktuellen CSRF-Token.
|
||||
|
||||
### API-Integration
|
||||
|
||||
Für API-Anfragen kann das CSRF-Token über einen HTTP-Header übermittelt werden:
|
||||
|
||||
```javascript
|
||||
// JavaScript-Beispiel
|
||||
fetch('/api/data', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]').content
|
||||
},
|
||||
body: JSON.stringify(data)
|
||||
});
|
||||
```
|
||||
|
||||
In der HTML-Vorlage:
|
||||
|
||||
```html
|
||||
<meta name="csrf-token" content="{{ csrf_token }}">
|
||||
```
|
||||
|
||||
## Erweiterte Funktionen
|
||||
|
||||
### Double Submit Cookie Pattern
|
||||
|
||||
Das Framework unterstützt das Double Submit Cookie Pattern für zusätzliche Sicherheit:
|
||||
|
||||
```php
|
||||
// In einem Controller
|
||||
public function showForm(): Response
|
||||
{
|
||||
$token = $this->csrfTokenGenerator->generate();
|
||||
|
||||
// Token in der Session speichern
|
||||
$this->session->set('csrf_token', $token->toString());
|
||||
|
||||
// Token auch als Cookie setzen
|
||||
$this->response->setCookie('csrf_token', $token->toString(), [
|
||||
'httponly' => false, // JavaScript muss darauf zugreifen können
|
||||
'samesite' => 'Strict',
|
||||
'secure' => true,
|
||||
'path' => '/',
|
||||
]);
|
||||
|
||||
return $this->render('form.twig', [
|
||||
'csrf_token' => $token->toString(),
|
||||
]);
|
||||
}
|
||||
```
|
||||
|
||||
### Token-Rotation
|
||||
|
||||
Für zusätzliche Sicherheit können CSRF-Token nach jeder Anfrage rotiert werden:
|
||||
|
||||
```php
|
||||
// In einem Controller
|
||||
public function handleForm(Request $request): Response
|
||||
{
|
||||
// Token validieren
|
||||
// ...
|
||||
|
||||
// Neues Token generieren
|
||||
$newToken = $this->csrfTokenGenerator->generate();
|
||||
$this->session->set('csrf_token', $newToken->toString());
|
||||
|
||||
// Antwort mit neuem Token
|
||||
return $this->render('success.twig', [
|
||||
'new_csrf_token' => $newToken->toString(),
|
||||
]);
|
||||
}
|
||||
```
|
||||
|
||||
### Synchronizer Token Pattern
|
||||
|
||||
Das Framework implementiert das Synchronizer Token Pattern, bei dem für jede Sitzung ein eindeutiges Token generiert wird:
|
||||
|
||||
```php
|
||||
// In der Session-Initialisierung
|
||||
public function initializeSession(): void
|
||||
{
|
||||
if (!$this->session->has('csrf_token')) {
|
||||
$token = $this->csrfTokenGenerator->generate();
|
||||
$this->session->set('csrf_token', $token->toString());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Fehlerbehebung
|
||||
|
||||
### Häufige Probleme
|
||||
|
||||
#### Token-Validierung schlägt fehl
|
||||
|
||||
Mögliche Ursachen:
|
||||
- Das Token ist abgelaufen
|
||||
- Das Token wurde bereits verwendet
|
||||
- Das Token wurde nicht korrekt übermittelt
|
||||
- Die Session ist abgelaufen oder wurde zurückgesetzt
|
||||
|
||||
Lösung:
|
||||
- Überprüfen Sie die Session-Konfiguration
|
||||
- Stellen Sie sicher, dass das Token korrekt im Formular eingebunden ist
|
||||
- Erhöhen Sie die Token-Lebensdauer in der Konfiguration
|
||||
|
||||
#### CSRF-Schutz für bestimmte Routen deaktivieren
|
||||
|
||||
Wenn Sie den CSRF-Schutz für bestimmte Routen deaktivieren müssen (z.B. für Webhook-Callbacks):
|
||||
|
||||
```php
|
||||
// config/security.php
|
||||
return [
|
||||
'csrf' => [
|
||||
'exclude_routes' => [
|
||||
'/api/webhook',
|
||||
'/api/external/callback',
|
||||
],
|
||||
],
|
||||
];
|
||||
```
|
||||
|
||||
#### AJAX-Anfragen mit CSRF-Schutz
|
||||
|
||||
Für AJAX-Anfragen müssen Sie das CSRF-Token im Header oder als Formularfeld übermitteln:
|
||||
|
||||
```javascript
|
||||
// JavaScript mit jQuery
|
||||
$.ajaxSetup({
|
||||
headers: {
|
||||
'X-CSRF-Token': $('meta[name="csrf-token"]').attr('content')
|
||||
}
|
||||
});
|
||||
|
||||
// Oder mit Fetch API
|
||||
fetch('/api/data', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]').content
|
||||
},
|
||||
body: JSON.stringify(data)
|
||||
});
|
||||
```
|
||||
|
||||
## Sicherheitsüberlegungen
|
||||
|
||||
### Best Practices
|
||||
|
||||
1. **Verwenden Sie HTTPS**: CSRF-Token sollten immer über eine verschlüsselte Verbindung übertragen werden.
|
||||
2. **Kurze Token-Lebensdauer**: Setzen Sie die Token-Lebensdauer auf einen angemessenen Wert (z.B. 1-2 Stunden).
|
||||
3. **Strikte SameSite-Cookie-Einstellungen**: Verwenden Sie `SameSite=Strict` oder `SameSite=Lax` für Session-Cookies.
|
||||
4. **Token-Rotation**: Rotieren Sie Token nach jeder Anfrage, die den Zustand ändert.
|
||||
5. **Validieren Sie alle Anfragen**: Stellen Sie sicher, dass alle Anfragen, die den Zustand ändern, ein gültiges CSRF-Token erfordern.
|
||||
|
||||
### Bekannte Einschränkungen
|
||||
|
||||
- CSRF-Schutz funktioniert nicht für Benutzer, die keine Cookies akzeptieren.
|
||||
- Bei sehr langen Sitzungen kann die Sicherheit des CSRF-Tokens beeinträchtigt sein.
|
||||
- CSRF-Schutz kann mit bestimmten Caching-Strategien in Konflikt geraten.
|
||||
|
||||
## Weiterführende Informationen
|
||||
|
||||
- [Security Features Übersicht](index.md)
|
||||
- [Security Headers und CSP](security-headers.md)
|
||||
- [Request Signing API](request-signing.md)
|
||||
- [Sicherheits-Best-Practices](/guides/security-best-practices.md)
|
||||
363
docs/components/security/index.md
Normal file
363
docs/components/security/index.md
Normal file
@@ -0,0 +1,363 @@
|
||||
# Security Features
|
||||
|
||||
> **Dokumentationshinweis:** Diese Dokumentation ist vollständig aktualisiert und stellt die aktuelle Implementierung der Sicherheitsfeatures korrekt dar.
|
||||
|
||||
## Übersicht
|
||||
|
||||
Das Framework bietet umfassende Sicherheitsfunktionen, die Ihre Anwendung vor gängigen Bedrohungen schützen. Diese Funktionen sind tief in die Architektur integriert und folgen modernen Best Practices für Webanwendungssicherheit.
|
||||
|
||||
## Hauptkomponenten
|
||||
|
||||
### CSRF-Schutz
|
||||
|
||||
Der Cross-Site Request Forgery (CSRF) Schutz verhindert, dass Angreifer unerwünschte Aktionen im Namen authentifizierter Benutzer ausführen:
|
||||
|
||||
```php
|
||||
// Token generieren
|
||||
$csrfToken = $csrfTokenGenerator->generate();
|
||||
|
||||
// Token in Formular einbinden
|
||||
$form = '<input type="hidden" name="csrf_token" value="' . $csrfToken->toString() . '">';
|
||||
|
||||
// Token validieren
|
||||
if (!$csrfToken->equalsString($request->getPostParam('csrf_token'))) {
|
||||
throw new SecurityException('Ungültiges CSRF-Token');
|
||||
}
|
||||
```
|
||||
|
||||
### Security Headers
|
||||
|
||||
Die `SecurityHeaderMiddleware` fügt automatisch wichtige Sicherheits-Header zu allen HTTP-Antworten hinzu:
|
||||
|
||||
- **Strict-Transport-Security (HSTS)**: Erzwingt HTTPS-Verbindungen
|
||||
- **X-Frame-Options**: Verhindert Clickjacking-Angriffe
|
||||
- **X-Content-Type-Options**: Verhindert MIME-Sniffing
|
||||
- **Content-Security-Policy (CSP)**: Schützt vor XSS und anderen Injection-Angriffen
|
||||
- **Referrer-Policy**: Kontrolliert die Weitergabe von Referrer-Informationen
|
||||
- **Permissions-Policy**: Beschränkt Browser-Features
|
||||
- **Cross-Origin-Policies**: Steuert ressourcenübergreifende Interaktionen
|
||||
|
||||
```php
|
||||
// In der Bootstrap-Datei oder Router-Konfiguration
|
||||
$app->addMiddleware(SecurityHeaderMiddleware::class);
|
||||
```
|
||||
|
||||
### Request Signing
|
||||
|
||||
Das Request-Signing-System ermöglicht die kryptografische Verifizierung von API-Anfragen:
|
||||
|
||||
```php
|
||||
// Ausgehende Anfrage signieren
|
||||
$signedRequest = $requestSigningService->signOutgoingRequest($request);
|
||||
|
||||
// Eingehende Anfrage verifizieren
|
||||
$result = $requestSigningService->verifyIncomingRequest($request);
|
||||
if (!$result->isSuccess()) {
|
||||
throw new SecurityException('Ungültige Anfrage-Signatur: ' . $result->errorMessage);
|
||||
}
|
||||
```
|
||||
|
||||
### Session-Sicherheit
|
||||
|
||||
Der `SecurityManager` schützt Benutzersitzungen vor verschiedenen Angriffen:
|
||||
|
||||
- **Session-Fixierung**: Automatische Regenerierung von Session-IDs
|
||||
- **Session-Hijacking**: Überprüfung von IP-Adressen und User-Agents
|
||||
- **Session-Timeout**: Automatisches Beenden inaktiver Sitzungen
|
||||
- **Concurrent-Login-Erkennung**: Erkennung gleichzeitiger Anmeldungen
|
||||
|
||||
```php
|
||||
// In einem Controller oder Middleware
|
||||
$securityManager->validateSession($request, $session);
|
||||
```
|
||||
|
||||
### Sicherheits-Ereignisbehandlung
|
||||
|
||||
Das Framework bietet ein umfassendes System zur Erkennung und Protokollierung von Sicherheitsereignissen:
|
||||
|
||||
```php
|
||||
// Sicherheitsereignis protokollieren
|
||||
$securityEventLogger->logEvent(
|
||||
SecurityEventType::AUTHENTICATION_FAILURE,
|
||||
'Fehlgeschlagene Anmeldung',
|
||||
['username' => $username, 'ip' => $request->getClientIp()]
|
||||
);
|
||||
|
||||
// Auf Sicherheitsereignisse reagieren
|
||||
$securityAlertManager->handleEvent($event);
|
||||
```
|
||||
|
||||
## Konfiguration
|
||||
|
||||
### CSRF-Schutz konfigurieren
|
||||
|
||||
```php
|
||||
// config/security.php
|
||||
return [
|
||||
'csrf' => [
|
||||
'enabled' => true,
|
||||
'token_lifetime' => 3600, // Sekunden
|
||||
'exclude_routes' => [
|
||||
'/api/webhook',
|
||||
],
|
||||
],
|
||||
];
|
||||
```
|
||||
|
||||
### Security Headers konfigurieren
|
||||
|
||||
```php
|
||||
// config/security.php
|
||||
return [
|
||||
'headers' => [
|
||||
'strict_mode' => true, // Produktionsmodus mit strengeren Einstellungen
|
||||
'content_security_policy' => "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline';",
|
||||
'hsts' => [
|
||||
'enabled' => true,
|
||||
'max_age' => 31536000, // 1 Jahr
|
||||
'include_subdomains' => true,
|
||||
'preload' => true,
|
||||
],
|
||||
],
|
||||
];
|
||||
```
|
||||
|
||||
### Request Signing konfigurieren
|
||||
|
||||
```php
|
||||
// config/security.php
|
||||
return [
|
||||
'request_signing' => [
|
||||
'enabled' => true,
|
||||
'default_algorithm' => 'hmac-sha256',
|
||||
'default_headers' => ['(request-target)', 'host', 'date', 'content-type'],
|
||||
'default_expiry' => 300, // Sekunden
|
||||
'required_routes' => [
|
||||
'/api/admin/*',
|
||||
'/api/payments/*',
|
||||
],
|
||||
],
|
||||
];
|
||||
```
|
||||
|
||||
## Verwendung
|
||||
|
||||
### CSRF-Schutz in Formularen
|
||||
|
||||
```php
|
||||
// In einem Controller
|
||||
public function showForm(): Response
|
||||
{
|
||||
$csrfToken = $this->csrfTokenGenerator->generate();
|
||||
$this->session->set('csrf_token', $csrfToken->toString());
|
||||
|
||||
return $this->render('form.twig', [
|
||||
'csrf_token' => $csrfToken->toString(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function handleForm(Request $request): Response
|
||||
{
|
||||
$storedToken = CsrfToken::fromString($this->session->get('csrf_token'));
|
||||
$submittedToken = $request->getPostParam('csrf_token');
|
||||
|
||||
if (!$storedToken->equalsString($submittedToken)) {
|
||||
throw new SecurityException('Ungültiges CSRF-Token');
|
||||
}
|
||||
|
||||
// Formular verarbeiten
|
||||
}
|
||||
```
|
||||
|
||||
### Content Security Policy anpassen
|
||||
|
||||
```php
|
||||
// In einem Controller oder Middleware
|
||||
public function __invoke(MiddlewareContext $context, Next $next): MiddlewareContext
|
||||
{
|
||||
$resultContext = $next($context);
|
||||
|
||||
if ($resultContext->hasResponse()) {
|
||||
$response = $resultContext->response;
|
||||
$headers = $response->headers;
|
||||
|
||||
// CSP für eine bestimmte Route anpassen
|
||||
$updatedHeaders = $headers->with(
|
||||
'Content-Security-Policy',
|
||||
"default-src 'self'; script-src 'self' https://trusted-cdn.com;"
|
||||
);
|
||||
|
||||
$updatedResponse = $this->manipulator->withHeaders($response, $updatedHeaders);
|
||||
return $resultContext->withResponse($updatedResponse);
|
||||
}
|
||||
|
||||
return $resultContext;
|
||||
}
|
||||
```
|
||||
|
||||
### API-Anfragen signieren und verifizieren
|
||||
|
||||
```php
|
||||
// Client-Seite: Anfrage signieren
|
||||
$request = new HttpRequest('POST', '/api/data');
|
||||
$signedRequest = $this->requestSigningService->signOutgoingRequest($request);
|
||||
$response = $this->httpClient->send($signedRequest);
|
||||
|
||||
// Server-Seite: Anfrage verifizieren (in Middleware)
|
||||
public function __invoke(MiddlewareContext $context, Next $next): MiddlewareContext
|
||||
{
|
||||
$request = $context->request;
|
||||
|
||||
// Prüfen, ob die Route signierte Anfragen erfordert
|
||||
if ($this->requiresSignature($request->path)) {
|
||||
$result = $this->requestSigningService->verifyIncomingRequest($request);
|
||||
|
||||
if (!$result->isSuccess()) {
|
||||
return $context->withResponse(
|
||||
new Response(401, [], ['error' => 'Ungültige Signatur: ' . $result->errorMessage])
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $next($context);
|
||||
}
|
||||
```
|
||||
|
||||
### Schlüsselverwaltung für Request Signing
|
||||
|
||||
```php
|
||||
// HMAC-Schlüssel generieren
|
||||
$key = $requestSigningService->generateHmacKey(
|
||||
'api-key-1',
|
||||
SigningAlgorithm::HMAC_SHA256,
|
||||
new \DateTimeImmutable('+30 days')
|
||||
);
|
||||
|
||||
// RSA-Schlüssel erstellen
|
||||
$privateKey = file_get_contents('private_key.pem');
|
||||
$key = $requestSigningService->createRsaKey(
|
||||
'api-key-2',
|
||||
$privateKey,
|
||||
new \DateTimeImmutable('+1 year')
|
||||
);
|
||||
|
||||
// Schlüssel rotieren
|
||||
$newKey = SigningKey::generateHmac('api-key-1-new', SigningAlgorithm::HMAC_SHA256);
|
||||
$requestSigningService->rotateSigningKey('api-key-1', $newKey);
|
||||
|
||||
// Schlüssel entfernen
|
||||
$requestSigningService->removeSigningKey('api-key-1');
|
||||
```
|
||||
|
||||
## Integration
|
||||
|
||||
### Middleware-Integration
|
||||
|
||||
Die Sicherheitsfunktionen sind als Middleware-Komponenten implementiert und können einfach in die Anwendung integriert werden:
|
||||
|
||||
```php
|
||||
// In der Bootstrap-Datei
|
||||
$app->addMiddleware(SecurityHeaderMiddleware::class);
|
||||
$app->addMiddleware(CsrfProtectionMiddleware::class);
|
||||
$app->addMiddleware(RequestSigningMiddleware::class);
|
||||
$app->addMiddleware(SessionSecurityMiddleware::class);
|
||||
```
|
||||
|
||||
### Event-Integration
|
||||
|
||||
Sicherheitsereignisse werden über das Event-System des Frameworks verarbeitet:
|
||||
|
||||
```php
|
||||
// Event-Listener registrieren
|
||||
$eventDispatcher->addListener(SecurityEventInterface::class, function (SecurityEventInterface $event) {
|
||||
// Auf Sicherheitsereignis reagieren
|
||||
if ($event->getSeverity() >= SecurityLogLevel::WARNING) {
|
||||
$this->notificationService->sendAlert($event);
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### Analytics-Integration
|
||||
|
||||
Sicherheitsereignisse werden automatisch im Analytics-System erfasst:
|
||||
|
||||
```php
|
||||
// In der Bootstrap-Datei
|
||||
$app->addAnalyticsBridge(SecurityAnalyticsListener::class);
|
||||
|
||||
// Sicherheitsanalysen abrufen
|
||||
$securityStats = $analyticsDashboard->getSecurityStats();
|
||||
```
|
||||
|
||||
## Erweiterte Funktionen
|
||||
|
||||
### IP-basierte Sicherheit
|
||||
|
||||
```php
|
||||
// IP-Adresse prüfen
|
||||
if (!$ipSecurityService->isAllowed($request->getClientIp())) {
|
||||
throw new SecurityException('Zugriff verweigert');
|
||||
}
|
||||
|
||||
// Rate-Limiting basierend auf IP-Adresse
|
||||
$ipSecurityService->trackRequest($request->getClientIp());
|
||||
if ($ipSecurityService->isRateLimited($request->getClientIp())) {
|
||||
throw new SecurityException('Rate-Limit überschritten');
|
||||
}
|
||||
```
|
||||
|
||||
### Benutzerdefinierte Security-Header
|
||||
|
||||
```php
|
||||
// Eigene Security-Header-Konfiguration erstellen
|
||||
$config = new SecurityHeaderConfig();
|
||||
$config->contentSecurityPolicy = "default-src 'self'; script-src 'self' https://trusted-cdn.com;";
|
||||
$config->frameOptions = "DENY";
|
||||
$config->referrerPolicy = "strict-origin-when-cross-origin";
|
||||
|
||||
// Konfiguration anwenden
|
||||
$securityHeaderMiddleware = new SecurityHeaderMiddleware($responseManipulator, $config);
|
||||
```
|
||||
|
||||
### Erweiterte CSRF-Schutzmaßnahmen
|
||||
|
||||
```php
|
||||
// CSRF-Token mit Metadaten generieren
|
||||
$csrfTokenData = new CsrfTokenData(
|
||||
$csrfToken,
|
||||
$request->getClientIp(),
|
||||
$request->path,
|
||||
new \DateTimeImmutable('+1 hour')
|
||||
);
|
||||
|
||||
// Token mit Metadaten validieren
|
||||
if (!$csrfValidator->validate($csrfTokenData, $request)) {
|
||||
throw new SecurityException('Ungültiges oder abgelaufenes CSRF-Token');
|
||||
}
|
||||
```
|
||||
|
||||
## Fehlerbehebung
|
||||
|
||||
### Häufige Probleme
|
||||
|
||||
1. **CSP-Fehler im Browser**:
|
||||
- Überprüfen Sie die Content-Security-Policy in der Konfiguration
|
||||
- Fügen Sie fehlende Quellen zur CSP hinzu
|
||||
- Verwenden Sie die Browser-Entwicklertools, um CSP-Verstöße zu identifizieren
|
||||
|
||||
2. **CSRF-Token-Fehler**:
|
||||
- Stellen Sie sicher, dass das Token korrekt im Formular übermittelt wird
|
||||
- Überprüfen Sie die Session-Konfiguration
|
||||
- Prüfen Sie, ob das Token abgelaufen ist
|
||||
|
||||
3. **Request-Signing-Fehler**:
|
||||
- Überprüfen Sie, ob der richtige Schlüssel verwendet wird
|
||||
- Stellen Sie sicher, dass die Uhrzeit auf Client und Server synchronisiert ist
|
||||
- Prüfen Sie, ob alle erforderlichen Header signiert werden
|
||||
|
||||
## Weiterführende Informationen
|
||||
|
||||
- [CSRF-Schutz im Detail](csrf-protection.md)
|
||||
- [Security Headers und CSP](security-headers.md)
|
||||
- [Request Signing API](request-signing.md)
|
||||
- [Sicherheits-Best-Practices](/guides/security-best-practices.md)
|
||||
410
docs/components/security/request-signing.md
Normal file
410
docs/components/security/request-signing.md
Normal file
@@ -0,0 +1,410 @@
|
||||
# Request Signing
|
||||
|
||||
> **Dokumentationshinweis:** Diese Dokumentation ist vollständig aktualisiert und stellt die aktuelle Implementierung des Request Signing korrekt dar.
|
||||
|
||||
## Übersicht
|
||||
|
||||
Request Signing ist ein Sicherheitsmechanismus, der die Authentizität und Integrität von HTTP-Anfragen gewährleistet. Durch die kryptografische Signierung von Anfragen können Empfänger überprüfen, dass die Anfrage von einem autorisierten Absender stammt und während der Übertragung nicht manipuliert wurde. Das Framework bietet eine robuste Implementierung von Request Signing, die sowohl für ausgehende als auch für eingehende Anfragen verwendet werden kann.
|
||||
|
||||
## Hauptkomponenten
|
||||
|
||||
### RequestSigningService
|
||||
|
||||
Die `RequestSigningService`-Klasse ist die zentrale Komponente für Request Signing:
|
||||
|
||||
```php
|
||||
use App\Framework\Security\RequestSigning\RequestSigningService;
|
||||
|
||||
// Service initialisieren
|
||||
$service = new RequestSigningService(
|
||||
$signer,
|
||||
$verifier,
|
||||
$keyRepository,
|
||||
$config,
|
||||
$logger
|
||||
);
|
||||
|
||||
// Ausgehende Anfrage signieren
|
||||
$signedRequest = $service->signOutgoingRequest($request, $keyId);
|
||||
|
||||
// Eingehende Anfrage verifizieren
|
||||
$result = $service->verifyIncomingRequest($request);
|
||||
if (!$result->isSuccess()) {
|
||||
throw new SecurityException('Ungültige Anfrage-Signatur: ' . $result->errorMessage);
|
||||
}
|
||||
```
|
||||
|
||||
### SigningKey
|
||||
|
||||
Die `SigningKey`-Klasse repräsentiert einen kryptografischen Schlüssel für das Signieren von Anfragen:
|
||||
|
||||
```php
|
||||
use App\Framework\Security\RequestSigning\SigningKey;
|
||||
use App\Framework\Security\RequestSigning\SigningAlgorithm;
|
||||
|
||||
// HMAC-Schlüssel generieren
|
||||
$hmacKey = SigningKey::generateHmac(
|
||||
'api-key-1',
|
||||
SigningAlgorithm::HMAC_SHA256,
|
||||
new \DateTimeImmutable('+30 days') // Ablaufdatum
|
||||
);
|
||||
|
||||
// RSA-Schlüssel erstellen
|
||||
$privateKey = file_get_contents('private_key.pem');
|
||||
$rsaKey = SigningKey::createRsa(
|
||||
'api-key-2',
|
||||
$privateKey,
|
||||
new \DateTimeImmutable('+1 year') // Ablaufdatum
|
||||
);
|
||||
```
|
||||
|
||||
### SigningAlgorithm
|
||||
|
||||
Das `SigningAlgorithm`-Enum definiert die unterstützten Signaturalgorithmen:
|
||||
|
||||
```php
|
||||
use App\Framework\Security\RequestSigning\SigningAlgorithm;
|
||||
|
||||
// Verfügbare Algorithmen
|
||||
$algorithm = SigningAlgorithm::HMAC_SHA256; // HMAC mit SHA-256
|
||||
$algorithm = SigningAlgorithm::HMAC_SHA512; // HMAC mit SHA-512
|
||||
$algorithm = SigningAlgorithm::RSA_SHA256; // RSA mit SHA-256
|
||||
$algorithm = SigningAlgorithm::RSA_SHA512; // RSA mit SHA-512
|
||||
```
|
||||
|
||||
### RequestSigner
|
||||
|
||||
Die `RequestSigner`-Klasse ist für das Signieren von ausgehenden Anfragen verantwortlich:
|
||||
|
||||
```php
|
||||
use App\Framework\Security\RequestSigning\RequestSigner;
|
||||
|
||||
// Signer initialisieren
|
||||
$signer = new RequestSigner($clock);
|
||||
|
||||
// Anfrage signieren
|
||||
$signedRequest = $signer->signRequest(
|
||||
$request,
|
||||
$signingKey,
|
||||
['(request-target)', 'host', 'date', 'content-type'],
|
||||
300 // Gültigkeitsdauer in Sekunden
|
||||
);
|
||||
```
|
||||
|
||||
### RequestVerifier
|
||||
|
||||
Die `RequestVerifier`-Klasse ist für die Überprüfung von eingehenden Anfragen verantwortlich:
|
||||
|
||||
```php
|
||||
use App\Framework\Security\RequestSigning\RequestVerifier;
|
||||
|
||||
// Verifier initialisieren
|
||||
$verifier = new RequestVerifier($keyRepository, $clock);
|
||||
|
||||
// Anfrage verifizieren
|
||||
$result = $verifier->verify($request);
|
||||
if ($result->isSuccess()) {
|
||||
// Anfrage ist gültig
|
||||
$signature = $result->signature;
|
||||
$key = $result->key;
|
||||
} else {
|
||||
// Anfrage ist ungültig
|
||||
$errorMessage = $result->errorMessage;
|
||||
}
|
||||
```
|
||||
|
||||
### SigningKeyRepository
|
||||
|
||||
Das `SigningKeyRepository`-Interface definiert die Methoden zum Speichern und Abrufen von Signaturschlüsseln:
|
||||
|
||||
```php
|
||||
use App\Framework\Security\RequestSigning\SigningKeyRepository;
|
||||
use App\Framework\Security\RequestSigning\InMemorySigningKeyRepository;
|
||||
use App\Framework\Security\RequestSigning\EntityManagerSigningKeyRepository;
|
||||
|
||||
// In-Memory-Repository für Tests
|
||||
$repository = new InMemorySigningKeyRepository();
|
||||
|
||||
// Entity-Manager-Repository für Produktion
|
||||
$repository = new EntityManagerSigningKeyRepository($entityManager);
|
||||
|
||||
// Schlüssel speichern
|
||||
$repository->store($signingKey);
|
||||
|
||||
// Schlüssel abrufen
|
||||
$key = $repository->findByKeyId('api-key-1');
|
||||
|
||||
// Alle aktiven Schlüssel abrufen
|
||||
$activeKeys = $repository->getAllActive();
|
||||
|
||||
// Schlüssel entfernen
|
||||
$repository->remove('api-key-1');
|
||||
|
||||
// Schlüssel rotieren
|
||||
$repository->rotateKey('api-key-1', $newKey);
|
||||
```
|
||||
|
||||
## Signaturformat
|
||||
|
||||
Das Framework verwendet das [HTTP Signature](https://tools.ietf.org/html/draft-cavage-http-signatures) Format für Request Signing:
|
||||
|
||||
```
|
||||
Authorization: Signature keyId="api-key-1",algorithm="hmac-sha256",headers="(request-target) host date",signature="Base64(HMAC-SHA256(SigningString))"
|
||||
```
|
||||
|
||||
Die Signatur wird aus einer Zeichenkette (Signing String) generiert, die aus den angegebenen Header-Werten besteht:
|
||||
|
||||
```
|
||||
(request-target): post /api/data
|
||||
host: example.com
|
||||
date: Tue, 07 Jun 2023 20:51:35 GMT
|
||||
```
|
||||
|
||||
## Verwendung
|
||||
|
||||
### Ausgehende Anfragen signieren
|
||||
|
||||
```php
|
||||
// In einem API-Client
|
||||
public function sendRequest(HttpRequest $request): HttpResponse
|
||||
{
|
||||
// Anfrage signieren
|
||||
$signedRequest = $this->requestSigningService->signOutgoingRequest(
|
||||
$request,
|
||||
'api-key-1', // Optional: Spezifischer Schlüssel
|
||||
['(request-target)', 'host', 'date', 'content-type'], // Optional: Zu signierende Header
|
||||
300 // Optional: Gültigkeitsdauer in Sekunden
|
||||
);
|
||||
|
||||
// Signierte Anfrage senden
|
||||
return $this->httpClient->send($signedRequest);
|
||||
}
|
||||
```
|
||||
|
||||
### Eingehende Anfragen verifizieren
|
||||
|
||||
```php
|
||||
// In einer Middleware
|
||||
public function __invoke(MiddlewareContext $context, Next $next): MiddlewareContext
|
||||
{
|
||||
$request = $context->request;
|
||||
|
||||
// Prüfen, ob die Route signierte Anfragen erfordert
|
||||
if ($this->requiresSignature($request->path)) {
|
||||
$result = $this->requestSigningService->verifyIncomingRequest($request);
|
||||
|
||||
if (!$result->isSuccess()) {
|
||||
return $context->withResponse(
|
||||
new Response(401, [], ['error' => 'Ungültige Signatur: ' . $result->errorMessage])
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $next($context);
|
||||
}
|
||||
```
|
||||
|
||||
### Schlüsselverwaltung
|
||||
|
||||
```php
|
||||
// HMAC-Schlüssel generieren
|
||||
$key = $requestSigningService->generateHmacKey(
|
||||
'api-key-1',
|
||||
SigningAlgorithm::HMAC_SHA256,
|
||||
new \DateTimeImmutable('+30 days')
|
||||
);
|
||||
|
||||
// RSA-Schlüssel erstellen
|
||||
$privateKey = file_get_contents('private_key.pem');
|
||||
$key = $requestSigningService->createRsaKey(
|
||||
'api-key-2',
|
||||
$privateKey,
|
||||
new \DateTimeImmutable('+1 year')
|
||||
);
|
||||
|
||||
// Schlüssel rotieren
|
||||
$newKey = SigningKey::generateHmac('api-key-1-new', SigningAlgorithm::HMAC_SHA256);
|
||||
$requestSigningService->rotateSigningKey('api-key-1', $newKey);
|
||||
|
||||
// Schlüssel entfernen
|
||||
$requestSigningService->removeSigningKey('api-key-1');
|
||||
|
||||
// Alle aktiven Schlüssel abrufen
|
||||
$activeKeys = $requestSigningService->getActiveKeys();
|
||||
```
|
||||
|
||||
## Integration mit dem Framework
|
||||
|
||||
### RequestSigningMiddleware
|
||||
|
||||
Das Framework bietet eine `RequestSigningMiddleware`, die automatisch eingehende Anfragen verifiziert:
|
||||
|
||||
```php
|
||||
// In der Bootstrap-Datei oder Router-Konfiguration
|
||||
$app->addMiddleware(RequestSigningMiddleware::class);
|
||||
```
|
||||
|
||||
### HttpClientSigningMiddleware
|
||||
|
||||
Für ausgehende Anfragen bietet das Framework eine `HttpClientSigningMiddleware`:
|
||||
|
||||
```php
|
||||
// HTTP-Client mit Signatur-Middleware konfigurieren
|
||||
$httpClient = new HttpClient([
|
||||
'middlewares' => [
|
||||
new HttpClientSigningMiddleware($requestSigningService)
|
||||
]
|
||||
]);
|
||||
|
||||
// Anfragen werden automatisch signiert
|
||||
$response = $httpClient->send($request);
|
||||
```
|
||||
|
||||
### Konfiguration
|
||||
|
||||
Die Request-Signing-Funktionen können in der Konfigurationsdatei angepasst werden:
|
||||
|
||||
```php
|
||||
// config/security.php
|
||||
return [
|
||||
'request_signing' => [
|
||||
'enabled' => true,
|
||||
'default_algorithm' => 'hmac-sha256',
|
||||
'default_headers' => ['(request-target)', 'host', 'date', 'content-type'],
|
||||
'default_expiry' => 300, // Sekunden
|
||||
'required_routes' => [
|
||||
'/api/admin/*',
|
||||
'/api/payments/*',
|
||||
],
|
||||
],
|
||||
];
|
||||
```
|
||||
|
||||
## Erweiterte Funktionen
|
||||
|
||||
### Signatur mit Ablaufdatum
|
||||
|
||||
Signaturen können mit einem Ablaufdatum versehen werden, um Replay-Angriffe zu verhindern:
|
||||
|
||||
```php
|
||||
// Anfrage mit Ablaufdatum signieren
|
||||
$signedRequest = $requestSigningService->signOutgoingRequest(
|
||||
$request,
|
||||
'api-key-1',
|
||||
['(request-target)', 'host', 'date', '(created)', '(expires)'],
|
||||
300 // Gültigkeitsdauer in Sekunden
|
||||
);
|
||||
```
|
||||
|
||||
Die Signatur enthält dann zusätzliche Felder:
|
||||
|
||||
```
|
||||
Authorization: Signature keyId="api-key-1",algorithm="hmac-sha256",headers="(request-target) host date (created) (expires)",created=1623094295,expires=1623094595,signature="..."
|
||||
```
|
||||
|
||||
### Digest-Header
|
||||
|
||||
Für zusätzliche Sicherheit kann der Anfrage-Body mit einem Digest-Header geschützt werden:
|
||||
|
||||
```php
|
||||
// Anfrage mit Digest-Header signieren
|
||||
$request = $request->withHeader('Digest', 'SHA-256=' . base64_encode(hash('sha256', $request->body, true)));
|
||||
$signedRequest = $requestSigningService->signOutgoingRequest(
|
||||
$request,
|
||||
'api-key-1',
|
||||
['(request-target)', 'host', 'date', 'digest']
|
||||
);
|
||||
```
|
||||
|
||||
### Schlüsselrotation
|
||||
|
||||
Für zusätzliche Sicherheit sollten Schlüssel regelmäßig rotiert werden:
|
||||
|
||||
```php
|
||||
// Neuen Schlüssel generieren
|
||||
$newKey = $requestSigningService->generateHmacKey(
|
||||
'api-key-1-new',
|
||||
SigningAlgorithm::HMAC_SHA256
|
||||
);
|
||||
|
||||
// Alten Schlüssel durch neuen ersetzen
|
||||
$requestSigningService->rotateSigningKey('api-key-1', $newKey);
|
||||
|
||||
// Alten Schlüssel nach einer Übergangszeit entfernen
|
||||
// (nachdem alle Clients auf den neuen Schlüssel umgestellt wurden)
|
||||
$requestSigningService->removeSigningKey('api-key-1');
|
||||
```
|
||||
|
||||
## Sicherheitsüberlegungen
|
||||
|
||||
### Best Practices
|
||||
|
||||
1. **Verwenden Sie starke Algorithmen**: HMAC-SHA256 oder RSA-SHA256 sind empfohlen.
|
||||
2. **Schützen Sie private Schlüssel**: Speichern Sie private Schlüssel sicher und teilen Sie sie nie öffentlich.
|
||||
3. **Signieren Sie kritische Header**: Mindestens `(request-target)`, `host`, `date` und `content-type` sollten signiert werden.
|
||||
4. **Verwenden Sie Ablaufdaten**: Sowohl für Schlüssel als auch für Signaturen, um Replay-Angriffe zu verhindern.
|
||||
5. **Rotieren Sie Schlüssel regelmäßig**: Implementieren Sie einen Prozess zur regelmäßigen Schlüsselrotation.
|
||||
|
||||
### Bekannte Einschränkungen
|
||||
|
||||
- Request Signing schützt nicht vor Man-in-the-Middle-Angriffen; verwenden Sie immer HTTPS.
|
||||
- Die Sicherheit hängt von der sicheren Speicherung und Verteilung der Schlüssel ab.
|
||||
- Uhrzeitsynchronisation zwischen Client und Server ist wichtig für zeitbasierte Validierung.
|
||||
|
||||
## Fehlerbehebung
|
||||
|
||||
### Häufige Probleme
|
||||
|
||||
#### Signaturvalidierung schlägt fehl
|
||||
|
||||
Mögliche Ursachen:
|
||||
- Die Signatur ist abgelaufen
|
||||
- Die Uhrzeit zwischen Client und Server ist nicht synchronisiert
|
||||
- Der falsche Schlüssel wird verwendet
|
||||
- Die Header-Liste stimmt nicht überein
|
||||
- Der Anfrage-Body wurde nach der Signierung geändert
|
||||
|
||||
Lösung:
|
||||
- Überprüfen Sie die Uhrzeit auf Client und Server
|
||||
- Stellen Sie sicher, dass der richtige Schlüssel verwendet wird
|
||||
- Überprüfen Sie, ob alle erforderlichen Header signiert werden
|
||||
- Verwenden Sie den Digest-Header für Anfragen mit Body
|
||||
|
||||
#### Schlüssel nicht gefunden
|
||||
|
||||
Wenn der Schlüssel nicht gefunden wird:
|
||||
|
||||
```php
|
||||
// Prüfen, ob der Schlüssel existiert
|
||||
$key = $keyRepository->findByKeyId($keyId);
|
||||
if ($key === null) {
|
||||
// Schlüssel nicht gefunden
|
||||
$this->logger->warning('Signing key not found', ['key_id' => $keyId]);
|
||||
}
|
||||
```
|
||||
|
||||
#### Logging für Fehlerbehebung
|
||||
|
||||
Das Framework bietet umfangreiches Logging für Request Signing:
|
||||
|
||||
```php
|
||||
// In der RequestSigningService-Klasse
|
||||
$this->logger->info('Request signature verified successfully', [
|
||||
'key_id' => $result->signature->keyId,
|
||||
'algorithm' => $result->signature->algorithm->value,
|
||||
'path' => $request->path,
|
||||
]);
|
||||
|
||||
$this->logger->warning('Request signature verification failed', [
|
||||
'error' => $result->errorMessage,
|
||||
'path' => $request->path,
|
||||
]);
|
||||
```
|
||||
|
||||
## Weiterführende Informationen
|
||||
|
||||
- [Security Features Übersicht](index.md)
|
||||
- [CSRF-Schutz](csrf-protection.md)
|
||||
- [Security Headers](security-headers.md)
|
||||
- [Sicherheits-Best-Practices](/guides/security-best-practices.md)
|
||||
- [HTTP Signatures Spezifikation](https://tools.ietf.org/html/draft-cavage-http-signatures)
|
||||
382
docs/components/security/security-headers.md
Normal file
382
docs/components/security/security-headers.md
Normal file
@@ -0,0 +1,382 @@
|
||||
# Security Headers
|
||||
|
||||
> **Dokumentationshinweis:** Diese Dokumentation ist vollständig aktualisiert und stellt die aktuelle Implementierung der Security Headers korrekt dar.
|
||||
|
||||
## Übersicht
|
||||
|
||||
Security Headers sind HTTP-Header, die dazu beitragen, die Sicherheit Ihrer Webanwendung zu verbessern, indem sie dem Browser Anweisungen geben, wie er mit bestimmten Sicherheitsaspekten umgehen soll. Das Framework implementiert automatisch eine umfassende Sammlung von Security Headers, die den aktuellen Best Practices entsprechen und Ihre Anwendung vor verschiedenen Arten von Angriffen schützen.
|
||||
|
||||
## Hauptkomponenten
|
||||
|
||||
### SecurityHeaderMiddleware
|
||||
|
||||
Die `SecurityHeaderMiddleware` ist die zentrale Komponente für die Implementierung von Security Headers:
|
||||
|
||||
```php
|
||||
use App\Framework\Http\Middlewares\SecurityHeaderMiddleware;
|
||||
|
||||
// In der Bootstrap-Datei oder Router-Konfiguration
|
||||
$app->addMiddleware(SecurityHeaderMiddleware::class);
|
||||
```
|
||||
|
||||
Diese Middleware:
|
||||
1. Entfernt unsichere Header wie `X-Powered-By` und `Server`
|
||||
2. Fügt automatisch wichtige Security Headers zu allen HTTP-Antworten hinzu
|
||||
3. Passt die Header basierend auf der Umgebung (Entwicklung oder Produktion) an
|
||||
|
||||
### SecurityHeaderConfig
|
||||
|
||||
Die `SecurityHeaderConfig`-Klasse enthält die Konfiguration für die Security Headers:
|
||||
|
||||
```php
|
||||
use App\Framework\Http\Middlewares\SecurityHeaderConfig;
|
||||
|
||||
// Vordefinierte Konfigurationen verwenden
|
||||
$config = SecurityHeaderConfig::forProduction(); // Strenge Einstellungen für Produktion
|
||||
$config = SecurityHeaderConfig::forDevelopment(); // Weniger strenge Einstellungen für Entwicklung
|
||||
|
||||
// Benutzerdefinierte Konfiguration erstellen
|
||||
$config = new SecurityHeaderConfig(
|
||||
$contentSecurityPolicy,
|
||||
$hstsHeader,
|
||||
$frameOptions,
|
||||
$referrerPolicy,
|
||||
$permissionsPolicy,
|
||||
$crossOriginEmbedderPolicy,
|
||||
$crossOriginOpenerPolicy,
|
||||
$crossOriginResourcePolicy
|
||||
);
|
||||
```
|
||||
|
||||
## Implementierte Security Headers
|
||||
|
||||
### Content-Security-Policy (CSP)
|
||||
|
||||
Die Content Security Policy schützt vor Cross-Site Scripting (XSS) und anderen Code-Injection-Angriffen, indem sie definiert, welche Ressourcen geladen werden dürfen:
|
||||
|
||||
```
|
||||
Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline';
|
||||
```
|
||||
|
||||
In der Produktionsumgebung wird eine strengere CSP verwendet:
|
||||
|
||||
```
|
||||
Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; font-src 'self'; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none';
|
||||
```
|
||||
|
||||
### Strict-Transport-Security (HSTS)
|
||||
|
||||
HSTS zwingt den Browser, nur HTTPS-Verbindungen zu verwenden, selbst wenn der Benutzer versucht, HTTP zu verwenden:
|
||||
|
||||
```
|
||||
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
|
||||
```
|
||||
|
||||
Parameter:
|
||||
- `max-age`: Dauer in Sekunden, für die der Browser nur HTTPS verwenden soll (1 Jahr)
|
||||
- `includeSubDomains`: Gilt auch für alle Subdomains
|
||||
- `preload`: Ermöglicht die Aufnahme in die HSTS-Preload-Liste der Browser
|
||||
|
||||
### X-Frame-Options
|
||||
|
||||
Schützt vor Clickjacking-Angriffen, indem es kontrolliert, ob die Seite in einem Frame eingebettet werden darf:
|
||||
|
||||
```
|
||||
X-Frame-Options: DENY
|
||||
```
|
||||
|
||||
Optionen:
|
||||
- `DENY`: Die Seite darf nicht in einem Frame eingebettet werden
|
||||
- `SAMEORIGIN`: Die Seite darf nur in einem Frame auf derselben Domain eingebettet werden
|
||||
|
||||
### X-Content-Type-Options
|
||||
|
||||
Verhindert MIME-Sniffing, bei dem Browser den Inhaltstyp einer Ressource erraten:
|
||||
|
||||
```
|
||||
X-Content-Type-Options: nosniff
|
||||
```
|
||||
|
||||
### Referrer-Policy
|
||||
|
||||
Kontrolliert, welche Referrer-Informationen beim Navigieren zu anderen Seiten gesendet werden:
|
||||
|
||||
```
|
||||
Referrer-Policy: strict-origin-when-cross-origin
|
||||
```
|
||||
|
||||
Optionen:
|
||||
- `no-referrer`: Keine Referrer-Informationen senden
|
||||
- `no-referrer-when-downgrade`: Keine Referrer-Informationen senden, wenn von HTTPS zu HTTP navigiert wird
|
||||
- `same-origin`: Nur Referrer-Informationen senden, wenn auf derselben Domain navigiert wird
|
||||
- `strict-origin`: Nur die Ursprungs-URL senden und nur bei gleichem Sicherheitsniveau
|
||||
- `strict-origin-when-cross-origin`: Vollständige URL bei gleicher Domain, nur Ursprung bei Cross-Origin und gleichem Sicherheitsniveau
|
||||
|
||||
### Permissions-Policy
|
||||
|
||||
Kontrolliert, welche Browser-Features und APIs die Seite verwenden darf:
|
||||
|
||||
```
|
||||
Permissions-Policy: camera=(), microphone=(), geolocation=(), interest-cohort=()
|
||||
```
|
||||
|
||||
Diese Einstellung deaktiviert den Zugriff auf Kamera, Mikrofon, Geolocation und FLoC (Federated Learning of Cohorts).
|
||||
|
||||
### Cross-Origin-Policies
|
||||
|
||||
Eine Gruppe von Headern, die die Interaktion zwischen verschiedenen Ursprüngen kontrollieren:
|
||||
|
||||
```
|
||||
Cross-Origin-Embedder-Policy: require-corp
|
||||
Cross-Origin-Opener-Policy: same-origin
|
||||
Cross-Origin-Resource-Policy: same-origin
|
||||
```
|
||||
|
||||
Diese Header schützen vor verschiedenen Cross-Origin-Angriffen und Informationslecks.
|
||||
|
||||
### X-Permitted-Cross-Domain-Policies
|
||||
|
||||
Kontrolliert, ob Cross-Domain-Richtliniendateien (crossdomain.xml) geladen werden dürfen:
|
||||
|
||||
```
|
||||
X-Permitted-Cross-Domain-Policies: none
|
||||
```
|
||||
|
||||
## Konfiguration
|
||||
|
||||
### Umgebungsspezifische Konfiguration
|
||||
|
||||
Das Framework bietet vordefinierte Konfigurationen für verschiedene Umgebungen:
|
||||
|
||||
```php
|
||||
// In der SecurityHeaderMiddleware
|
||||
$config = $this->securityConfig->enableStrictMode
|
||||
? SecurityHeaderConfig::forProduction()
|
||||
: SecurityHeaderConfig::forDevelopment();
|
||||
```
|
||||
|
||||
Die `enableStrictMode`-Einstellung kann in der Konfigurationsdatei angepasst werden:
|
||||
|
||||
```php
|
||||
// config/security.php
|
||||
return [
|
||||
'headers' => [
|
||||
'strict_mode' => true, // Produktionsmodus mit strengeren Einstellungen
|
||||
'content_security_policy' => "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline';",
|
||||
'hsts' => [
|
||||
'enabled' => true,
|
||||
'max_age' => 31536000, // 1 Jahr
|
||||
'include_subdomains' => true,
|
||||
'preload' => true,
|
||||
],
|
||||
],
|
||||
];
|
||||
```
|
||||
|
||||
### Benutzerdefinierte Konfiguration
|
||||
|
||||
Sie können die Security Headers auch manuell konfigurieren:
|
||||
|
||||
```php
|
||||
// Eigene Security-Header-Konfiguration erstellen
|
||||
$config = new SecurityHeaderConfig();
|
||||
$config->contentSecurityPolicy = "default-src 'self'; script-src 'self' https://trusted-cdn.com;";
|
||||
$config->frameOptions = "DENY";
|
||||
$config->referrerPolicy = "strict-origin-when-cross-origin";
|
||||
|
||||
// Konfiguration anwenden
|
||||
$securityHeaderMiddleware = new SecurityHeaderMiddleware($responseManipulator, $config);
|
||||
```
|
||||
|
||||
## Verwendung
|
||||
|
||||
### Automatische Anwendung
|
||||
|
||||
Die Security Headers werden automatisch auf alle Antworten angewendet, wenn die Middleware registriert ist:
|
||||
|
||||
```php
|
||||
// In der Bootstrap-Datei
|
||||
$app->addMiddleware(SecurityHeaderMiddleware::class);
|
||||
```
|
||||
|
||||
### Anpassung für bestimmte Routen
|
||||
|
||||
In einigen Fällen möchten Sie möglicherweise die Security Headers für bestimmte Routen anpassen:
|
||||
|
||||
```php
|
||||
// In einem Controller oder einer Middleware
|
||||
public function __invoke(MiddlewareContext $context, Next $next): MiddlewareContext
|
||||
{
|
||||
$resultContext = $next($context);
|
||||
|
||||
if ($resultContext->hasResponse()) {
|
||||
$response = $resultContext->response;
|
||||
$headers = $response->headers;
|
||||
|
||||
// CSP für eine bestimmte Route anpassen
|
||||
$updatedHeaders = $headers->with(
|
||||
'Content-Security-Policy',
|
||||
"default-src 'self'; script-src 'self' https://trusted-cdn.com;"
|
||||
);
|
||||
|
||||
$updatedResponse = $this->manipulator->withHeaders($response, $updatedHeaders);
|
||||
return $resultContext->withResponse($updatedResponse);
|
||||
}
|
||||
|
||||
return $resultContext;
|
||||
}
|
||||
```
|
||||
|
||||
### Prüfen der aktuellen Security Headers
|
||||
|
||||
Sie können die aktuellen Security Headers Ihrer Anwendung mit verschiedenen Tools überprüfen:
|
||||
|
||||
1. **Browser-Entwicklertools**: Überprüfen Sie die Response-Header in der Netzwerk-Ansicht
|
||||
2. **Online-Tools**: Verwenden Sie Dienste wie [securityheaders.com](https://securityheaders.com) oder [observatory.mozilla.org](https://observatory.mozilla.org)
|
||||
3. **Curl-Befehl**: `curl -I https://yourdomain.com`
|
||||
|
||||
## Content Security Policy (CSP)
|
||||
|
||||
### Grundlegende CSP-Direktiven
|
||||
|
||||
Die Content Security Policy ist einer der wichtigsten Security Headers und verdient besondere Aufmerksamkeit:
|
||||
|
||||
- `default-src`: Standardrichtlinie für alle Ressourcentypen
|
||||
- `script-src`: Erlaubte Quellen für JavaScript
|
||||
- `style-src`: Erlaubte Quellen für CSS
|
||||
- `img-src`: Erlaubte Quellen für Bilder
|
||||
- `font-src`: Erlaubte Quellen für Schriftarten
|
||||
- `connect-src`: Erlaubte Ziele für Fetch, XHR, WebSocket
|
||||
- `media-src`: Erlaubte Quellen für Audio und Video
|
||||
- `object-src`: Erlaubte Quellen für Plugins (Flash, PDF)
|
||||
- `frame-src`: Erlaubte Quellen für Frames
|
||||
- `base-uri`: Erlaubte URLs für das base-Element
|
||||
- `form-action`: Erlaubte Ziele für Formularübermittlungen
|
||||
- `frame-ancestors`: Erlaubte übergeordnete Dokumente (ähnlich X-Frame-Options)
|
||||
|
||||
### CSP-Quellwerte
|
||||
|
||||
- `'self'`: Ressourcen von derselben Ursprungs-Domain
|
||||
- `'none'`: Keine Ressourcen erlaubt
|
||||
- `'unsafe-inline'`: Inline-Skripte und Styles erlaubt (vermeiden in Produktion)
|
||||
- `'unsafe-eval'`: eval() und ähnliche Funktionen erlaubt (vermeiden in Produktion)
|
||||
- `'nonce-<base64-value>'`: Ressourcen mit entsprechendem Nonce-Attribut
|
||||
- `'sha256-<hash>'`: Ressourcen mit entsprechendem Hash
|
||||
- `https://example.com`: Spezifische Domain
|
||||
- `https://*.example.com`: Alle Subdomains
|
||||
- `data:`: Data-URLs (vorsichtig verwenden)
|
||||
|
||||
### CSP-Beispiele
|
||||
|
||||
Strenge CSP für Produktion:
|
||||
|
||||
```
|
||||
Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; font-src 'self'; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none';
|
||||
```
|
||||
|
||||
CSP mit externen Ressourcen:
|
||||
|
||||
```
|
||||
Content-Security-Policy: default-src 'self'; script-src 'self' https://cdn.jsdelivr.net; style-src 'self' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; img-src 'self' data: https://img.example.com;
|
||||
```
|
||||
|
||||
CSP mit Nonce für Inline-Skripte:
|
||||
|
||||
```php
|
||||
// In einem Controller
|
||||
public function showPage(): Response
|
||||
{
|
||||
$nonce = bin2hex(random_bytes(16));
|
||||
|
||||
// CSP mit Nonce setzen
|
||||
$csp = "default-src 'self'; script-src 'self' 'nonce-{$nonce}';";
|
||||
|
||||
return $this->render('page.twig', [
|
||||
'nonce' => $nonce,
|
||||
'csp' => $csp,
|
||||
]);
|
||||
}
|
||||
```
|
||||
|
||||
In der Vorlage:
|
||||
|
||||
```html
|
||||
<script nonce="{{ nonce }}">
|
||||
// Dieser Inline-Code ist erlaubt, weil er den korrekten Nonce hat
|
||||
console.log('Hello, world!');
|
||||
</script>
|
||||
```
|
||||
|
||||
### CSP-Reporting
|
||||
|
||||
Sie können CSP-Verstöße an einen Endpunkt melden lassen:
|
||||
|
||||
```
|
||||
Content-Security-Policy: default-src 'self'; report-uri /csp-report;
|
||||
```
|
||||
|
||||
Oder verwenden Sie den neueren Report-To-Header:
|
||||
|
||||
```
|
||||
Content-Security-Policy: default-src 'self'; report-to csp-endpoint;
|
||||
Report-To: {"group":"csp-endpoint","max_age":10886400,"endpoints":[{"url":"/csp-report"}]}
|
||||
```
|
||||
|
||||
## Fehlerbehebung
|
||||
|
||||
### Häufige Probleme
|
||||
|
||||
#### CSP blockiert legitime Ressourcen
|
||||
|
||||
Wenn Ihre Anwendung nicht korrekt funktioniert, weil die CSP legitime Ressourcen blockiert:
|
||||
|
||||
1. Überprüfen Sie die Konsole im Browser auf CSP-Verstöße
|
||||
2. Erweitern Sie die CSP um die benötigten Quellen
|
||||
3. Verwenden Sie vorübergehend eine weniger strenge CSP während der Entwicklung
|
||||
|
||||
```php
|
||||
// Weniger strenge CSP für Entwicklung
|
||||
$config->contentSecurityPolicy = "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline';";
|
||||
```
|
||||
|
||||
#### HSTS-Probleme
|
||||
|
||||
Wenn Sie HSTS aktiviert haben und Probleme auftreten:
|
||||
|
||||
1. Beginnen Sie mit einer kurzen `max-age` (z.B. 300 Sekunden)
|
||||
2. Aktivieren Sie `includeSubDomains` erst, wenn Sie sicher sind, dass alle Subdomains HTTPS unterstützen
|
||||
3. Verwenden Sie `preload` nur, wenn Sie sicher sind, dass Ihre Domain dauerhaft HTTPS unterstützt
|
||||
|
||||
#### Frames werden blockiert
|
||||
|
||||
Wenn Ihre Anwendung Frames verwenden muss:
|
||||
|
||||
```php
|
||||
// X-Frame-Options anpassen
|
||||
$config->frameOptions = "SAMEORIGIN"; // Erlaubt Frames auf derselben Domain
|
||||
```
|
||||
|
||||
## Sicherheitsüberlegungen
|
||||
|
||||
### Best Practices
|
||||
|
||||
1. **Verwenden Sie strenge CSP**: Vermeiden Sie 'unsafe-inline' und 'unsafe-eval' in Produktion
|
||||
2. **Aktivieren Sie HSTS**: Erzwingen Sie HTTPS für alle Verbindungen
|
||||
3. **Regelmäßige Überprüfung**: Testen Sie Ihre Security Headers regelmäßig mit Tools wie [securityheaders.com](https://securityheaders.com)
|
||||
4. **Umgebungsspezifische Konfiguration**: Verwenden Sie unterschiedliche Konfigurationen für Entwicklung und Produktion
|
||||
5. **CSP-Reporting**: Implementieren Sie CSP-Reporting, um Verstöße zu erkennen
|
||||
|
||||
### Bekannte Einschränkungen
|
||||
|
||||
- Ältere Browser unterstützen möglicherweise nicht alle Security Headers
|
||||
- Eine zu strenge CSP kann die Funktionalität von Drittanbieter-Skripten beeinträchtigen
|
||||
- HSTS kann nicht rückgängig gemacht werden, bis die `max-age` abgelaufen ist
|
||||
|
||||
## Weiterführende Informationen
|
||||
|
||||
- [Security Features Übersicht](index.md)
|
||||
- [CSRF-Schutz](csrf-protection.md)
|
||||
- [Request Signing API](request-signing.md)
|
||||
- [Sicherheits-Best-Practices](/guides/security-best-practices.md)
|
||||
- [MDN Web Security](https://developer.mozilla.org/en-US/docs/Web/Security)
|
||||
- [OWASP Security Headers](https://owasp.org/www-project-secure-headers/)
|
||||
424
docs/components/validation/examples.md
Normal file
424
docs/components/validation/examples.md
Normal file
@@ -0,0 +1,424 @@
|
||||
# Validation Framework Beispiele
|
||||
|
||||
> **Dokumentationshinweis:** Diese Dokumentation ist vollständig aktualisiert und stellt die aktuelle Implementierung des Validation Frameworks korrekt dar.
|
||||
|
||||
## Übersicht
|
||||
|
||||
Diese Dokumentation enthält praktische Beispiele für die Verwendung des Validation Frameworks in verschiedenen Szenarien. Die Beispiele zeigen, wie Sie Validierungsregeln auf Objekteigenschaften anwenden, Validierungsergebnisse verarbeiten und das Framework in verschiedenen Kontexten integrieren können.
|
||||
|
||||
## Grundlegende Verwendung
|
||||
|
||||
### Einfache Objektvalidierung
|
||||
|
||||
```php
|
||||
use App\Framework\Validation\Rules\Required;
|
||||
use App\Framework\Validation\Rules\Email;
|
||||
use App\Framework\Validation\Rules\StringLength;
|
||||
|
||||
// 1. Definieren Sie eine Klasse mit Validierungsregeln
|
||||
class UserData
|
||||
{
|
||||
#[Required]
|
||||
#[StringLength(min: 3, max: 50)]
|
||||
public string $name;
|
||||
|
||||
#[Required]
|
||||
#[Email]
|
||||
public string $email;
|
||||
|
||||
#[StringLength(max: 500)]
|
||||
public ?string $bio = null;
|
||||
}
|
||||
|
||||
// 2. Erstellen Sie eine Instanz der Klasse
|
||||
$userData = new UserData();
|
||||
$userData->name = 'Max Mustermann';
|
||||
$userData->email = 'max@example.com';
|
||||
|
||||
// 3. Validieren Sie das Objekt
|
||||
$validator = $container->get(Validator::class);
|
||||
$result = $validator->validate($userData);
|
||||
|
||||
// 4. Überprüfen Sie das Ergebnis
|
||||
if ($result->hasErrors()) {
|
||||
// Fehlerbehandlung
|
||||
$errors = $result->getAllErrorMessages();
|
||||
foreach ($errors as $error) {
|
||||
echo $error . "\n";
|
||||
}
|
||||
} else {
|
||||
// Erfolgreiche Validierung
|
||||
echo "Validierung erfolgreich!";
|
||||
}
|
||||
```
|
||||
|
||||
### Validierung mit benutzerdefinierten Fehlermeldungen
|
||||
|
||||
```php
|
||||
class ProductData
|
||||
{
|
||||
#[Required(message: "Der Produktname ist erforderlich.")]
|
||||
#[StringLength(min: 3, max: 100, message: "Der Produktname muss zwischen 3 und 100 Zeichen lang sein.")]
|
||||
public string $name;
|
||||
|
||||
#[Required(message: "Der Preis ist erforderlich.")]
|
||||
#[Range(min: 0, message: "Der Preis muss größer oder gleich 0 sein.")]
|
||||
public float $price;
|
||||
|
||||
#[Required(message: "Die Kategorie ist erforderlich.")]
|
||||
#[In(values: ['electronics', 'books', 'clothing'], message: "Ungültige Kategorie.")]
|
||||
public string $category;
|
||||
}
|
||||
```
|
||||
|
||||
### Zugriff auf feldspezifische Fehler
|
||||
|
||||
```php
|
||||
$result = $validator->validate($userData);
|
||||
|
||||
if ($result->hasErrors()) {
|
||||
// Alle Fehler für ein bestimmtes Feld abrufen
|
||||
$emailErrors = $result->getFieldErrors('email');
|
||||
foreach ($emailErrors as $error) {
|
||||
echo "Fehler im Feld 'email': $error\n";
|
||||
}
|
||||
|
||||
// Alle Fehler nach Feld gruppiert abrufen
|
||||
$allErrors = $result->getAll();
|
||||
foreach ($allErrors as $field => $errors) {
|
||||
echo "Fehler im Feld '$field':\n";
|
||||
foreach ($errors as $error) {
|
||||
echo "- $error\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Fortgeschrittene Verwendung
|
||||
|
||||
### Validierungsgruppen
|
||||
|
||||
Validierungsgruppen ermöglichen es, verschiedene Validierungsszenarien für dasselbe Objekt zu definieren:
|
||||
|
||||
```php
|
||||
use App\Framework\Validation\Rules\Required;
|
||||
use App\Framework\Validation\Rules\Email;
|
||||
use App\Framework\Validation\Rules\StringLength;
|
||||
|
||||
class UserProfile
|
||||
{
|
||||
#[Required(groups: ['registration', 'profile'])]
|
||||
#[StringLength(min: 3, max: 50)]
|
||||
public string $name;
|
||||
|
||||
#[Required(groups: ['registration'])]
|
||||
#[Email]
|
||||
public string $email;
|
||||
|
||||
#[Required(groups: ['profile'])]
|
||||
#[StringLength(max: 500)]
|
||||
public ?string $bio = null;
|
||||
|
||||
#[Required(groups: ['profile'])]
|
||||
#[Url]
|
||||
public ?string $website = null;
|
||||
}
|
||||
|
||||
// Validierung mit einer bestimmten Gruppe
|
||||
$result = $validator->validate($userProfile, 'registration');
|
||||
|
||||
// In diesem Fall werden nur die Regeln angewendet, die zur Gruppe 'registration' gehören
|
||||
// oder keine Gruppe spezifiziert haben
|
||||
```
|
||||
|
||||
### Verschachtelte Validierung
|
||||
|
||||
Validierung von Objekten, die andere validierbare Objekte enthalten:
|
||||
|
||||
```php
|
||||
use App\Framework\Validation\Rules\Required;
|
||||
use App\Framework\Validation\Rules\Email;
|
||||
|
||||
class Address
|
||||
{
|
||||
#[Required]
|
||||
#[StringLength(min: 3, max: 100)]
|
||||
public string $street;
|
||||
|
||||
#[Required]
|
||||
#[StringLength(min: 3, max: 50)]
|
||||
public string $city;
|
||||
|
||||
#[Required]
|
||||
#[StringLength(min: 5, max: 10)]
|
||||
public string $zipCode;
|
||||
}
|
||||
|
||||
class Customer
|
||||
{
|
||||
#[Required]
|
||||
#[StringLength(min: 3, max: 50)]
|
||||
public string $name;
|
||||
|
||||
#[Required]
|
||||
#[Email]
|
||||
public string $email;
|
||||
|
||||
#[Required]
|
||||
public Address $address;
|
||||
}
|
||||
|
||||
// Validierung eines Objekts mit verschachtelten Objekten
|
||||
$address = new Address();
|
||||
$address->street = 'Musterstraße 123';
|
||||
$address->city = 'Berlin';
|
||||
$address->zipCode = '10115';
|
||||
|
||||
$customer = new Customer();
|
||||
$customer->name = 'Max Mustermann';
|
||||
$customer->email = 'max@example.com';
|
||||
$customer->address = $address;
|
||||
|
||||
// Validierung des Hauptobjekts
|
||||
$result = $validator->validate($customer);
|
||||
|
||||
// Manuelle Validierung des verschachtelten Objekts
|
||||
$addressResult = $validator->validate($customer->address);
|
||||
$result->merge($addressResult); // Kombinieren der Ergebnisse
|
||||
```
|
||||
|
||||
### Benutzerdefinierte Validierungslogik
|
||||
|
||||
Verwendung der `Custom`-Regel für komplexe Validierungsanforderungen:
|
||||
|
||||
```php
|
||||
use App\Framework\Validation\Rules\Custom;
|
||||
use App\Framework\Validation\Rules\Required;
|
||||
|
||||
class PasswordReset
|
||||
{
|
||||
#[Required]
|
||||
public string $email;
|
||||
|
||||
#[Required]
|
||||
public string $password;
|
||||
|
||||
#[Required]
|
||||
#[Custom(callback: 'validatePasswordConfirmation', message: "Die Passwörter stimmen nicht überein.")]
|
||||
public string $passwordConfirmation;
|
||||
|
||||
private function validatePasswordConfirmation($value): bool
|
||||
{
|
||||
return $value === $this->password;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Integration mit Formularen
|
||||
|
||||
### Formularvalidierung
|
||||
|
||||
Verwendung des Validation Frameworks zur Validierung von Formulardaten:
|
||||
|
||||
```php
|
||||
use App\Framework\Validation\ValidationFormHandler;
|
||||
use App\Framework\Http\Response;
|
||||
|
||||
class UserController
|
||||
{
|
||||
public function handleRegistration(
|
||||
Request $request,
|
||||
ValidationFormHandler $formHandler,
|
||||
UserService $userService
|
||||
): Response {
|
||||
// Formulardaten abrufen
|
||||
$formData = $request->getFormData();
|
||||
|
||||
// Validierung durchführen
|
||||
$result = $formHandler->validate(UserRegistration::class, $formData);
|
||||
|
||||
if ($result->hasErrors()) {
|
||||
// Formular mit Fehlern neu rendern
|
||||
return $this->renderForm('registration', [
|
||||
'errors' => $result->getAll(),
|
||||
'data' => $formData
|
||||
]);
|
||||
}
|
||||
|
||||
// Erfolgreiche Validierung, Benutzer registrieren
|
||||
$userService->registerUser($formData);
|
||||
|
||||
// Weiterleitung zur Erfolgsseite
|
||||
return $this->redirect('/registration/success');
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Formularvorlage mit Fehleranzeige
|
||||
|
||||
```html
|
||||
<form method="post" action="/register">
|
||||
<div class="form-group">
|
||||
<label for="name">Name:</label>
|
||||
<input type="text" id="name" name="name" value="<?= htmlspecialchars($data['name'] ?? '') ?>">
|
||||
<?php if (isset($errors['name'])): ?>
|
||||
<div class="error">
|
||||
<?= htmlspecialchars($errors['name'][0]) ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="email">E-Mail:</label>
|
||||
<input type="email" id="email" name="email" value="<?= htmlspecialchars($data['email'] ?? '') ?>">
|
||||
<?php if (isset($errors['email'])): ?>
|
||||
<div class="error">
|
||||
<?= htmlspecialchars($errors['email'][0]) ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="password">Passwort:</label>
|
||||
<input type="password" id="password" name="password">
|
||||
<?php if (isset($errors['password'])): ?>
|
||||
<div class="error">
|
||||
<?= htmlspecialchars($errors['password'][0]) ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<button type="submit">Registrieren</button>
|
||||
</form>
|
||||
```
|
||||
|
||||
## API-Validierung
|
||||
|
||||
### Middleware für API-Validierung
|
||||
|
||||
Das Framework bietet eine `InputValidationMiddleware` für die automatische Validierung von API-Anfragen:
|
||||
|
||||
```php
|
||||
// In der Bootstrap-Datei oder Router-Konfiguration
|
||||
$app->addMiddleware(InputValidationMiddleware::class);
|
||||
```
|
||||
|
||||
Konfiguration in `config/validation.php`:
|
||||
|
||||
```php
|
||||
return [
|
||||
'routes' => [
|
||||
'/api/users' => [
|
||||
'POST' => UserCreateData::class,
|
||||
'PUT' => UserUpdateData::class
|
||||
],
|
||||
'/api/products' => [
|
||||
'POST' => ProductCreateData::class,
|
||||
'PUT' => ProductUpdateData::class
|
||||
]
|
||||
]
|
||||
];
|
||||
```
|
||||
|
||||
### API-Controller mit Validierung
|
||||
|
||||
```php
|
||||
class ApiController
|
||||
{
|
||||
public function createUser(Request $request): Response
|
||||
{
|
||||
// Die Validierung wurde bereits durch die Middleware durchgeführt
|
||||
// Wenn wir hier sind, waren die Daten gültig
|
||||
|
||||
$userData = $request->getJsonData();
|
||||
$user = $this->userService->createUser($userData);
|
||||
|
||||
return new Response(201, [], [
|
||||
'success' => true,
|
||||
'user' => $user->toArray()
|
||||
]);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Fehlerbehandlung für API-Validierung
|
||||
|
||||
Die `InputValidationMiddleware` gibt automatisch eine Fehlerantwort zurück, wenn die Validierung fehlschlägt:
|
||||
|
||||
```json
|
||||
{
|
||||
"error": "Validation failed",
|
||||
"validation_errors": {
|
||||
"name": ["Der Name ist erforderlich."],
|
||||
"email": ["Bitte geben Sie eine gültige E-Mail-Adresse ein."]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Leistungsoptimierung
|
||||
|
||||
### Validierungs-Cache
|
||||
|
||||
Für häufig validierte Objekte kann ein Cache verwendet werden, um die Leistung zu verbessern:
|
||||
|
||||
```php
|
||||
use App\Framework\Validation\ValidationCacheDecorator;
|
||||
|
||||
// Cache-Decorator erstellen
|
||||
$cachedValidator = new ValidationCacheDecorator($validator, $cache);
|
||||
|
||||
// Validierung mit Cache durchführen
|
||||
$result = $cachedValidator->validate($userData);
|
||||
```
|
||||
|
||||
### Validierungsgruppen für partielle Validierung
|
||||
|
||||
Verwenden Sie Validierungsgruppen, um nur die relevanten Teile eines Objekts zu validieren:
|
||||
|
||||
```php
|
||||
// Nur die für die Aktualisierung relevanten Felder validieren
|
||||
$result = $validator->validate($userData, 'update');
|
||||
```
|
||||
|
||||
## Fehlerbehebung
|
||||
|
||||
### Häufige Probleme und Lösungen
|
||||
|
||||
#### Validierungsregeln werden nicht angewendet
|
||||
|
||||
Stellen Sie sicher, dass:
|
||||
- Die Attribute korrekt definiert sind
|
||||
- Die Eigenschaften für den Validator zugänglich sind (public oder mit Reflection)
|
||||
- Der Validator korrekt initialisiert wurde
|
||||
|
||||
```php
|
||||
// Korrekte Initialisierung des Validators
|
||||
$validator = new Validator($reflectionProvider);
|
||||
```
|
||||
|
||||
#### Falsche Fehlermeldungen
|
||||
|
||||
Überprüfen Sie die Reihenfolge der Validierungsregeln:
|
||||
|
||||
```php
|
||||
// Korrekte Reihenfolge: Required vor anderen Regeln
|
||||
#[Required]
|
||||
#[Email]
|
||||
public string $email;
|
||||
```
|
||||
|
||||
#### Leistungsprobleme
|
||||
|
||||
Verwenden Sie den ValidationCacheDecorator und vermeiden Sie zu komplexe Validierungsregeln:
|
||||
|
||||
```php
|
||||
// Einfache Regeln verwenden
|
||||
#[StringLength(max: 255)] // Besser als komplexe reguläre Ausdrücke
|
||||
public string $name;
|
||||
```
|
||||
|
||||
## Weiterführende Informationen
|
||||
|
||||
- [Validation Framework Übersicht](index.md)
|
||||
- [Verfügbare Validierungsregeln](rules.md)
|
||||
- [API-Validierung](/guides/api-validation.md)
|
||||
338
docs/components/validation/index.md
Normal file
338
docs/components/validation/index.md
Normal file
@@ -0,0 +1,338 @@
|
||||
# Validation Framework
|
||||
|
||||
> **Dokumentationshinweis:** Diese Dokumentation ist vollständig aktualisiert und stellt die aktuelle Implementierung des Validation Frameworks korrekt dar.
|
||||
|
||||
## Übersicht
|
||||
|
||||
Das Validation Framework ist ein leistungsstarkes System zur Validierung von Objekten und Daten in der Anwendung. Es nutzt moderne PHP-Attribute, um Validierungsregeln direkt an Objekteigenschaften zu definieren, und bietet eine flexible, erweiterbare Architektur für verschiedene Validierungsanforderungen.
|
||||
|
||||
## Hauptkomponenten
|
||||
|
||||
### Validator-Klasse
|
||||
|
||||
Die zentrale Klasse für die Validierungsfunktionalität:
|
||||
|
||||
```php
|
||||
// Klasse initialisieren
|
||||
$validator = new Validator($reflectionProvider);
|
||||
|
||||
// Objekt validieren
|
||||
$result = $validator->validate($object);
|
||||
|
||||
// Validierungsergebnis prüfen
|
||||
if ($result->hasErrors()) {
|
||||
// Fehlerbehandlung
|
||||
$errors = $result->getAllErrorMessages();
|
||||
}
|
||||
```
|
||||
|
||||
### ValidationRule-Interface
|
||||
|
||||
Das Basis-Interface für alle Validierungsregeln:
|
||||
|
||||
```php
|
||||
interface ValidationRule
|
||||
{
|
||||
public function validate(mixed $value): bool;
|
||||
public function getErrorMessages(): array;
|
||||
}
|
||||
```
|
||||
|
||||
Alle Validierungsregeln implementieren dieses Interface und werden als PHP-Attribute verwendet.
|
||||
|
||||
### ValidationResult-Klasse
|
||||
|
||||
Speichert und verwaltet Validierungsergebnisse:
|
||||
|
||||
- Sammelt Fehlermeldungen für verschiedene Felder
|
||||
- Bietet Methoden zum Abfragen von Fehlern
|
||||
- Ermöglicht das Zusammenführen mehrerer Validierungsergebnisse
|
||||
|
||||
```php
|
||||
// Fehler prüfen
|
||||
if ($result->hasErrors()) {
|
||||
// Alle Fehlermeldungen abrufen
|
||||
$allErrors = $result->getAllErrorMessages();
|
||||
|
||||
// Fehler für ein bestimmtes Feld abrufen
|
||||
$fieldErrors = $result->getFieldErrors('email');
|
||||
}
|
||||
```
|
||||
|
||||
## Validierungsregeln
|
||||
|
||||
Das Framework bietet eine Vielzahl vordefinierter Validierungsregeln:
|
||||
|
||||
### Grundlegende Regeln
|
||||
|
||||
- **Required**: Stellt sicher, dass ein Wert vorhanden ist
|
||||
- **Email**: Validiert E-Mail-Adressen
|
||||
- **Url**: Validiert URLs
|
||||
- **Numeric**: Stellt sicher, dass ein Wert numerisch ist
|
||||
- **StringLength**: Validiert die Länge eines Strings
|
||||
|
||||
### Erweiterte Regeln
|
||||
|
||||
- **Pattern**: Validiert Werte gegen reguläre Ausdrücke
|
||||
- **Range**: Validiert numerische Werte innerhalb eines Bereichs
|
||||
- **In**: Prüft, ob ein Wert in einer Liste gültiger Werte enthalten ist
|
||||
- **DateFormat**: Validiert Datumsformate
|
||||
- **Phone**: Validiert Telefonnummern
|
||||
- **Ulid**: Validiert ULID-Werte
|
||||
- **IsTrue**: Prüft, ob ein Wert true ist
|
||||
|
||||
### Benutzerdefinierte Regeln
|
||||
|
||||
Eigene Validierungsregeln können durch Implementierung des ValidationRule-Interfaces erstellt werden:
|
||||
|
||||
```php
|
||||
#[Attribute(Attribute::TARGET_PROPERTY)]
|
||||
final readonly class CustomRule implements ValidationRule
|
||||
{
|
||||
public function __construct(
|
||||
private string $param,
|
||||
private ?string $message = null
|
||||
) {
|
||||
}
|
||||
|
||||
public function validate(mixed $value): bool
|
||||
{
|
||||
// Validierungslogik implementieren
|
||||
return true; // oder false
|
||||
}
|
||||
|
||||
public function getErrorMessages(): array
|
||||
{
|
||||
return [$this->message ?? 'Standardfehlermeldung'];
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Verwendung
|
||||
|
||||
### Objekte mit Attributen validieren
|
||||
|
||||
```php
|
||||
class UserData
|
||||
{
|
||||
#[Required]
|
||||
#[StringLength(min: 3, max: 50)]
|
||||
public string $name;
|
||||
|
||||
#[Required]
|
||||
#[Email]
|
||||
public string $email;
|
||||
|
||||
#[StringLength(max: 500)]
|
||||
public ?string $bio = null;
|
||||
|
||||
#[Range(min: 18)]
|
||||
public ?int $age = null;
|
||||
}
|
||||
|
||||
// Validierung durchführen
|
||||
$userData = new UserData();
|
||||
$userData->name = 'Max';
|
||||
$userData->email = 'invalid-email';
|
||||
|
||||
$result = $validator->validate($userData);
|
||||
```
|
||||
|
||||
### Validierungsgruppen
|
||||
|
||||
Das Framework unterstützt Validierungsgruppen, um verschiedene Validierungsszenarien zu ermöglichen:
|
||||
|
||||
```php
|
||||
class UserData
|
||||
{
|
||||
#[Required(groups: ['registration', 'profile'])]
|
||||
#[StringLength(min: 3, max: 50)]
|
||||
public string $name;
|
||||
|
||||
#[Required(groups: ['registration'])]
|
||||
#[Email]
|
||||
public string $email;
|
||||
|
||||
#[Required(groups: ['profile'])]
|
||||
#[StringLength(max: 500)]
|
||||
public ?string $bio = null;
|
||||
}
|
||||
|
||||
// Validierung mit Gruppe durchführen
|
||||
$result = $validator->validate($userData, 'registration');
|
||||
```
|
||||
|
||||
### Fehlerbehandlung
|
||||
|
||||
```php
|
||||
if ($result->hasErrors()) {
|
||||
foreach ($result->getAll() as $field => $errors) {
|
||||
echo "Fehler im Feld '$field':\n";
|
||||
foreach ($errors as $error) {
|
||||
echo "- $error\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Integration
|
||||
|
||||
### Formular-Validierung
|
||||
|
||||
Das Framework bietet eine `ValidationFormHandler`-Klasse für die Validierung von Formulardaten:
|
||||
|
||||
```php
|
||||
// In einem Controller
|
||||
public function handleForm(Request $request, ValidationFormHandler $formHandler): Response
|
||||
{
|
||||
$formData = $request->getFormData();
|
||||
|
||||
$result = $formHandler->validate(UserData::class, $formData);
|
||||
|
||||
if ($result->hasErrors()) {
|
||||
return $this->renderForm('user_form', [
|
||||
'errors' => $result->getAll(),
|
||||
'data' => $formData
|
||||
]);
|
||||
}
|
||||
|
||||
// Erfolgreiche Validierung, Daten verarbeiten
|
||||
$this->userService->createUser($formData);
|
||||
|
||||
return $this->redirect('/success');
|
||||
}
|
||||
```
|
||||
|
||||
### Middleware-Integration
|
||||
|
||||
Das Framework bietet eine `InputValidationMiddleware` für die automatische Validierung von API-Anfragen:
|
||||
|
||||
```php
|
||||
// In der Bootstrap-Datei oder Router-Konfiguration
|
||||
$app->addMiddleware(InputValidationMiddleware::class);
|
||||
```
|
||||
|
||||
Konfiguration in `config/validation.php`:
|
||||
|
||||
```php
|
||||
return [
|
||||
'routes' => [
|
||||
'/api/users' => [
|
||||
'POST' => UserData::class,
|
||||
'PUT' => UserUpdateData::class
|
||||
]
|
||||
]
|
||||
];
|
||||
```
|
||||
|
||||
## Erweiterbarkeit
|
||||
|
||||
### Eigene Validierungsregeln erstellen
|
||||
|
||||
1. Interface implementieren:
|
||||
|
||||
```php
|
||||
#[Attribute(Attribute::TARGET_PROPERTY)]
|
||||
final readonly class Password implements ValidationRule
|
||||
{
|
||||
public function __construct(
|
||||
private int $minLength = 8,
|
||||
private bool $requireSpecialChars = true,
|
||||
private ?string $message = null
|
||||
) {
|
||||
}
|
||||
|
||||
public function validate(mixed $value): bool
|
||||
{
|
||||
if ($value === null || $value === '') {
|
||||
return true; // Leere Werte werden von Required-Regel behandelt
|
||||
}
|
||||
|
||||
if (!is_string($value) || strlen($value) < $this->minLength) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->requireSpecialChars && !preg_match('/[^a-zA-Z0-9]/', $value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getErrorMessages(): array
|
||||
{
|
||||
return [$this->message ?? "Das Passwort muss mindestens {$this->minLength} Zeichen lang sein und Sonderzeichen enthalten."];
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
2. Verwendung:
|
||||
|
||||
```php
|
||||
class UserRegistration
|
||||
{
|
||||
#[Required]
|
||||
#[Email]
|
||||
public string $email;
|
||||
|
||||
#[Required]
|
||||
#[Password(minLength: 10)]
|
||||
public string $password;
|
||||
}
|
||||
```
|
||||
|
||||
### Validierungsgruppen implementieren
|
||||
|
||||
Für Regeln, die Validierungsgruppen unterstützen sollen, implementieren Sie das `GroupAware`-Interface:
|
||||
|
||||
```php
|
||||
#[Attribute(Attribute::TARGET_PROPERTY)]
|
||||
final readonly class Required implements ValidationRule, GroupAware
|
||||
{
|
||||
/**
|
||||
* @param array<string> $groups Validierungsgruppen
|
||||
*/
|
||||
public function __construct(
|
||||
private ?string $message = null,
|
||||
private array $groups = []
|
||||
) {
|
||||
}
|
||||
|
||||
public function validate(mixed $value): bool
|
||||
{
|
||||
return $value !== null && $value !== '';
|
||||
}
|
||||
|
||||
public function getErrorMessages(): array
|
||||
{
|
||||
return [$this->message ?? 'Dieser Wert ist erforderlich.'];
|
||||
}
|
||||
|
||||
public function belongsToGroup(string $group): bool
|
||||
{
|
||||
return empty($this->groups) || in_array($group, $this->groups);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Fehlerbehebung
|
||||
|
||||
### Häufige Probleme
|
||||
|
||||
1. **Validierungsregeln werden nicht angewendet**:
|
||||
- Stellen Sie sicher, dass die Attribute korrekt definiert sind
|
||||
- Überprüfen Sie, ob die Eigenschaften für den Validator zugänglich sind (public oder mit Reflection)
|
||||
|
||||
2. **Falsche Fehlermeldungen**:
|
||||
- Überprüfen Sie die Reihenfolge der Validierungsregeln
|
||||
- Stellen Sie sicher, dass die Regeln für den richtigen Datentyp verwendet werden
|
||||
|
||||
3. **Leistungsprobleme**:
|
||||
- Verwenden Sie ValidationCacheDecorator für häufig validierte Objekte
|
||||
- Vermeiden Sie zu komplexe Validierungsregeln
|
||||
|
||||
## Weiterführende Informationen
|
||||
|
||||
- [Verfügbare Validierungsregeln](rules.md)
|
||||
- [Formular-Integration](examples.md)
|
||||
- [API-Validierung](/guides/api-validation.md)
|
||||
451
docs/components/validation/rules.md
Normal file
451
docs/components/validation/rules.md
Normal file
@@ -0,0 +1,451 @@
|
||||
# Validierungsregeln
|
||||
|
||||
> **Dokumentationshinweis:** Diese Dokumentation ist vollständig aktualisiert und stellt die aktuelle Implementierung der Validierungsregeln korrekt dar.
|
||||
|
||||
## Übersicht
|
||||
|
||||
Das Validation Framework bietet eine umfangreiche Sammlung vordefinierter Validierungsregeln, die als PHP-Attribute verwendet werden können. Diese Regeln können auf Objekteigenschaften angewendet werden, um Daten zu validieren und sicherzustellen, dass sie den erwarteten Anforderungen entsprechen.
|
||||
|
||||
## Grundlegende Regeln
|
||||
|
||||
### Required
|
||||
|
||||
Die `Required`-Regel stellt sicher, dass ein Wert vorhanden ist und nicht leer ist:
|
||||
|
||||
```php
|
||||
use App\Framework\Validation\Rules\Required;
|
||||
|
||||
class UserData
|
||||
{
|
||||
#[Required]
|
||||
public string $username;
|
||||
|
||||
#[Required(message: "Die E-Mail-Adresse ist erforderlich.")]
|
||||
public string $email;
|
||||
}
|
||||
```
|
||||
|
||||
**Parameter:**
|
||||
- `message` (optional): Benutzerdefinierte Fehlermeldung
|
||||
- `groups` (optional): Array von Validierungsgruppen, zu denen diese Regel gehört
|
||||
|
||||
**Validierungslogik:**
|
||||
- Gibt `false` zurück, wenn der Wert `null` oder ein leerer String ist
|
||||
- Gibt `true` zurück für alle anderen Werte, einschließlich `0` und `false`
|
||||
|
||||
### Email
|
||||
|
||||
Die `Email`-Regel validiert, dass ein Wert eine gültige E-Mail-Adresse ist:
|
||||
|
||||
```php
|
||||
use App\Framework\Validation\Rules\Email;
|
||||
|
||||
class UserData
|
||||
{
|
||||
#[Email]
|
||||
public string $email;
|
||||
|
||||
#[Email(message: "Bitte geben Sie eine gültige E-Mail-Adresse ein.")]
|
||||
public string $alternativeEmail;
|
||||
}
|
||||
```
|
||||
|
||||
**Parameter:**
|
||||
- `message` (optional): Benutzerdefinierte Fehlermeldung
|
||||
|
||||
**Validierungslogik:**
|
||||
- Verwendet `filter_var()` mit `FILTER_VALIDATE_EMAIL` zur Validierung
|
||||
- Leere Werte werden als gültig betrachtet (verwenden Sie `Required` in Kombination, um leere Werte zu verhindern)
|
||||
|
||||
### StringLength
|
||||
|
||||
Die `StringLength`-Regel validiert die Länge eines Strings:
|
||||
|
||||
```php
|
||||
use App\Framework\Validation\Rules\StringLength;
|
||||
|
||||
class UserData
|
||||
{
|
||||
#[StringLength(min: 3, max: 50)]
|
||||
public string $username;
|
||||
|
||||
#[StringLength(min: 8, message: "Das Passwort muss mindestens 8 Zeichen lang sein.")]
|
||||
public string $password;
|
||||
|
||||
#[StringLength(max: 500)]
|
||||
public ?string $bio;
|
||||
}
|
||||
```
|
||||
|
||||
**Parameter:**
|
||||
- `min` (optional): Minimale Länge des Strings
|
||||
- `max` (optional): Maximale Länge des Strings
|
||||
- `message` (optional): Benutzerdefinierte Fehlermeldung
|
||||
|
||||
**Validierungslogik:**
|
||||
- Mindestens einer der Parameter `min` oder `max` muss gesetzt sein
|
||||
- Verwendet `mb_strlen()` für korrekte Behandlung von Multibyte-Zeichen
|
||||
- Leere Werte werden als gültig betrachtet (verwenden Sie `Required` in Kombination, um leere Werte zu verhindern)
|
||||
|
||||
### Url
|
||||
|
||||
Die `Url`-Regel validiert, dass ein Wert eine gültige URL ist:
|
||||
|
||||
```php
|
||||
use App\Framework\Validation\Rules\Url;
|
||||
|
||||
class UserData
|
||||
{
|
||||
#[Url]
|
||||
public string $website;
|
||||
|
||||
#[Url(message: "Bitte geben Sie eine gültige URL ein.")]
|
||||
public string $profileUrl;
|
||||
}
|
||||
```
|
||||
|
||||
**Parameter:**
|
||||
- `message` (optional): Benutzerdefinierte Fehlermeldung
|
||||
|
||||
**Validierungslogik:**
|
||||
- Verwendet `filter_var()` mit `FILTER_VALIDATE_URL` zur Validierung
|
||||
- Leere Werte werden als gültig betrachtet (verwenden Sie `Required` in Kombination, um leere Werte zu verhindern)
|
||||
|
||||
### Numeric
|
||||
|
||||
Die `Numeric`-Regel validiert, dass ein Wert numerisch ist:
|
||||
|
||||
```php
|
||||
use App\Framework\Validation\Rules\Numeric;
|
||||
|
||||
class ProductData
|
||||
{
|
||||
#[Numeric]
|
||||
public $price;
|
||||
|
||||
#[Numeric(message: "Die Menge muss eine Zahl sein.")]
|
||||
public $quantity;
|
||||
}
|
||||
```
|
||||
|
||||
**Parameter:**
|
||||
- `message` (optional): Benutzerdefinierte Fehlermeldung
|
||||
|
||||
**Validierungslogik:**
|
||||
- Verwendet `is_numeric()` zur Validierung
|
||||
- Leere Werte werden als gültig betrachtet (verwenden Sie `Required` in Kombination, um leere Werte zu verhindern)
|
||||
|
||||
## Erweiterte Regeln
|
||||
|
||||
### Pattern
|
||||
|
||||
Die `Pattern`-Regel validiert einen Wert gegen einen regulären Ausdruck:
|
||||
|
||||
```php
|
||||
use App\Framework\Validation\Rules\Pattern;
|
||||
|
||||
class UserData
|
||||
{
|
||||
#[Pattern(pattern: '/^[a-zA-Z0-9_]+$/', message: "Der Benutzername darf nur Buchstaben, Zahlen und Unterstriche enthalten.")]
|
||||
public string $username;
|
||||
}
|
||||
```
|
||||
|
||||
**Parameter:**
|
||||
- `pattern`: Regulärer Ausdruck für die Validierung
|
||||
- `message` (optional): Benutzerdefinierte Fehlermeldung
|
||||
|
||||
**Validierungslogik:**
|
||||
- Verwendet `preg_match()` zur Validierung gegen den angegebenen regulären Ausdruck
|
||||
- Leere Werte werden als gültig betrachtet (verwenden Sie `Required` in Kombination, um leere Werte zu verhindern)
|
||||
|
||||
### Range
|
||||
|
||||
Die `Range`-Regel validiert, dass ein numerischer Wert innerhalb eines bestimmten Bereichs liegt:
|
||||
|
||||
```php
|
||||
use App\Framework\Validation\Rules\Range;
|
||||
|
||||
class ProductData
|
||||
{
|
||||
#[Range(min: 0)]
|
||||
public float $price;
|
||||
|
||||
#[Range(min: 1, max: 100, message: "Die Menge muss zwischen 1 und 100 liegen.")]
|
||||
public int $quantity;
|
||||
}
|
||||
```
|
||||
|
||||
**Parameter:**
|
||||
- `min` (optional): Minimaler Wert
|
||||
- `max` (optional): Maximaler Wert
|
||||
- `message` (optional): Benutzerdefinierte Fehlermeldung
|
||||
|
||||
**Validierungslogik:**
|
||||
- Mindestens einer der Parameter `min` oder `max` muss gesetzt sein
|
||||
- Leere Werte werden als gültig betrachtet (verwenden Sie `Required` in Kombination, um leere Werte zu verhindern)
|
||||
|
||||
### In
|
||||
|
||||
Die `In`-Regel validiert, dass ein Wert in einer Liste gültiger Werte enthalten ist:
|
||||
|
||||
```php
|
||||
use App\Framework\Validation\Rules\In;
|
||||
|
||||
class ProductData
|
||||
{
|
||||
#[In(values: ['small', 'medium', 'large'])]
|
||||
public string $size;
|
||||
|
||||
#[In(values: [1, 2, 3, 4, 5], message: "Bitte wählen Sie eine gültige Kategorie.")]
|
||||
public int $category;
|
||||
}
|
||||
```
|
||||
|
||||
**Parameter:**
|
||||
- `values`: Array gültiger Werte
|
||||
- `message` (optional): Benutzerdefinierte Fehlermeldung
|
||||
|
||||
**Validierungslogik:**
|
||||
- Verwendet `in_array()` zur Validierung
|
||||
- Leere Werte werden als gültig betrachtet (verwenden Sie `Required` in Kombination, um leere Werte zu verhindern)
|
||||
|
||||
### DateFormat
|
||||
|
||||
Die `DateFormat`-Regel validiert, dass ein Wert ein gültiges Datum in einem bestimmten Format ist:
|
||||
|
||||
```php
|
||||
use App\Framework\Validation\Rules\DateFormat;
|
||||
|
||||
class EventData
|
||||
{
|
||||
#[DateFormat(format: 'Y-m-d')]
|
||||
public string $date;
|
||||
|
||||
#[DateFormat(format: 'Y-m-d H:i:s', message: "Bitte geben Sie ein gültiges Datum und eine gültige Uhrzeit ein.")]
|
||||
public string $datetime;
|
||||
}
|
||||
```
|
||||
|
||||
**Parameter:**
|
||||
- `format`: Das erwartete Datumsformat (PHP date()-Format)
|
||||
- `message` (optional): Benutzerdefinierte Fehlermeldung
|
||||
|
||||
**Validierungslogik:**
|
||||
- Verwendet `DateTime::createFromFormat()` zur Validierung
|
||||
- Leere Werte werden als gültig betrachtet (verwenden Sie `Required` in Kombination, um leere Werte zu verhindern)
|
||||
|
||||
### Phone
|
||||
|
||||
Die `Phone`-Regel validiert, dass ein Wert eine gültige Telefonnummer ist:
|
||||
|
||||
```php
|
||||
use App\Framework\Validation\Rules\Phone;
|
||||
|
||||
class ContactData
|
||||
{
|
||||
#[Phone]
|
||||
public string $phoneNumber;
|
||||
|
||||
#[Phone(message: "Bitte geben Sie eine gültige Telefonnummer ein.")]
|
||||
public string $mobileNumber;
|
||||
}
|
||||
```
|
||||
|
||||
**Parameter:**
|
||||
- `message` (optional): Benutzerdefinierte Fehlermeldung
|
||||
|
||||
**Validierungslogik:**
|
||||
- Validiert Telefonnummern mit einem flexiblen Muster, das verschiedene Formate unterstützt
|
||||
- Leere Werte werden als gültig betrachtet (verwenden Sie `Required` in Kombination, um leere Werte zu verhindern)
|
||||
|
||||
### Ulid
|
||||
|
||||
Die `Ulid`-Regel validiert, dass ein Wert ein gültiger ULID (Universally Unique Lexicographically Sortable Identifier) ist:
|
||||
|
||||
```php
|
||||
use App\Framework\Validation\Rules\Ulid;
|
||||
|
||||
class EntityData
|
||||
{
|
||||
#[Ulid]
|
||||
public string $id;
|
||||
|
||||
#[Ulid(message: "Die ID muss ein gültiger ULID sein.")]
|
||||
public string $parentId;
|
||||
}
|
||||
```
|
||||
|
||||
**Parameter:**
|
||||
- `message` (optional): Benutzerdefinierte Fehlermeldung
|
||||
|
||||
**Validierungslogik:**
|
||||
- Validiert, dass der Wert ein gültiger ULID ist (26 Zeichen, Base32-Kodierung)
|
||||
- Leere Werte werden als gültig betrachtet (verwenden Sie `Required` in Kombination, um leere Werte zu verhindern)
|
||||
|
||||
### IsTrue
|
||||
|
||||
Die `IsTrue`-Regel validiert, dass ein Wert `true` ist:
|
||||
|
||||
```php
|
||||
use App\Framework\Validation\Rules\IsTrue;
|
||||
|
||||
class RegistrationData
|
||||
{
|
||||
#[IsTrue(message: "Sie müssen die Nutzungsbedingungen akzeptieren.")]
|
||||
public bool $termsAccepted;
|
||||
}
|
||||
```
|
||||
|
||||
**Parameter:**
|
||||
- `message` (optional): Benutzerdefinierte Fehlermeldung
|
||||
|
||||
**Validierungslogik:**
|
||||
- Gibt `true` zurück, wenn der Wert `true` ist
|
||||
- Gibt `false` zurück für alle anderen Werte
|
||||
- Leere Werte werden als ungültig betrachtet
|
||||
|
||||
## Benutzerdefinierte Regeln
|
||||
|
||||
### Custom
|
||||
|
||||
Die `Custom`-Regel ermöglicht die Definition einer benutzerdefinierten Validierungslogik mit einer Callback-Funktion:
|
||||
|
||||
```php
|
||||
use App\Framework\Validation\Rules\Custom;
|
||||
|
||||
class UserData
|
||||
{
|
||||
#[Custom(callback: 'validateUsername', message: "Der Benutzername ist ungültig.")]
|
||||
public string $username;
|
||||
|
||||
private function validateUsername($value): bool
|
||||
{
|
||||
// Benutzerdefinierte Validierungslogik
|
||||
return preg_match('/^[a-zA-Z0-9_]{3,20}$/', $value) === 1;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Parameter:**
|
||||
- `callback`: Name der Callback-Methode oder Closure
|
||||
- `message` (optional): Benutzerdefinierte Fehlermeldung
|
||||
|
||||
**Validierungslogik:**
|
||||
- Ruft die angegebene Callback-Funktion auf und gibt deren Rückgabewert zurück
|
||||
- Leere Werte werden an die Callback-Funktion übergeben
|
||||
|
||||
## Eigene Validierungsregeln erstellen
|
||||
|
||||
Sie können eigene Validierungsregeln erstellen, indem Sie das `ValidationRule`-Interface implementieren:
|
||||
|
||||
```php
|
||||
use App\Framework\Validation\ValidationRule;
|
||||
use Attribute;
|
||||
|
||||
#[Attribute(Attribute::TARGET_PROPERTY)]
|
||||
final readonly class Password implements ValidationRule
|
||||
{
|
||||
public function __construct(
|
||||
private int $minLength = 8,
|
||||
private bool $requireSpecialChars = true,
|
||||
private ?string $message = null
|
||||
) {
|
||||
}
|
||||
|
||||
public function validate(mixed $value): bool
|
||||
{
|
||||
if ($value === null || $value === '') {
|
||||
return true; // Leere Werte werden von Required-Regel behandelt
|
||||
}
|
||||
|
||||
if (!is_string($value) || strlen($value) < $this->minLength) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->requireSpecialChars && !preg_match('/[^a-zA-Z0-9]/', $value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getErrorMessages(): array
|
||||
{
|
||||
return [$this->message ?? "Das Passwort muss mindestens {$this->minLength} Zeichen lang sein und Sonderzeichen enthalten."];
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Validierungsgruppen
|
||||
|
||||
Für Regeln, die Validierungsgruppen unterstützen sollen, implementieren Sie das `GroupAware`-Interface:
|
||||
|
||||
```php
|
||||
use App\Framework\Validation\ValidationRule;
|
||||
use App\Framework\Validation\GroupAware;
|
||||
use Attribute;
|
||||
|
||||
#[Attribute(Attribute::TARGET_PROPERTY)]
|
||||
final readonly class Required implements ValidationRule, GroupAware
|
||||
{
|
||||
/**
|
||||
* @param array<string> $groups Validierungsgruppen
|
||||
*/
|
||||
public function __construct(
|
||||
private ?string $message = null,
|
||||
private array $groups = []
|
||||
) {
|
||||
}
|
||||
|
||||
public function validate(mixed $value): bool
|
||||
{
|
||||
return $value !== null && $value !== '';
|
||||
}
|
||||
|
||||
public function getErrorMessages(): array
|
||||
{
|
||||
return [$this->message ?? 'Dieser Wert ist erforderlich.'];
|
||||
}
|
||||
|
||||
public function belongsToGroup(string $group): bool
|
||||
{
|
||||
return empty($this->groups) || in_array($group, $this->groups);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Kombinieren von Regeln
|
||||
|
||||
Validierungsregeln können kombiniert werden, um komplexe Validierungsanforderungen zu erfüllen:
|
||||
|
||||
```php
|
||||
class UserData
|
||||
{
|
||||
#[Required]
|
||||
#[StringLength(min: 3, max: 50)]
|
||||
#[Pattern(pattern: '/^[a-zA-Z0-9_]+$/')]
|
||||
public string $username;
|
||||
|
||||
#[Required]
|
||||
#[Email]
|
||||
public string $email;
|
||||
|
||||
#[Required]
|
||||
#[StringLength(min: 8)]
|
||||
#[Custom(callback: 'validatePassword')]
|
||||
public string $password;
|
||||
|
||||
private function validatePassword($value): bool
|
||||
{
|
||||
// Mindestens ein Großbuchstabe, ein Kleinbuchstabe, eine Zahl und ein Sonderzeichen
|
||||
return preg_match('/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[^a-zA-Z\d]).+$/', $value) === 1;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Weiterführende Informationen
|
||||
|
||||
- [Validation Framework Übersicht](index.md)
|
||||
- [Beispiele für die Verwendung von Validierungsregeln](examples.md)
|
||||
- [API-Validierung](/guides/api-validation.md)
|
||||
279
docs/components/waf/configuration.md
Normal file
279
docs/components/waf/configuration.md
Normal file
@@ -0,0 +1,279 @@
|
||||
# WAF Konfiguration
|
||||
|
||||
> **Dokumentationshinweis:** Diese Dokumentation ist vollständig aktualisiert und stellt die aktuelle Implementierung der WAF-Konfiguration korrekt dar.
|
||||
|
||||
## Übersicht
|
||||
|
||||
Die Web Application Firewall (WAF) bietet umfangreiche Konfigurationsmöglichkeiten, um das Sicherheitsniveau und die Leistung an die Anforderungen Ihrer Anwendung anzupassen. Diese Dokumentation beschreibt die verfügbaren Konfigurationsoptionen und wie Sie diese anwenden können.
|
||||
|
||||
## Konfigurationsklasse
|
||||
|
||||
Die zentrale Klasse für die WAF-Konfiguration ist `WafConfig`. Diese Klasse enthält alle Einstellungen, die das Verhalten der WAF steuern:
|
||||
|
||||
```php
|
||||
// Konfiguration erstellen
|
||||
$config = new WafConfig(
|
||||
$enabled,
|
||||
$defaultLayerConfig,
|
||||
$globalTimeout,
|
||||
$blockingThreshold,
|
||||
$alertThreshold,
|
||||
$parallelProcessing,
|
||||
$maxParallelLayers,
|
||||
$detailedLogging,
|
||||
$metricsEnabled,
|
||||
$enabledLayers,
|
||||
$layerConfigs,
|
||||
$customSettings
|
||||
);
|
||||
```
|
||||
|
||||
## Vordefinierte Konfigurationen
|
||||
|
||||
Die `WafConfig`-Klasse bietet mehrere vordefinierte Konfigurationen für verschiedene Umgebungen:
|
||||
|
||||
### Produktionsumgebung
|
||||
|
||||
```php
|
||||
// Produktionskonfiguration verwenden
|
||||
$config = WafConfig::production();
|
||||
```
|
||||
|
||||
Die Produktionskonfiguration aktiviert alle Sicherheitsfeatures mit strengen Einstellungen:
|
||||
- Alle Schutzebenen aktiviert
|
||||
- Niedrige Schwellenwerte für Blockierung
|
||||
- Parallele Verarbeitung für optimale Leistung
|
||||
- Detaillierte Metriken für Überwachung
|
||||
|
||||
### Entwicklungsumgebung
|
||||
|
||||
```php
|
||||
// Entwicklungskonfiguration verwenden
|
||||
$config = WafConfig::development();
|
||||
```
|
||||
|
||||
Die Entwicklungskonfiguration ist weniger streng und bietet:
|
||||
- Höhere Schwellenwerte für Blockierung
|
||||
- Detailliertes Logging für Debugging
|
||||
- Alle Schutzebenen aktiviert, aber mit weniger strengen Einstellungen
|
||||
|
||||
### Testumgebung
|
||||
|
||||
```php
|
||||
// Testkonfiguration verwenden
|
||||
$config = WafConfig::testing();
|
||||
```
|
||||
|
||||
Die Testkonfiguration ist für automatisierte Tests optimiert:
|
||||
- Reduzierte Timeouts
|
||||
- Deaktivierte parallele Verarbeitung
|
||||
- Minimales Logging
|
||||
|
||||
### Deaktivierte Konfiguration
|
||||
|
||||
```php
|
||||
// WAF deaktivieren
|
||||
$config = WafConfig::disabled();
|
||||
```
|
||||
|
||||
Diese Konfiguration deaktiviert die WAF vollständig, was für bestimmte Entwicklungs- oder Diagnoseszenarien nützlich sein kann.
|
||||
|
||||
## Hauptkonfigurationsoptionen
|
||||
|
||||
### Aktivierung/Deaktivierung
|
||||
|
||||
```php
|
||||
// WAF aktivieren
|
||||
$config = $config->enable();
|
||||
|
||||
// WAF deaktivieren
|
||||
$config = $config->disable();
|
||||
```
|
||||
|
||||
### Timeout-Einstellungen
|
||||
|
||||
```php
|
||||
// Globales Timeout für WAF-Verarbeitung setzen
|
||||
$config = $config->withGlobalTimeout(Duration::fromMilliseconds(100));
|
||||
```
|
||||
|
||||
### Schwellenwerte
|
||||
|
||||
```php
|
||||
// Schwellenwert für Blockierung setzen
|
||||
$config = $config->withBlockingThreshold(Percentage::fromFloat(0.75));
|
||||
```
|
||||
|
||||
Der Blockierungsschwellenwert bestimmt, ab welchem Konfidenzwert ein Request blockiert wird. Ein höherer Wert bedeutet weniger Blockierungen (und potenziell mehr falsch negative Ergebnisse), während ein niedrigerer Wert mehr Blockierungen (und potenziell mehr falsch positive Ergebnisse) bedeutet.
|
||||
|
||||
## Schutzebenen-Konfiguration
|
||||
|
||||
Die WAF verwendet ein mehrschichtiges Schutzsystem. Jede Schutzebene kann individuell konfiguriert werden:
|
||||
|
||||
```php
|
||||
// Schutzebene aktivieren mit benutzerdefinierter Konfiguration
|
||||
$config = $config->enableLayer(
|
||||
'statistical',
|
||||
new LayerConfig(
|
||||
$enabled = true,
|
||||
$weight = 0.6,
|
||||
$threshold = Percentage::fromFloat(0.7),
|
||||
$timeout = Duration::fromMilliseconds(50)
|
||||
)
|
||||
);
|
||||
|
||||
// Schutzebene deaktivieren
|
||||
$config = $config->disableLayer('clustering');
|
||||
```
|
||||
|
||||
### Verfügbare Schutzebenen
|
||||
|
||||
- **statistical**: Statistische Anomalieerkennung
|
||||
- **clustering**: Clustering-basierte Anomalieerkennung
|
||||
- **signature**: Signaturbasierte Erkennung bekannter Angriffsmuster
|
||||
- **behavioral**: Verhaltensbasierte Anomalieerkennung
|
||||
- **ratelimiting**: Ratenbegrenzung für Anfragen
|
||||
|
||||
## Benutzerdefinierte Einstellungen
|
||||
|
||||
Die WAF unterstützt benutzerdefinierte Einstellungen für spezielle Anwendungsfälle:
|
||||
|
||||
```php
|
||||
// Benutzerdefinierte Einstellung hinzufügen
|
||||
$config = $config->withCustomSetting('whitelist_ips', ['127.0.0.1', '192.168.1.1']);
|
||||
|
||||
// Benutzerdefinierte Einstellung abrufen
|
||||
$whitelistIps = $config->getCustomSetting('whitelist_ips', []);
|
||||
```
|
||||
|
||||
## Konfigurationsvalidierung
|
||||
|
||||
Die WAF-Konfiguration kann validiert werden, um sicherzustellen, dass alle Einstellungen gültig sind:
|
||||
|
||||
```php
|
||||
// Konfiguration validieren
|
||||
$validationErrors = $config->validate();
|
||||
|
||||
// Prüfen, ob die Konfiguration gültig ist
|
||||
if (!$config->isValid()) {
|
||||
// Fehlerbehandlung
|
||||
}
|
||||
```
|
||||
|
||||
## Konfiguration in der Anwendung
|
||||
|
||||
### Konfigurationsdatei
|
||||
|
||||
Die WAF-Konfiguration kann in einer dedizierten Konfigurationsdatei definiert werden:
|
||||
|
||||
```php
|
||||
// config/waf.php
|
||||
return [
|
||||
'enabled' => true,
|
||||
'learning_mode' => false,
|
||||
'threshold' => 0.75,
|
||||
'detectors' => [
|
||||
'statistical' => true,
|
||||
'clustering' => true
|
||||
],
|
||||
'whitelist' => [
|
||||
'ips' => ['127.0.0.1'],
|
||||
'paths' => ['/api/health']
|
||||
]
|
||||
];
|
||||
```
|
||||
|
||||
### Laufzeitkonfiguration
|
||||
|
||||
Die WAF-Konfiguration kann zur Laufzeit aktualisiert werden:
|
||||
|
||||
```php
|
||||
// WAF-Konfiguration aktualisieren
|
||||
$waf->updateConfig($newConfig);
|
||||
```
|
||||
|
||||
## Leistungsoptimierung
|
||||
|
||||
### Parallele Verarbeitung
|
||||
|
||||
Die parallele Verarbeitung kann die Leistung der WAF verbessern, insbesondere bei mehreren aktivierten Schutzebenen:
|
||||
|
||||
```php
|
||||
// Parallele Verarbeitung aktivieren
|
||||
$config = new WafConfig(
|
||||
$enabled = true,
|
||||
$defaultLayerConfig,
|
||||
$globalTimeout,
|
||||
$blockingThreshold,
|
||||
$alertThreshold,
|
||||
$parallelProcessing = true,
|
||||
$maxParallelLayers = 4,
|
||||
$detailedLogging,
|
||||
$metricsEnabled,
|
||||
$enabledLayers,
|
||||
$layerConfigs,
|
||||
$customSettings
|
||||
);
|
||||
```
|
||||
|
||||
### Caching
|
||||
|
||||
Die WAF unterstützt Caching für wiederholte Anfragen, um die Leistung zu verbessern:
|
||||
|
||||
```php
|
||||
// Benutzerdefinierte Caching-Einstellungen
|
||||
$config = $config->withCustomSetting('cache_ttl', 300); // 5 Minuten
|
||||
$config = $config->withCustomSetting('cache_size', 1000); // 1000 Einträge
|
||||
```
|
||||
|
||||
## Fehlerbehebung
|
||||
|
||||
### Logging
|
||||
|
||||
Detailliertes Logging kann für die Fehlerbehebung aktiviert werden:
|
||||
|
||||
```php
|
||||
// Detailliertes Logging aktivieren
|
||||
$config = new WafConfig(
|
||||
$enabled = true,
|
||||
$defaultLayerConfig,
|
||||
$globalTimeout,
|
||||
$blockingThreshold,
|
||||
$alertThreshold,
|
||||
$parallelProcessing,
|
||||
$maxParallelLayers,
|
||||
$detailedLogging = true,
|
||||
$metricsEnabled,
|
||||
$enabledLayers,
|
||||
$layerConfigs,
|
||||
$customSettings
|
||||
);
|
||||
```
|
||||
|
||||
### Metriken
|
||||
|
||||
Die WAF kann Metriken zur Leistung und Effektivität sammeln:
|
||||
|
||||
```php
|
||||
// Metriken aktivieren
|
||||
$config = new WafConfig(
|
||||
$enabled = true,
|
||||
$defaultLayerConfig,
|
||||
$globalTimeout,
|
||||
$blockingThreshold,
|
||||
$alertThreshold,
|
||||
$parallelProcessing,
|
||||
$maxParallelLayers,
|
||||
$detailedLogging,
|
||||
$metricsEnabled = true,
|
||||
$enabledLayers,
|
||||
$layerConfigs,
|
||||
$customSettings
|
||||
);
|
||||
```
|
||||
|
||||
## Weiterführende Informationen
|
||||
|
||||
- [WAF-Übersicht](index.md)
|
||||
- [Machine Learning Dokumentation](machine-learning.md)
|
||||
- [Sicherheitsrichtlinien](/guides/security.md)
|
||||
280
docs/components/waf/feedback.md
Normal file
280
docs/components/waf/feedback.md
Normal file
@@ -0,0 +1,280 @@
|
||||
# WAF Feedback System
|
||||
|
||||
The Web Application Firewall (WAF) Feedback System allows users to provide feedback on WAF detections, helping to improve the accuracy of the security system over time. This document explains how to use the feedback system, how it works, and how to integrate it into your application.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
1. [User Guide](#user-guide)
|
||||
- [Submitting Feedback](#submitting-feedback)
|
||||
- [Types of Feedback](#types-of-feedback)
|
||||
- [Feedback Dashboard](#feedback-dashboard)
|
||||
2. [Technical Documentation](#technical-documentation)
|
||||
- [Architecture](#architecture)
|
||||
- [Components](#components)
|
||||
- [Integration](#integration)
|
||||
3. [Learning Algorithms](#learning-algorithms)
|
||||
- [False Positive Reduction](#false-positive-reduction)
|
||||
- [False Negative Reduction](#false-negative-reduction)
|
||||
- [Severity Adjustment](#severity-adjustment)
|
||||
4. [Examples](#examples)
|
||||
- [API Examples](#api-examples)
|
||||
- [Dashboard Examples](#dashboard-examples)
|
||||
- [Learning Examples](#learning-examples)
|
||||
|
||||
## User Guide
|
||||
|
||||
### Submitting Feedback
|
||||
|
||||
When the WAF detects a potential security threat, it may block the request or display a warning. Users with appropriate permissions can provide feedback on these detections to help improve the system's accuracy.
|
||||
|
||||
To submit feedback:
|
||||
|
||||
1. Navigate to the security event in the admin dashboard
|
||||
2. Click on the "Provide Feedback" button
|
||||
3. Select the type of feedback (false positive, false negative, correct detection, or severity adjustment)
|
||||
4. Add an optional comment explaining your feedback
|
||||
5. Click "Submit"
|
||||
|
||||
Alternatively, you can use the API to submit feedback programmatically (see [API Examples](#api-examples)).
|
||||
|
||||
### Types of Feedback
|
||||
|
||||
The WAF Feedback System supports four types of feedback:
|
||||
|
||||
1. **False Positive**: The WAF incorrectly identified a legitimate request as a security threat. This feedback helps reduce false alarms.
|
||||
|
||||
2. **False Negative**: The WAF failed to detect a security threat. This feedback helps improve detection coverage.
|
||||
|
||||
3. **Correct Detection**: The WAF correctly identified a security threat. This feedback confirms the accuracy of the detection.
|
||||
|
||||
4. **Severity Adjustment**: The WAF correctly identified a security threat, but the severity level was incorrect. This feedback helps calibrate the severity assessment.
|
||||
|
||||
### Feedback Dashboard
|
||||
|
||||
The Feedback Dashboard provides an overview of all feedback submitted to the WAF system, along with analytics and trends. To access the dashboard:
|
||||
|
||||
1. Navigate to Admin > Security > WAF > Feedback
|
||||
2. Use the filters to view specific types of feedback, categories, or time periods
|
||||
3. View charts and graphs showing feedback trends and detection accuracy
|
||||
4. Access detailed reports for specific detection categories
|
||||
|
||||
## Technical Documentation
|
||||
|
||||
### Architecture
|
||||
|
||||
The WAF Feedback System consists of several components that work together to collect, store, analyze, and learn from user feedback:
|
||||
|
||||
```
|
||||
+-------------------+ +-------------------+ +-------------------+
|
||||
| | | | | |
|
||||
| Feedback API | --> | Feedback Service | --> | Feedback Storage |
|
||||
| | | | | |
|
||||
+-------------------+ +-------------------+ +-------------------+
|
||||
|
|
||||
v
|
||||
+-------------------+ +-------------------+ +-------------------+
|
||||
| | | | | |
|
||||
| Learning Service | <-- | Analytics | --> | Reporting |
|
||||
| | | | | |
|
||||
+-------------------+ +-------------------+ +-------------------+
|
||||
|
|
||||
v
|
||||
+-------------------+
|
||||
| |
|
||||
| ML Engine |
|
||||
| |
|
||||
+-------------------+
|
||||
```
|
||||
|
||||
### Components
|
||||
|
||||
The WAF Feedback System includes the following components:
|
||||
|
||||
1. **Feedback API**: Provides endpoints for submitting and retrieving feedback.
|
||||
- `POST /api/security/waf/feedback`: Submit feedback
|
||||
- `GET /api/security/waf/feedback/{detectionId}`: Get feedback for a specific detection
|
||||
- `GET /api/security/waf/feedback/stats`: Get feedback statistics
|
||||
- `GET /api/security/waf/feedback/recent`: Get recent feedback
|
||||
|
||||
2. **Feedback Service**: Handles the business logic for feedback submission and retrieval.
|
||||
- `FeedbackService`: Core service for handling feedback
|
||||
- `DetectionFeedback`: Value object representing feedback
|
||||
- `FeedbackType`: Enum of feedback types
|
||||
|
||||
3. **Feedback Storage**: Stores feedback data for later analysis.
|
||||
- `FeedbackRepositoryInterface`: Interface for feedback storage
|
||||
- `DatabaseFeedbackRepository`: Database implementation
|
||||
|
||||
4. **Learning Service**: Analyzes feedback and adjusts the WAF models.
|
||||
- `FeedbackLearningService`: Core service for learning from feedback
|
||||
- `ModelAdjustment`: Value object representing model adjustments
|
||||
|
||||
5. **Analytics**: Analyzes feedback data to identify patterns and trends.
|
||||
- `FeedbackReportGenerator`: Generates reports from feedback data
|
||||
|
||||
6. **Reporting**: Provides visualizations and reports on feedback data.
|
||||
- `WafFeedbackDashboardController`: Controller for the feedback dashboard
|
||||
|
||||
7. **ML Engine**: Machine learning engine that uses feedback to improve detection.
|
||||
- `MachineLearningEngine`: Core ML engine
|
||||
- `ThresholdAdjustableInterface`: Interface for adjustable thresholds
|
||||
- `ConfidenceAdjustableInterface`: Interface for adjustable confidence
|
||||
- `FeatureWeightAdjustableInterface`: Interface for adjustable feature weights
|
||||
|
||||
### Integration
|
||||
|
||||
To integrate the WAF Feedback System into your application:
|
||||
|
||||
1. **Register Services**: Register the feedback services in your dependency injection container:
|
||||
|
||||
```php
|
||||
// Register feedback services
|
||||
$container->singleton(FeedbackRepositoryInterface::class, DatabaseFeedbackRepository::class);
|
||||
$container->singleton(FeedbackService::class, FeedbackService::class);
|
||||
$container->singleton(FeedbackLearningService::class, FeedbackLearningService::class);
|
||||
$container->singleton(FeedbackReportGenerator::class, FeedbackReportGenerator::class);
|
||||
```
|
||||
|
||||
2. **Register Controllers**: Register the feedback controllers:
|
||||
|
||||
```php
|
||||
// Register feedback controllers
|
||||
$container->singleton(WafFeedbackController::class, WafFeedbackController::class);
|
||||
$container->singleton(WafFeedbackDashboardController::class, WafFeedbackDashboardController::class);
|
||||
```
|
||||
|
||||
3. **Schedule Learning**: Schedule the learning process to run periodically:
|
||||
|
||||
```php
|
||||
// Schedule learning process to run daily
|
||||
$scheduler->daily('waf:learn-from-feedback');
|
||||
```
|
||||
|
||||
4. **Add UI Components**: Add feedback UI components to your application:
|
||||
|
||||
```php
|
||||
// Add feedback button to WAF detection alerts
|
||||
$alertComponent->addAction('Provide Feedback', '/admin/security/waf/feedback/submit/{detectionId}');
|
||||
```
|
||||
|
||||
## Learning Algorithms
|
||||
|
||||
The WAF Feedback System uses several learning algorithms to improve detection accuracy based on user feedback.
|
||||
|
||||
### False Positive Reduction
|
||||
|
||||
When users report false positives, the system adjusts the detection models to reduce the likelihood of similar false positives in the future:
|
||||
|
||||
1. **Threshold Adjustment**: Increases the detection threshold for the affected category, making it less sensitive.
|
||||
2. **Confidence Reduction**: Decreases the confidence score for similar detections, making them less likely to trigger alerts.
|
||||
3. **Feature Weight Adjustment**: Reduces the weights of features that contributed to the false positive.
|
||||
|
||||
### False Negative Reduction
|
||||
|
||||
When users report false negatives, the system adjusts the detection models to improve detection coverage:
|
||||
|
||||
1. **Threshold Adjustment**: Decreases the detection threshold for the affected category, making it more sensitive.
|
||||
2. **Confidence Increase**: Increases the confidence score for similar detections, making them more likely to trigger alerts.
|
||||
3. **Feature Weight Adjustment**: Increases the weights of features that should have contributed to detection.
|
||||
|
||||
### Severity Adjustment
|
||||
|
||||
When users report incorrect severity levels, the system adjusts the severity assessment:
|
||||
|
||||
1. **Confidence Adjustment**: Adjusts the confidence score based on the severity change.
|
||||
2. **Category Default Severity**: Updates the default severity for the detection category.
|
||||
|
||||
## Examples
|
||||
|
||||
### API Examples
|
||||
|
||||
#### Submitting Feedback
|
||||
|
||||
```javascript
|
||||
// Example: Submit false positive feedback
|
||||
fetch('/api/security/waf/feedback', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRF-TOKEN': csrfToken
|
||||
},
|
||||
body: JSON.stringify({
|
||||
detection_id: 'abc123',
|
||||
feedback_type: 'false_positive',
|
||||
category: 'SQL_INJECTION',
|
||||
severity: 'HIGH',
|
||||
comment: 'This is a legitimate query used by our reporting system',
|
||||
context: {
|
||||
query: 'SELECT * FROM reports WHERE date > ?'
|
||||
}
|
||||
})
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => console.log(data));
|
||||
```
|
||||
|
||||
#### Getting Feedback Statistics
|
||||
|
||||
```javascript
|
||||
// Example: Get feedback statistics
|
||||
fetch('/api/security/waf/feedback/stats')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
console.log('Total feedback:', data.stats.total_count);
|
||||
console.log('False positives:', data.stats.by_feedback_type.false_positive);
|
||||
console.log('False negatives:', data.stats.by_feedback_type.false_negative);
|
||||
});
|
||||
```
|
||||
|
||||
### Dashboard Examples
|
||||
|
||||
#### Viewing Feedback Trends
|
||||
|
||||
The Feedback Dashboard provides visualizations of feedback trends over time:
|
||||
|
||||
- **Accuracy Trend**: Shows how detection accuracy has improved over time
|
||||
- **False Positive Rate**: Shows how the false positive rate has decreased
|
||||
- **False Negative Rate**: Shows how the false negative rate has decreased
|
||||
- **Category Breakdown**: Shows feedback distribution by detection category
|
||||
|
||||
#### Analyzing Detection Categories
|
||||
|
||||
The Category Analysis view shows detailed information about each detection category:
|
||||
|
||||
- **Accuracy**: Detection accuracy for the category
|
||||
- **False Positive Rate**: False positive rate for the category
|
||||
- **False Negative Rate**: False negative rate for the category
|
||||
- **Severity Distribution**: Distribution of severity levels for the category
|
||||
- **Common Patterns**: Common patterns in false positives and false negatives
|
||||
|
||||
### Learning Examples
|
||||
|
||||
#### Scheduled Learning
|
||||
|
||||
The learning process can be scheduled to run periodically:
|
||||
|
||||
```bash
|
||||
# Run learning process daily
|
||||
php console.php waf:learn-from-feedback
|
||||
```
|
||||
|
||||
#### Manual Learning
|
||||
|
||||
The learning process can also be triggered manually:
|
||||
|
||||
```bash
|
||||
# Run learning process with custom parameters
|
||||
php console.php waf:learn-from-feedback --threshold=10 --learning-rate=0.5
|
||||
```
|
||||
|
||||
#### Learning Results
|
||||
|
||||
The learning process provides detailed results:
|
||||
|
||||
```
|
||||
Starting WAF feedback learning process...
|
||||
Processing 25 false positives, 10 false negatives, 5 severity adjustments
|
||||
Created 3 model adjustments for SQL_INJECTION, XSS, COMMAND_INJECTION
|
||||
Applied 3 model adjustments
|
||||
Learning process completed successfully in 0.5 seconds
|
||||
```
|
||||
135
docs/components/waf/index.md
Normal file
135
docs/components/waf/index.md
Normal file
@@ -0,0 +1,135 @@
|
||||
# WAF (Web Application Firewall)
|
||||
|
||||
> **Dokumentationshinweis:** Diese Dokumentation ist vollständig aktualisiert und stellt die aktuelle Implementierung der WAF-Komponente korrekt dar.
|
||||
|
||||
## Übersicht
|
||||
|
||||
Die WAF-Komponente (Web Application Firewall) ist ein fortschrittliches Sicherheitssystem, das Webanwendungen vor verschiedenen Bedrohungen schützt. Es verwendet Machine Learning und statistische Analysen, um Anomalien zu erkennen und potenzielle Angriffe zu blockieren, bevor sie Schaden anrichten können.
|
||||
|
||||
## Hauptkomponenten
|
||||
|
||||
### WAF-Klasse
|
||||
|
||||
Die zentrale Klasse für die WAF-Funktionalität:
|
||||
|
||||
```php
|
||||
// Klasse initialisieren
|
||||
$waf = new Waf($config, $eventDispatcher);
|
||||
|
||||
// Request prüfen
|
||||
$result = $waf->analyzeRequest($request);
|
||||
|
||||
// Entscheidung treffen
|
||||
if ($result->isBlocked()) {
|
||||
// Request blockieren
|
||||
}
|
||||
```
|
||||
|
||||
### MachineLearningEngine
|
||||
|
||||
Verarbeitet Requests mit Machine-Learning-Algorithmen:
|
||||
|
||||
- Extrahiert Features aus Requests
|
||||
- Vergleicht mit bekannten Mustern
|
||||
- Erkennt Anomalien und ungewöhnliches Verhalten
|
||||
- Klassifiziert potenzielle Bedrohungen
|
||||
|
||||
### Detektoren
|
||||
|
||||
Verschiedene Detektoren für unterschiedliche Arten von Anomalien:
|
||||
|
||||
- `StatisticalAnomalyDetector`: Erkennt statistische Abweichungen
|
||||
- `ClusteringAnomalyDetector`: Gruppiert ähnliche Requests und identifiziert Ausreißer
|
||||
- Erweiterbar für zusätzliche Detektoren
|
||||
|
||||
### Middleware-System
|
||||
|
||||
```php
|
||||
// In Bootstrap oder Application-Klasse
|
||||
$app->addMiddleware(WafMiddleware::class);
|
||||
```
|
||||
|
||||
## Feature-Extraktion
|
||||
|
||||
Die WAF extrahiert verschiedene Features aus eingehenden Requests:
|
||||
|
||||
1. **Request-Metadaten**: URL, HTTP-Methode, Header
|
||||
2. **Parameter-Analyse**: Anzahl, Länge, Entropie der Parameter
|
||||
3. **Inhaltsmuster**: Spezielle Zeichen, Skript-Tags, SQL-Fragmente
|
||||
4. **Verhaltensanalyse**: Anfragehäufigkeit, Sitzungsdauer, Navigationsmuster
|
||||
|
||||
## Anomalie-Erkennung
|
||||
|
||||
Der Anomalie-Erkennungsprozess umfasst:
|
||||
|
||||
1. **Feature-Extraktion** aus dem eingehenden Request
|
||||
2. **Baseline-Vergleich** mit normalen Verhaltensmustern
|
||||
3. **Anomalie-Bewertung** durch verschiedene Detektoren
|
||||
4. **Entscheidungsfindung** basierend auf Schwellenwerten und Regeln
|
||||
|
||||
## Konfiguration
|
||||
|
||||
Die WAF kann über die Konfigurationsdatei angepasst werden:
|
||||
|
||||
```php
|
||||
// config/waf.php
|
||||
return [
|
||||
'enabled' => true,
|
||||
'learning_mode' => false,
|
||||
'threshold' => 0.75,
|
||||
'detectors' => [
|
||||
'statistical' => true,
|
||||
'clustering' => true
|
||||
],
|
||||
'whitelist' => [
|
||||
'ips' => ['127.0.0.1'],
|
||||
'paths' => ['/api/health']
|
||||
]
|
||||
];
|
||||
```
|
||||
|
||||
## Integration
|
||||
|
||||
### Service-Container
|
||||
|
||||
Die WAF wird automatisch registriert und kann per Dependency Injection verwendet werden:
|
||||
|
||||
```php
|
||||
public function __construct(private readonly Waf $waf) {}
|
||||
```
|
||||
|
||||
### Event-Integration
|
||||
|
||||
Die WAF löst folgende Events aus:
|
||||
|
||||
- `waf.request_analyzed`: Nach der Analyse eines Requests
|
||||
- `waf.request_blocked`: Wenn ein Request blockiert wird
|
||||
- `waf.anomaly_detected`: Bei Erkennung einer Anomalie
|
||||
|
||||
## Feedback-System (geplant)
|
||||
|
||||
Ein Feedback-System für die WAF ist in Planung und wird folgende Funktionen bieten:
|
||||
|
||||
- Admin-Interface zur Überprüfung von WAF-Entscheidungen
|
||||
- Feedback-Mechanismus für falsch positive/negative Ergebnisse
|
||||
- Kontinuierliches Training der ML-Modelle basierend auf Feedback
|
||||
|
||||
## Fehlerbehebung
|
||||
|
||||
### Häufige Probleme
|
||||
|
||||
1. **Falsch positive Blockierungen**:
|
||||
- Überprüfen Sie die Whitelist-Konfiguration
|
||||
- Passen Sie den Schwellenwert an
|
||||
- Aktivieren Sie den Lernmodus vorübergehend
|
||||
|
||||
2. **Leistungsprobleme**:
|
||||
- Deaktivieren Sie rechenintensive Detektoren
|
||||
- Implementieren Sie Caching für wiederholte Anfragen
|
||||
- Optimieren Sie die Feature-Extraktion
|
||||
|
||||
## Weiterführende Informationen
|
||||
|
||||
- [Machine Learning Dokumentation](machine-learning.md)
|
||||
- [WAF-Konfiguration](configuration.md)
|
||||
- [Sicherheitsrichtlinien](/guides/security.md)
|
||||
252
docs/components/waf/machine-learning.md
Normal file
252
docs/components/waf/machine-learning.md
Normal file
@@ -0,0 +1,252 @@
|
||||
# WAF Machine Learning
|
||||
|
||||
> **Dokumentationshinweis:** Diese Dokumentation ist vollständig aktualisiert und stellt die aktuelle Implementierung der Machine-Learning-Komponente der WAF korrekt dar.
|
||||
|
||||
## Übersicht
|
||||
|
||||
Die Machine-Learning-Komponente ist das Herzstück des WAF-Systems und ermöglicht die intelligente Erkennung von Bedrohungen und Anomalien in Echtzeit. Sie verwendet fortschrittliche Algorithmen, um normales Verhalten zu lernen und Abweichungen zu identifizieren, die auf potenzielle Angriffe hindeuten könnten.
|
||||
|
||||
## MachineLearningEngine
|
||||
|
||||
Die `MachineLearningEngine`-Klasse ist die zentrale Komponente des Machine-Learning-Systems:
|
||||
|
||||
```php
|
||||
// Initialisierung
|
||||
$engine = new MachineLearningEngine(
|
||||
$enabled,
|
||||
$extractors,
|
||||
$detectors,
|
||||
$clock,
|
||||
$analysisTimeout,
|
||||
$confidenceThreshold,
|
||||
$enableParallelProcessing,
|
||||
$enableFeatureCaching,
|
||||
$maxFeaturesPerRequest
|
||||
);
|
||||
|
||||
// Analyse eines Requests
|
||||
$result = $engine->analyzeRequest($requestData, $context);
|
||||
```
|
||||
|
||||
### Hauptfunktionen
|
||||
|
||||
- **Request-Analyse**: Analysiert eingehende Requests auf Anomalien
|
||||
- **Feature-Extraktion**: Extrahiert relevante Features aus Requests
|
||||
- **Anomalie-Erkennung**: Identifiziert Abweichungen vom normalen Verhalten
|
||||
- **Baseline-Management**: Verwaltet und aktualisiert Verhaltensbaselines
|
||||
- **Modell-Aktualisierung**: Passt Modelle kontinuierlich an neue Daten an
|
||||
- **Leistungsüberwachung**: Überwacht und optimiert die Leistung des Systems
|
||||
|
||||
## Prozessablauf
|
||||
|
||||
Der Machine-Learning-Prozess umfasst folgende Schritte:
|
||||
|
||||
1. **Feature-Extraktion** (`extractFeatures`):
|
||||
- Extrahiert relevante Features aus dem Request
|
||||
- Validiert und normalisiert Features
|
||||
- Wendet Feature-Caching an, wenn aktiviert
|
||||
|
||||
2. **Baseline-Abruf** (`getBaselines`):
|
||||
- Ruft relevante Baselines für die extrahierten Features ab
|
||||
- Erstellt neue Baselines, wenn keine existieren
|
||||
- Verwaltet Baseline-Cache für optimale Leistung
|
||||
|
||||
3. **Anomalie-Erkennung** (`detectAnomalies`):
|
||||
- Wendet verschiedene Detektoren auf die Features an
|
||||
- Vergleicht Features mit Baselines
|
||||
- Berechnet Anomalie-Scores und Konfidenzwerte
|
||||
|
||||
4. **Konfidenzberechnung** (`calculateOverallConfidence`):
|
||||
- Aggregiert Ergebnisse verschiedener Detektoren
|
||||
- Berechnet Gesamtkonfidenz basierend auf gewichteten Scores
|
||||
- Vergleicht mit Schwellenwert für Entscheidungsfindung
|
||||
|
||||
5. **Modell-Aktualisierung** (`updateModels`):
|
||||
- Aktualisiert Modelle mit neuen Daten
|
||||
- Passt Baselines an sich ändernde Muster an
|
||||
- Optimiert Detektoren basierend auf Feedback
|
||||
|
||||
## Feature-Extraktion
|
||||
|
||||
Die Feature-Extraktion ist ein kritischer Schritt im Machine-Learning-Prozess:
|
||||
|
||||
```php
|
||||
$features = $engine->extractFeatures($requestData, $context);
|
||||
```
|
||||
|
||||
### Feature-Typen
|
||||
|
||||
- **Request-Features**: URL-Struktur, Parameter, Header
|
||||
- **Inhalts-Features**: Payload-Struktur, Entropie, Zeichenverteilung
|
||||
- **Verhaltens-Features**: Anfragemuster, Sitzungsverhalten, Timing
|
||||
- **Kontext-Features**: IP-Reputation, Geolocation, Benutzeragent
|
||||
|
||||
### Feature-Validierung
|
||||
|
||||
Alle extrahierten Features werden validiert, um Qualität und Konsistenz zu gewährleisten:
|
||||
|
||||
- Überprüfung auf Vollständigkeit und Gültigkeit
|
||||
- Normalisierung für konsistente Verarbeitung
|
||||
- Filterung irrelevanter oder redundanter Features
|
||||
|
||||
## Anomalie-Detektoren
|
||||
|
||||
Das System verwendet verschiedene Detektoren für unterschiedliche Arten von Anomalien:
|
||||
|
||||
### StatisticalAnomalyDetector
|
||||
|
||||
Erkennt statistische Abweichungen in numerischen Features:
|
||||
|
||||
- Berechnet Z-Scores für numerische Features
|
||||
- Identifiziert Ausreißer basierend auf statistischen Verteilungen
|
||||
- Erkennt ungewöhnliche Wertekombinationen
|
||||
|
||||
### ClusteringAnomalyDetector
|
||||
|
||||
Gruppiert ähnliche Requests und identifiziert Ausreißer:
|
||||
|
||||
- Verwendet Clustering-Algorithmen zur Gruppierung ähnlicher Requests
|
||||
- Berechnet Distanz zu Cluster-Zentren
|
||||
- Identifiziert isolierte Punkte als potenzielle Anomalien
|
||||
|
||||
### Erweiterbarkeit
|
||||
|
||||
Das System ist erweiterbar für zusätzliche Detektoren:
|
||||
|
||||
```php
|
||||
// Eigenen Detektor registrieren
|
||||
$config->registerDetector(new CustomAnomalyDetector());
|
||||
```
|
||||
|
||||
## Baselines
|
||||
|
||||
Baselines repräsentieren normales Verhalten und dienen als Referenz für die Anomalie-Erkennung:
|
||||
|
||||
### Baseline-Typen
|
||||
|
||||
- **Statistische Baselines**: Mittelwerte, Standardabweichungen, Verteilungen
|
||||
- **Verhaltensbaselines**: Typische Anfragemuster und Sequenzen
|
||||
- **Inhaltsbaselines**: Normale Payload-Strukturen und Charakteristiken
|
||||
|
||||
### Baseline-Management
|
||||
|
||||
Baselines werden kontinuierlich aktualisiert und optimiert:
|
||||
|
||||
- Automatische Erstellung für neue Feature-Kombinationen
|
||||
- Regelmäßige Aktualisierung basierend auf neuen Daten
|
||||
- Gewichtete Historisierung für Stabilität und Aktualität
|
||||
|
||||
## Leistungsoptimierung
|
||||
|
||||
Die Machine-Learning-Komponente enthält verschiedene Optimierungen:
|
||||
|
||||
### Feature-Caching
|
||||
|
||||
```php
|
||||
// Konfiguration
|
||||
$config->setEnableFeatureCaching(true);
|
||||
```
|
||||
|
||||
Speichert extrahierte Features für ähnliche Requests, um Rechenaufwand zu reduzieren.
|
||||
|
||||
### Parallele Verarbeitung
|
||||
|
||||
```php
|
||||
// Konfiguration
|
||||
$config->setEnableParallelProcessing(true);
|
||||
```
|
||||
|
||||
Verarbeitet Features und Detektoren parallel für verbesserte Leistung.
|
||||
|
||||
### Leistungsmetriken
|
||||
|
||||
Das System sammelt Leistungsmetriken zur Überwachung und Optimierung:
|
||||
|
||||
- Verarbeitungszeiten für verschiedene Phasen
|
||||
- Feature- und Anomaliezahlen
|
||||
- Cache-Trefferquoten und Speichernutzung
|
||||
|
||||
## Konfiguration
|
||||
|
||||
Die Machine-Learning-Komponente bietet umfangreiche Konfigurationsmöglichkeiten:
|
||||
|
||||
```php
|
||||
// config/waf_ml.php
|
||||
return [
|
||||
'enabled' => true,
|
||||
'confidence_threshold' => 0.75,
|
||||
'analysis_timeout' => 100, // ms
|
||||
'enable_parallel_processing' => true,
|
||||
'enable_feature_caching' => true,
|
||||
'max_features_per_request' => 50,
|
||||
'detectors' => [
|
||||
'statistical' => [
|
||||
'enabled' => true,
|
||||
'weight' => 0.6
|
||||
],
|
||||
'clustering' => [
|
||||
'enabled' => true,
|
||||
'weight' => 0.4
|
||||
]
|
||||
]
|
||||
];
|
||||
```
|
||||
|
||||
## Integration mit WAF
|
||||
|
||||
Die Machine-Learning-Komponente ist nahtlos in das WAF-System integriert:
|
||||
|
||||
```php
|
||||
// In WAF-Klasse
|
||||
public function analyzeRequest(Request $request): WafResult
|
||||
{
|
||||
$requestData = $this->analyzer->analyze($request);
|
||||
$mlResult = $this->mlEngine->analyzeRequest($requestData, $this->context);
|
||||
|
||||
return new WafResult($requestData, $mlResult);
|
||||
}
|
||||
```
|
||||
|
||||
## Fehlerbehebung
|
||||
|
||||
### Häufige Probleme
|
||||
|
||||
1. **Hohe Falsch-Positiv-Rate**:
|
||||
- Erhöhen Sie den Konfidenz-Schwellenwert
|
||||
- Aktivieren Sie den Lernmodus für einen längeren Zeitraum
|
||||
- Überprüfen Sie die Gewichtung der Detektoren
|
||||
|
||||
2. **Leistungsprobleme**:
|
||||
- Aktivieren Sie Feature-Caching
|
||||
- Reduzieren Sie die maximale Anzahl von Features pro Request
|
||||
- Optimieren Sie die Detektorkonfiguration
|
||||
|
||||
3. **Fehlende Erkennung**:
|
||||
- Senken Sie den Konfidenz-Schwellenwert
|
||||
- Aktivieren Sie zusätzliche Detektoren
|
||||
- Überprüfen Sie die Feature-Extraktion
|
||||
|
||||
## Weiterentwicklung
|
||||
|
||||
### Geplante Erweiterungen
|
||||
|
||||
1. **Feedback-System**:
|
||||
- Integration von Administratorfeedback
|
||||
- Automatische Anpassung basierend auf Feedback
|
||||
- Kontinuierliches Lernen aus Fehlklassifikationen
|
||||
|
||||
2. **Erweiterte Detektoren**:
|
||||
- Deep-Learning-basierte Anomalieerkennung
|
||||
- Sequenzanalyse für komplexe Angriffsmuster
|
||||
- Verhaltensbasierte Benutzerprofilierung
|
||||
|
||||
3. **Verbesserte Visualisierung**:
|
||||
- Dashboard für Anomalietrends
|
||||
- Feature-Wichtigkeitsanalyse
|
||||
- Echtzeit-Monitoring von Bedrohungen
|
||||
|
||||
## Weiterführende Informationen
|
||||
|
||||
- [WAF-Übersicht](index.md)
|
||||
- [WAF-Konfiguration](configuration.md)
|
||||
- [Sicherheitsrichtlinien](/guides/security.md)
|
||||
Reference in New Issue
Block a user