In meinen beiden vorherigen Beiträgen habe ich über die Erstellung von Menüs, Internationalisierung und dem aufsetzen von Symfony gesprochen. Nun ist es an der Zeit das erste Usermanagement anzugehen.
Die Rollen
Macht euch vorher klar was Ihr jetzt und in einem Jahr benötigt. Lieber einen Schritt zu weit gedacht, als später alles umzukrempeln. In meinem Fall benötige ich 3 verschiedene Rollen (abgesehen vom normalen Seitenbesucher)
Admin
Hat alle Rechte für CRUD Operationen auf alle Entitäten
Bearbeiter
Hat CRU Rechte auf einige Entitäten
Helfer
Kann lediglich Aufträge einsehen und sich dafür eintragen
User Entität
Ich habe wie im ersten Artikel beschrieben mein User Model 1zu1 übernommen, dass heißt diesen Schritt spare ich mir. Ich habe mir lediglich einen User Provider in meiner security.yaml angelegt – in diesen definiere ich welche Entität genommen werden soll und welches Feld zur Identifikation genommen werden soll.
security:
encoders:
App\Entity\Users:
algorithm: bcrypt
providers:
app_user_provider:
entity:
class: App\Entity\Users
property: email
Symfony empfiehlt hier als Encription argon2i (damit braucht Ihr aber PHP 7.2 – also aufpassen) – ich nutze bcrypt (aus oben bereits genannten Gründen, ziehe das aber noch nach)
php bin/console make:auth
Das generiert euch folgende Files:
created: src/Security/LoginFormAuthenticator.php
updated: config/packages/security.yaml
created: src/Controller/SecurityController.php
created: templates/security/login.html.twig
und fügt der security.yaml folgende Zeilen hinzu:
guard:
authenticators:
- App\Security\LoginFormAuthenticator
Bestimmt so also euren „Türsteher“ der bestimmt wer rein kann und wer nicht 😉 Vor dieser Code-Generierung hat das ganze so ausgesehen
Nun werdet Ihr automatisch auf die Login-Action des Security-Controllers geleitet (schön sieht es noch nicht aus, aber das kommt später)
UserInterface
Da wir unsere User Entität nicht über make erstellt haben, nutzen wir von Haus aus kein UserInterface. Also fügen wir das noch hinzu:
use Symfony\Component\Security\Core\User\UserInterface;
/**
* Users
*
* @ORM\Table(name="users")
* @ORM\Entity
*/
class Users implements UserInterface...
Das Interface braucht 3 Methoden (getSalt(), eraseCredentials(), getRoles()) – vergesst diese nicht. Jede vernünftige IDE wird euch das aber anzeigen
getsalt() brauchen wir bei bcrypt nicht wenn wir es in der security.yaml implementieren erasecredentials() auch erstmal nicht da wir keine sensitiven Daten auf den Benutzer schreiben. Nur die getRoles()
/**
* @see UserInterface
*/
public function getRoles(): array
{
$roles = $this->role;
// damit mindestens eine Rolle gesetzt wird
$roles[] = 'ROLE_USER';
return array_unique($roles);
}
Rollen Datentyp
In meiner alten Datenbank habe ich als Format einen einfachen String gehabt, hier möchte ich nun auf den von Symfony empfohlenen Weg umsteigen – JSON.
/**
* @ORM\Column(type="json")
*/
private $role = [];
Damit können wir später entspannter auf Multirollen switchen und man hält sich an einen Standard (bin kein Fan von Komma oder Semicolon getrennten Values wenn Mysql doch schon JSON als Typ mitbringt)
Das ganze sieht dann so in der Datenbank aus
{"role":"ROLE_USER"}
Login
Beim Login gibt es nicht all zu viel zu beachten, lediglich Style-technisch kann man hier etwas nachhelfen.
<section id="login">
<div class="container">
<div class="row">
<div class="col-12 col-md-6">
FORM ELEMENT
</div>
</div>
</div>
</section>
Je nachdem ob Ihr auch Bootstrap nutzt oder nicht. Da da Login-Formular relativ klein ist ist ein min-height hier angebracht.
#login{
min-height: 70vh;
}
Nun wäre es noch schön den normalen Benutzer und den Admin auf eine bestimmte Seite zu leiten. Das machen wir in der src/Security/LoginFormAuthenticator.php -> onAuthenticationSuccess() Methode.
Über return gebt Ihr die entsprechende Route zurück
return new RedirectResponse($this->router->generate('users_index'));
wollt Ihr aber wie ich hier versch. Pfade für versch. Rollen zurückgeben schaut euch mein GIST an.
Access Denied
Wenn Ihr aktuell eine Ressource aufruft, kommt im Debug Mode hier erstmal folgende Meldung
Um diese abzufangen legt Ihr in config/packages/security.yaml unter security folgenden Parameter mit Pfad /login an
access_denied_url: /login
Damit werdet Ihr bei jedem Zugriff auf gesperrte Ressourcen auf /login geleitet.
Logout
Um eure Benutzer nun auszuloggen benötigt Ihr erstmal den richtigen Pfad in eurem Konfigurationparameter logout
Also auf zur security.yaml und eine path für logout hinzufügen. Diesen könnt Ihr dann in eurer routes.yaml benutzen, like so:
app_logout:
path: /logout
Damit könnt Ihr euch über /logout ausloggen oder eine URL generieren
{% if is_granted('IS_AUTHENTICATED_FULLY') %}
<li class="nav-item">
<a href="{{ logout_path(key = null) }}" class="nav-link">{{ 'menu.logout.name'|trans }}</a>
</li>
{% endif %}
Das ganze soll natürlich nur angezeigt werden wenn der User (egal ob Admin oder Helfer) eingeloggt ist, daher die IF-CLAUSE drum herum.
That’s it – damit steht das Grundgerüst der Benutzerverwaltung inkl. Authentifikation. Gefühlt geht das in Cakephp etwas leichter – aber das ist subjektiv, da ich schon so lange mit Cakephp gearbeitet habe.
Alles in allem hat mich das mit dem schreiben des Artikels ca. 8 Stunden gekostet. Wäre ohne Dokumentation einiger Schritte sicher schneller gegangen, allerdings kann ich so selbst ab und zu nochmal auf meinen Artikel zurückgreifen und mir so mühseliges suchen im WWW ersparen.
Anregungen zum Thema oder Fehler gefunden? Schreibt gerne in die Kommentare!