Das Sicherheitssystem ist beim Symfony-Upgrade fast immer die Komponente, die den meisten Respekt verdient. Nicht weil sie schlecht gebaut wäre, sondern weil sich hier zwischen Symfony 4.4 und 6.x mehr geändert hat als fast überall sonst. Wer eine Anwendung von 3.4 oder 4.4 auf eine aktuelle Version hebt, stolpert selten über die Fachlogik der Anwendung. Er stolpert über den Rahmen: das alte Guard-System, den UserProviderInterface, das Passwort-Encoding und die Reihenfolge, in der access_control ausgewertet wird. Genau dieser Rahmen ist beim Symfony Security Upgrade das eigentliche Projekt.
Die gute Nachricht vorweg: Ihre Berechtigungslogik, Ihre Rollen und Ihre Passwort-Hashes müssen Sie in aller Regel nicht neu erfinden. Sie müssen sie nur sauber in das neue Authenticator-System überführen. Das ist Handarbeit, aber es ist beherrschbare Handarbeit.
Warum das Symfony Security Upgrade der heikelste Teil ist
Die zentrale Änderung: Symfony hat mit Version 5.1 ein neues Authenticator-System eingeführt und das alte Guard-System mit 6.0 vollständig entfernt. Wer von 4.4 kommt, arbeitet also mit einer Komponente, die im Zielsystem schlicht nicht mehr existiert.
Das macht Security anders als etwa den Wechsel von annotations zu attributes. Ein falsch migrierter Router wirft einen Fehler, den Sie sofort sehen. Ein falsch migriertes Security-System kann Türen öffnen, die geschlossen bleiben sollten, ohne dass eine einzige Exception fliegt. Ein Login funktioniert weiterhin, aber eine access_control-Regel greift plötzlich nicht mehr, weil sich die Auswertungsreihenfolge oder das Verhalten des Firewall-Kontexts geändert hat. Solche Fehler sind still. Deshalb gehört zu jeder Security-Migration ein Netz aus Tests, das genau diese stillen Fälle abdeckt.
Der Umstieg von Guard auf das neue Authenticator-System
Kern der Arbeit ist der Wechsel von Guard\AbstractGuardAuthenticator auf Http\Authenticator\AbstractAuthenticator beziehungsweise die passenden Built-in-Authenticators. Zwei Wege sind sinnvoll:
- Standardfälle auf Built-ins umstellen. Wer nur Formular-Login und "Remember me" nutzt, braucht oft gar keinen eigenen Authenticator mehr. Der
form_login-Authenticator deckt das vollständig ab. Custom-Code, den Sie löschen können, ist Code, den Sie nicht mehr migrieren müssen. - Sonderfälle als eigenen Authenticator neu schreiben. API-Token, SSO-Header oder Zwei-Faktor-Logik wandern in eine Klasse, die
AbstractAuthenticatorerweitert.
Der wichtigste konzeptionelle Unterschied: Das alte Guard-System hatte getrennte Methoden wie getCredentials(), getUser() und checkCredentials(). Das neue System bündelt das in einer authenticate()-Methode, die ein Passport zurückgibt. Der Passport trägt Badges: einen UserBadge, einen PasswordCredentials oder CustomCredentials, optional ein CsrfTokenBadge. Ihre Aufgabe ist es, die alte, verstreute Logik in genau diese Badge-Struktur zu übersetzen. Die Prüfung selbst, also welcher Nutzer zu welchen Credentials passt, bleibt inhaltlich identisch.
Was passiert mit checkCredentials und der Passwortprüfung?
Die manuelle Passwortprüfung aus checkCredentials() entfällt. Übergeben Sie im Passport einen PasswordCredentials-Badge mit dem Klartext-Passwort, prüft Symfony den Hash automatisch gegen den geladenen User. Ihr Code wird dadurch kürzer und weniger fehleranfällig, weil die kritische Vergleichslogik im Framework liegt, nicht bei Ihnen.
Passwort-Hashing: encoder wird zu hasher
Ein Detail, das gern übersehen wird und trotzdem produktionskritisch ist. Der security.encoders-Knoten heißt jetzt security.password_hashers, und das UserPasswordEncoderInterface wurde zu UserPasswordHasherInterface. Rein technisch ist das ein Umbenennen.
Entscheidend: Ihre bestehenden Passwort-Hashes bleiben gültig. Wenn Sie auto als Algorithmus konfigurieren, erkennt Symfony den alten Hash-Typ (etwa bcrypt) und verifiziert korrekt dagegen. Sie müssen also von keinem Nutzer verlangen, sein Passwort neu zu setzen. Wer migrate_from konfiguriert, kann Hashes bei der nächsten erfolgreichen Anmeldung sogar transparent auf einen stärkeren Algorithmus umheben, ohne dass der Nutzer etwas merkt. Das ist ein gutes Beispiel für die Grundhaltung: Das laufende System hat funktionierende Hashes. Die behalten Sie, Sie verpacken sie nur neu.
UserInterface: die stille Breaking Change
Das UserInterface hat sich mehrfach geändert, und diese Änderungen sind tückisch, weil der alte Code oft ohne Fehler weiterläuft, aber falsch. Konkret:
getUsername()wurde durchgetUserIdentifier()ersetzt.getSalt()und die Pflicht zueraseCredentials()in alter Form sind entfallen beziehungsweise anders geregelt.- Der Login-Konfigurationsschlüssel heißt statt
username_pathnun konsistent übergetUserIdentifier().
Prüfen Sie hier besonders genau, ob Ihre User-Entität das Identifier-Feld korrekt zurückgibt. Ein häufiger Fehler: Die Entität liefert weiterhin den Anzeigenamen statt der eindeutigen Kennung, und plötzlich matchen Sessions nicht mehr sauber.
access_control und Firewalls prüfen, nicht nur übernehmen
Kopieren Sie Ihre security.yaml nicht blind. Zwei Punkte verdienen Aufmerksamkeit:
- Reihenfolge der access_control-Regeln. Sie werden von oben nach unten ausgewertet, die erste passende Regel gewinnt. Beim Umschreiben verrutscht diese Reihenfolge leicht, und eine zu breite Regel weiter oben kann eine spezifischere weiter unten aushebeln.
- anonymous ist weg. Der Schlüssel
anonymous: trueexistiert nicht mehr. Nicht authentifizierte Nutzer gelten jetzt schlicht als nicht eingeloggt. Wo Sie früher aufIS_AUTHENTICATED_ANONYMOUSLYgeprüft haben, nutzen Sie heutePUBLIC_ACCESS.
So gehen Sie das Upgrade in der Praxis an
Ein bewährter, risikoarmer Ablauf sieht so aus:
- Vor der Migration Feature-Tests für jeden Login-Weg und die wichtigsten
access_control-Regeln schreiben. Diese Tests sind Ihr Sicherheitsnetz gegen stille Fehler. - Auf 4.4 bereits
enable_authenticator_manager: trueaktivieren, um das neue System parallel einzuschalten und schrittweise umzustellen. - Authenticator einzeln migrieren, nicht alle auf einmal. Nach jedem Schritt die Testsuite laufen lassen.
- Passwort-Hashing auf
automitmigrate_fromkonfigurieren, damit Bestandshashes gültig bleiben. - Erst am Ende Guard-Reste und
anonymousentfernen und auf die Zielversion heben.
Praxis-Tipp: Testen Sie explizit den negativen Fall. Ein Login, der funktioniert, beweist wenig. Beweisen Sie, dass ein gesperrter Nutzer, ein falsches Passwort und ein Zugriff ohne Rolle zuverlässig abgewiesen werden. Genau dort verstecken sich die teuren Fehler einer Security-Migration.
Ein Security-System, das seit Jahren zuverlässig anmeldet und absichert, ist kein Sanierungsfall, sondern der beste Beweis, dass Ihre Logik trägt. Bei genau solchen Migrationen, wenn das Guard-System weg muss, ohne dass eine Tür aufspringt, unterstützt LegacyWerk mit Erfahrung aus vielen realen Symfony-Upgrades.