Die Security-Komponente beim Symfony-Upgrade richtig migrieren

Beim Symfony Security Upgrade scheitern viele Teams nicht an der Fachlogik, sondern an Authenticator, Guard und Passwort-Hashing. So migrieren Sie sicher und ohne Ausfall.

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 AbstractAuthenticator erweitert.

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 durch getUserIdentifier() ersetzt.
  • getSalt() und die Pflicht zu eraseCredentials() in alter Form sind entfallen beziehungsweise anders geregelt.
  • Der Login-Konfigurationsschlüssel heißt statt username_path nun konsistent über getUserIdentifier().

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:

  1. 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.
  2. anonymous ist weg. Der Schlüssel anonymous: true existiert nicht mehr. Nicht authentifizierte Nutzer gelten jetzt schlicht als nicht eingeloggt. Wo Sie früher auf IS_AUTHENTICATED_ANONYMOUSLY geprüft haben, nutzen Sie heute PUBLIC_ACCESS.

So gehen Sie das Upgrade in der Praxis an

Ein bewährter, risikoarmer Ablauf sieht so aus:

  1. 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.
  2. Auf 4.4 bereits enable_authenticator_manager: true aktivieren, um das neue System parallel einzuschalten und schrittweise umzustellen.
  3. Authenticator einzeln migrieren, nicht alle auf einmal. Nach jedem Schritt die Testsuite laufen lassen.
  4. Passwort-Hashing auf auto mit migrate_from konfigurieren, damit Bestandshashes gültig bleiben.
  5. Erst am Ende Guard-Reste und anonymous entfernen 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.

Guard-System auf Authenticator-System migrieren Alt (Guard) Neu (Authenticator) getCredentials() getUser() checkCredentials() authenticate() liefert einen Passport UserBadge PasswordCredentials CsrfTokenBadge (optional) encoder wird zu hasher (Hashes bleiben gültig)

Ist Ihre PHP-Anwendung noch zu retten?

Im kostenlosen Kurz-Check schaue ich mit Ihnen auf Ihr System und sage Ihnen ehrlich, was möglich ist – unverbindlich und ohne Verkaufsdruck.

Kurz-Check vereinbaren