Security-Grundlagen: Firewall und Provider
Security-Grundlagen: Firewall und Provider
~16 Min. Lesezeit Zuletzt aktualisiert am 8. August 2026
Bisher war JEDE Seite unseres Aufgaben-Managers für JEDEN erreichbar – Zeit, das Security-Bundle einzurichten und die vollständige User-Entity zu bauen.
Security-Bundle installieren
composer require symfony/security-bundleDie vollständige User-Entity
php bin/console make:usermake:user fragt interaktiv nach Entity-Name, Identifier-Feld (E-Mail oder Benutzername) und ob Passwörter gehasht werden sollen – erweitert die aus Kapitel 23 bekannte User-Entity um ZWEI Pflicht-Interfaces:
<?php
declare(strict_types=1);
namespace App\Entity;
use App\Repository\UserRepository;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
use Symfony\Component\Security\Core\User\UserInterface;
#[ORM\Entity(repositoryClass: UserRepository::class)]
class User implements UserInterface, PasswordAuthenticatedUserInterface
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\Column(length: 180, unique: true)]
private string $email = '';
#[ORM\Column(length: 180)]
private string $name = '';
/**
* @var list<string>
*/
#[ORM\Column]
private array $roles = [];
#[ORM\Column]
private string $password = '';
public function getId(): ?int
{
return $this->id;
}
public function getEmail(): string
{
return $this->email;
}
public function setEmail(string $email): static
{
$this->email = $email;
return $this;
}
public function getUserIdentifier(): string
{
return $this->email;
}
public function getName(): string
{
return $this->name;
}
public function setName(string $name): static
{
$this->name = $name;
return $this;
}
/**
* @return list<string>
*/
public function getRoles(): array
{
$roles = $this->roles;
$roles[] = 'ROLE_USER';
return array_unique($roles);
}
/**
* @param list<string> $roles
*/
public function setRoles(array $roles): static
{
$this->roles = $roles;
return $this;
}
public function getPassword(): string
{
return $this->password;
}
public function setPassword(string $password): static
{
$this->password = $password;
return $this;
}
public function eraseCredentials(): void
{
// Klartext-Passwort (falls temporär im Objekt gehalten) hier löschen
}
}Die vier Pflichtmethoden im Detail
getUserIdentifier()– der eindeutige Login-Bezeichner (hier: E-Mail), NICHT zwingend die ID.getRoles()– IMMER MINDESTENS['ROLE_USER']zurückgeben, da Symfony sonst manche Sicherheitschecks als "kein Zugriff" statt "unbekannte Rolle" behandelt.getPassword()– das GEHASHTE Passwort (Kapitel 28), NIEMALS Klartext.eraseCredentials()– wird nach der Authentifizierung aufgerufen, um sensible temporäre Daten zu löschen (bei unserem einfachen Setup meist leer).
config/packages/security.yaml verstehen
security:
password_hashers:
Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 'auto'
providers:
app_user_provider:
entity:
class: App\Entity\User
property: email
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
lazy: true
provider: app_user_provider
access_control:
- { path: ^/login, roles: PUBLIC_ACCESS }
- { path: ^/projects, roles: ROLE_USER }Provider vs. Firewall: der entscheidende Unterschied
| Konzept | Zweck |
|---|---|
| Provider | Beantwortet: "WOHER kommen Nutzer-Daten?" – hier: aus der User-Entity, per email nachgeschlagen. |
| Firewall | Beantwortet: "WELCHER URL-Bereich braucht überhaupt Authentifizierung, und WIE (Formular, Token, ...)?" – der dev-Firewall-Eintrag schließt z. B. die Debug-Toolbar von JEDER Security-Prüfung aus. |
access_control definiert, WELCHE Rolle für WELCHE URL-Muster nötig ist – PUBLIC_ACCESS für die Login-Seite selbst (sonst könnte sich NIEMAND einloggen), ROLE_USER für /projects. Regeln werden von OBEN nach UNTEN geprüft, die ERSTE passende gewinnt – GENAU wie bei Routing (Kapitel 7).
Migration für die erweiterte User-Entity
php bin/console make:migration
php bin/console doctrine:migrations:migrateTipp: Der aktuelle Stand: Symfony WEISS jetzt, wie es Nutzer findet und prüft – aber es gibt noch KEINEN Weg, sich tatsächlich einzuloggen. Kapitel 27 baut genau das.