Rollen und Zugriffskontrolle mit Voters
Rollen und Zugriffskontrolle mit Voters
~17 Min. Lesezeit Zuletzt aktualisiert am 8. August 2026
access_control aus Kapitel 26 regelt GROBE Zugriffsrechte (eingeloggt oder nicht) – für unser eigentliches Ziel aus Kapitel 5 ("nur Projekt-Mitglieder sehen ihre eigenen Projekte") brauchen wir FEINGRANULARE, objektbezogene Prüfungen: Voters.
Warum ROLE_USER allein nicht reicht
ROLE_USER beantwortet nur "ist dieser Nutzer EINGELOGGT?" – NICHT "darf DIESER Nutzer GENAU DIESES Projekt sehen?". Diese Frage hängt von den KONKRETEN Daten ab (ist der Nutzer Mitglied DIESES Projekts?), nicht von einer statischen Rolle.
Einen Voter generieren
php bin/console make:voter ProjectVoter<?php
declare(strict_types=1);
namespace App\Security\Voter;
use App\Entity\Project;
use App\Entity\User;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
class ProjectVoter extends Voter
{
public const string VIEW = 'PROJECT_VIEW';
public const string EDIT = 'PROJECT_EDIT';
protected function supports(string $attribute, mixed $subject): bool
{
return in_array($attribute, [self::VIEW, self::EDIT], true)
&& $subject instanceof Project;
}
protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool
{
$user = $token->getUser();
if (!$user instanceof User) {
return false;
}
/** @var Project $project */
$project = $subject;
return match ($attribute) {
self::VIEW => $project->getMitglieder()->contains($user),
self::EDIT => $project->getMitglieder()->contains($user),
default => false,
};
}
}supports() und voteOnAttribute() im Detail
supports()– schnelle Vorprüfung: "fühlt sich DIESER Voter überhaupt für diese Kombination aus Attribut und Objekt zuständig?" Symfony ruft NUR beitrueauchvoteOnAttribute()auf.voteOnAttribute()– die eigentliche LOGIK:true= Zugriff erlaubt,false= verweigert.
self::VIEW/self::EDIT als benannte Konstanten statt roher Strings verhindert Tippfehler und macht per IDE-Autovervollständigung sichtbar, WELCHE Attribute dieser Voter überhaupt unterstützt.
Den Voter im Controller nutzen
use App\Security\Voter\ProjectVoter;
#[Route('/projects/{id}', name: 'project_show', requirements: ['id' => '\d+'])]
public function show(int $id, ProjectRepository $projectRepository): Response
{
$projekt = $projectRepository->find($id);
if ($projekt === null) {
throw $this->createNotFoundException();
}
$this->denyAccessUnlessGranted(ProjectVoter::VIEW, $projekt);
return $this->render('project/show.html.twig', ['projekt' => $projekt]);
}denyAccessUnlessGranted() (aus AbstractController) ruft INTERN voteOnAttribute() auf und wirft AUTOMATISCH eine AccessDeniedException (Symfony wandelt das in eine 403-Fehlerseite um), falls der Zugriff verweigert wird – KEIN manuelles if nötig.
Voter-Attribute in Twig prüfen
{% if is_granted('PROJECT_EDIT', projekt) %}
<a href="{{ path('project_edit', {id: projekt.id}) }}">Bearbeiten</a>
{% endif %}is_granted() ist die Twig-Entsprechung zu denyAccessUnlessGranted() – hier ohne Exception, sondern für BEDINGTE Anzeige einzelner UI-Elemente (den Bearbeiten-Link nur zeigen, wenn er tatsächlich genutzt werden DARF).
Einfache Rollenprüfungen ohne Voter
Für simple Rollenprüfungen (ohne Objektbezug) ist ein Voter ÜBERDIMENSIONIERT – #[IsGranted] als Attribut auf der Methode reicht:
use Symfony\Component\Security\Http\Attribute\IsGranted;
#[Route('/admin/users', name: 'admin_user_index')]
#[IsGranted('ROLE_ADMIN')]
public function index(): Response
{
// Nur für Admins erreichbar
}Faustregel: Voter vs. #[IsGranted] vs. access_control
| Werkzeug | Einsatzfall |
|---|---|
| access_control (security.yaml) | GROBE, URL-Muster-basierte Regeln – z. B. "der gesamte /admin-Bereich braucht ROLE_ADMIN". |
| #[IsGranted('ROLE_...')] | Einfache Rollenprüfung auf EINER Controller-Methode, OHNE Objektbezug. |
| Voter | OBJEKTBEZOGENE Prüfungen ("gehört DIESES Projekt DIESEM Nutzer?") – wie unser Beispiel. |
Tipp: Ein weit verbreitetes Anti-Pattern: Berechtigungslogik direkt IM Controller mit verschachtelten if-Anweisungen prüfen. Voters halten diese Logik an GENAU EINER Stelle, testbar isoliert (Kapitel 42-43) und wiederverwendbar über MEHRERE Controller hinweg.