# Bezpieczeństwo Symfony w 2026: Voters, Firewalle i Pytania Rekrutacyjne > Kompleksowy przewodnik po systemie bezpieczeństwa Symfony: firewalle, votery, atrybut IsGranted, strategie decyzyjne, debugowanie w Twig 7.4 oraz pytania z rozmów kwalifikacyjnych dla programistów PHP. - Published: 2026-06-18 - Updated: 2026-06-18 - Author: SharpSkill - Tags: symfony, security, php, voters, firewall, authentication, interview - Reading time: 13 min --- Komponent Security należy do najbardziej rozbudowanych i najczęściej omawianych elementów Symfony podczas rozmów kwalifikacyjnych dla programistów backendowych w 2026 roku. Uwierzytelnianie, autoryzacja, firewalle, votery — każda warstwa opiera się na precyzyjnych abstrakcjach, których opanowanie odróżnia seniorów od programistów na poziomie średniozaawansowanym. Wraz z wydaniem Symfony 7.4 LTS system bezpieczeństwa zyskał lepszą obserwowalność dzięki debugowaniu decyzji dostępu bezpośrednio w Twig, zachowując jednocześnie rozszerzalną architekturę, która stanowi siłę frameworka od lat. Niniejszy artykuł analizuje dogłębnie wewnętrzne mechanizmy systemu bezpieczeństwa Symfony: konfigurację firewalli, zarządzanie tokenami dostępu, projektowanie voterów, strategie decyzyjne oraz najlepsze praktyki wzmacniania zabezpieczeń. Każdy koncept jest zilustrowany kodem gotowym do wdrożenia produkcyjnego i zestawiony z pytaniami pojawiającymi się na rozmowach kwalifikacyjnych. > **Symfony 7.4 LTS — co nowego w bezpieczeństwie** > > Symfony 7.4 LTS, wydane w listopadzie 2025 z wsparciem do listopada 2029, konsoliduje kluczowe zmiany w komponencie Security wprowadzone od wersji 6.0: zunifikowane authenticatory, natywny atrybut IsGranted, debugowanie decyzji dostępu w Profilerze i Twig oraz udoskonalony system voterów. Ta wersja stanowi punkt odniesienia dla projektów produkcyjnych i pytań rekrutacyjnych w 2026 roku. ## Firewalle: pierwsza linia obrony System bezpieczeństwa Symfony opiera się na architekturze firewalli deklarowanych w pliku `security.yaml`. Każdy firewall definiuje zakres ochrony dla określonego zestawu tras, z własnymi regułami uwierzytelniania i kontroli dostępu. Kolejność deklaracji ma kluczowe znaczenie: Symfony przechodzi przez firewalle sekwencyjnie i stosuje pierwszy, którego wzorzec pasuje do URL żądania. ```yaml # config/packages/security.yaml security: firewalls: dev: pattern: ^/(_(profiler|wdt))/ security: false api: pattern: ^/api/ stateless: true custom_authenticators: - App\Security\ApiTokenHandler main: lazy: true provider: app_user_provider form_login: login_path: app_login check_path: app_login logout: path: app_logout access_control: - { path: ^/api/public, roles: PUBLIC_ACCESS } - { path: ^/api/, roles: ROLE_API_USER } - { path: ^/admin, roles: ROLE_ADMIN } - { path: ^/dashboard, roles: ROLE_USER } ``` W tej konfiguracji współistnieją trzy firewalle. Firewall `dev` całkowicie wyłącza bezpieczeństwo dla tras Profilera i Web Debug Toolbar, eliminując wszelkie zakłócenia w środowisku deweloperskim. Firewall `api` chroni endpointy API uwierzytelnianiem bezstanowym opartym na tokenie custom. Firewall `main` obsługuje klasyczne uwierzytelnianie formularzowe z sesją dla interfejsu webowego. Sekcja `access_control` definiuje globalne reguły dostępu, ewaluowane po uwierzytelnieniu. Kolejność reguł jest kluczowa: wygrywa pierwsze dopasowanie. Reguła `PUBLIC_ACCESS` na `/api/public` pozwala na dostęp bez uwierzytelnienia, nawet gdy firewall `api` jest aktywny. Ta subtelność stanowi częsty temat pytań rekrutacyjnych. > **Ostrożnie z security: false na trasach API** > > Flaga `security: false` całkowicie wyłącza komponent Security dla odpowiadających tras: brak tokenu, brak użytkownika, brak votera. Ta opcja nie powinna być nigdy stosowana na trasach produkcyjnych wystawionych publicznie. Aby umożliwić nieuwierzytelniony dostęp do wybranych tras API, należy użyć `PUBLIC_ACCESS` w `access_control`, zachowując aktywny firewall. ## Stateless kontra Stateful: dwa paradygmaty uwierzytelniania Wybór między uwierzytelnianiem stateless a stateful determinuje architekturę bezpieczeństwa aplikacji. W paradygmacie stateful (firewall `main`) Symfony przechowuje token uwierzytelniania w sesji PHP. Każde kolejne żądanie odbudowuje kontekst bezpieczeństwa z sesji, bez ponownego uwierzytelniania. Flaga `lazy: true` optymalizuje ten proces, ładując sesję dopiero wtedy, gdy weryfikacja autoryzacji jest faktycznie wymagana. W paradygmacie stateless (firewall `api`) każde żądanie przenosi własne dane uwierzytelniające — token Bearer, klucz API, podpis JWT. Po stronie serwera nie jest tworzona żadna sesja. Ten model naturalnie pasuje do architektur rozproszonych, mikroserwisów i klientów mobilnych, ale wymaga, aby każde żądanie było samowystarczalne z punktu widzenia uwierzytelniania. Wybór między tymi paradygmatami nie wynika z preferencji technicznej, lecz z ograniczeń architektonicznych. Monolityczna aplikacja z interfejsem webowym preferuje tryb stateful ze względu na prostotę zarządzania. API konsumowane przez heterogenicznych klientów wymusza tryb stateless dla horyzontalnej skalowalności. ## Access Token Handler: uwierzytelnianie custom Symfony 6.2 wprowadziło system Access Token Handler, unifikujący zarządzanie tokenami uwierzytelniania poprzez przejrzysty interfejs. Mechanizm ten zastąpił starsze Guard Authenticatory, oferując bardziej bezpośrednią integrację z komponentem Security. ```php // src/Security/ApiTokenHandler.php namespace App\Security; use App\Repository\ApiTokenRepository; use Symfony\Component\Security\Core\Exception\BadCredentialsException; use Symfony\Component\Security\Http\AccessToken\AccessTokenHandlerInterface; use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge; final readonly class ApiTokenHandler implements AccessTokenHandlerInterface { public function __construct( private ApiTokenRepository $repository, ) {} public function getUserBadgeFrom(#[\SensitiveParameter] string $accessToken): UserBadge { $token = $this->repository->findOneByValue($accessToken); if ($token === null || !$token->isValid()) { throw new BadCredentialsException('Invalid or expired token.'); } return new UserBadge($token->getUser()->getUserIdentifier()); } } ``` Interfejs `AccessTokenHandlerInterface` wymaga jednej metody: `getUserBadgeFrom`. Metoda ta otrzymuje surowy token wyekstrahowany z żądania (domyślnie nagłówek `Authorization: Bearer xxx`) i musi zwrócić obiekt `UserBadge` zawierający identyfikator powiązanego użytkownika. Atrybut `#[\SensitiveParameter]` oznacza token jako dane wrażliwe, uniemożliwiając jego pojawienie się w stack trace'ach i logach — dobra praktyka bezpieczeństwa wprowadzona w PHP 8.2. Wzorzec jest celowo minimalistyczny: walidacja tokenu (istnienie, wygaśnięcie, unieważnienie) pozostaje w repozytorium lub dedykowanym serwisie. Handler jedynie tłumaczy token na tożsamość użytkownika. Ta separacja odpowiedzialności ułatwia testy jednostkowe i respektuje zasadę pojedynczej odpowiedzialności. ## Votery: granularna kontrola dostępu Votery stanowią centralny mechanizm autoryzacji w Symfony. W przeciwieństwie do statycznych ról definiowanych w `access_control`, votery umożliwiają dynamiczną logikę decyzyjną opartą na kontekście: bieżącym użytkowniku, obiekcie docelowym i żądanej akcji. Każdy voter odpowiada na precyzyjne pytanie: "Czy ten użytkownik może wykonać tę akcję na tym obiekcie?" ```php // src/Security/Voter/PostVoter.php namespace App\Security\Voter; use App\Entity\Post; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Authorization\Voter\Vote; use Symfony\Component\Security\Core\Authorization\Voter\Voter; use Symfony\Component\Security\Core\User\UserInterface; final class PostVoter extends Voter { public const EDIT = 'POST_EDIT'; public const DELETE = 'POST_DELETE'; public const PUBLISH = 'POST_PUBLISH'; protected function supports(string $attribute, mixed $subject): bool { return in_array($attribute, [self::EDIT, self::DELETE, self::PUBLISH]) && $subject instanceof Post; } protected function voteOnAttribute( string $attribute, mixed $subject, TokenInterface $token, ?Vote $vote = null, ): bool { $user = $token->getUser(); if (!$user instanceof UserInterface) { $vote?->addReason('User is not authenticated.'); return false; } /** @var Post $post */ $post = $subject; return match ($attribute) { self::EDIT => $this->canEdit($post, $user, $vote), self::DELETE => $this->canDelete($post, $user, $vote), self::PUBLISH => $this->canPublish($post, $user, $vote), default => false, }; } private function canEdit(Post $post, UserInterface $user, ?Vote $vote): bool { if ($post->getAuthor() === $user) { $vote?->addReason('User is the author of the post.'); return true; } $vote?->addReason('User is not the author.'); return false; } private function canDelete(Post $post, UserInterface $user, ?Vote $vote): bool { if (in_array('ROLE_ADMIN', $user->getRoles())) { $vote?->addReason('User has ROLE_ADMIN.'); return true; } if ($post->getAuthor() === $user && !$post->isPublished()) { $vote?->addReason('Author can delete unpublished posts.'); return true; } $vote?->addReason('Only admins or authors of unpublished posts can delete.'); return false; } private function canPublish(Post $post, UserInterface $user, ?Vote $vote): bool { if (in_array('ROLE_EDITOR', $user->getRoles())) { $vote?->addReason('User has ROLE_EDITOR.'); return true; } $vote?->addReason('Only editors can publish posts.'); return false; } } ``` Kilka elementów tego votera zasługuje na szczegółową analizę. Parametr `Vote` (wprowadzony w Symfony 7.1) pozwala dołączać tekstowe uzasadnienia do każdej decyzji, ułatwiając debugowanie w środowisku deweloperskim i audytowalność w produkcji. Metoda `supports` filtruje wywołania: voter aktywuje się wyłącznie dla atrybutów `POST_EDIT`, `POST_DELETE` i `POST_PUBLISH` powiązanych z instancją `Post`. Wyrażenie `match` w `voteOnAttribute` deleguje logikę do wyspecjalizowanych metod prywatnych, z których każda enkapsuluje reguły biznesowe danej akcji. Ta struktura sprawia, że voter jest czytelny i łatwo rozszerzalny: dodanie nowej akcji ogranicza się do zadeklarowania stałej, dodania przypadku w `match` i napisania metody prywatnej. Logika `canDelete` ilustruje częsty wzorzec: łączenie weryfikacji roli z weryfikacją własności. Administrator może usunąć dowolny artykuł, ale autor może usunąć wyłącznie własne nieopublikowane artykuły. Tego typu kontekstowa reguła jest niemożliwa do wyrażenia za pomocą prostych ról w `access_control`. ## Atrybut IsGranted: deklaratywna kontrola dostępu Atrybut `#[IsGranted]` stosuje kontrolę dostępu bezpośrednio na poziomie kontrolera lub metody, pełniąc tę samą funkcję co dawne adnotacje bezpieczeństwa, ale z natywną składnią PHP. Symfony ewaluuje wyrażenie przed wykonaniem metody i zwraca odpowiedź 403, jeśli weryfikacja nie przejdzie. ```php // src/Controller/PostController.php namespace App\Controller; use App\Entity\Post; use App\Security\Voter\PostVoter; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Attribute\Route; use Symfony\Component\Security\Http\Attribute\IsGranted; #[Route('/post')] final class PostController extends AbstractController { #[Route('/{id}/edit', methods: ['GET', 'POST'])] #[IsGranted(PostVoter::EDIT, subject: 'post', message: 'You cannot edit this post.')] public function edit(Post $post): Response { // User is guaranteed to have edit permission at this point return $this->render('post/edit.html.twig', [ 'post' => $post, ]); } #[Route('/{id}/publish', methods: ['POST'])] #[IsGranted(PostVoter::PUBLISH, subject: 'post')] public function publish(Post $post): Response { // Only editors reach this code $post->setPublished(true); // ... return $this->redirectToRoute('post_show', ['id' => $post->getId()]); } } ``` Parametr `subject: 'post'` wiąże atrybut z parametrem metody o tej samej nazwie. Symfony automatycznie rozwiązuje encję poprzez ParamConverter i przekazuje ją voterowi jako podmiot weryfikacji. Parametr `message` personalizuje komunikat błędu 403, przydatny zarówno przy debugowaniu, jak i w logach audytowych. Ten deklaratywny styl ma istotną zaletę pod względem czytelności: reguły dostępu są widoczne w tym samym miejscu co sygnatura metody, bez konieczności inspekcji ciała funkcji. Na rozmowie kwalifikacyjnej umiejętność wyjaśnienia pełnej ścieżki — od atrybutu, przez votera, po `AccessDecisionManager` — demonstruje głębokie zrozumienie komponentu Security. ## Strategie decyzyjne: unanimous, affirmative, consensus Gdy wielu voterów wypowiada się na temat tej samej weryfikacji dostępu, `AccessDecisionManager` stosuje strategię decyzyjną w celu agregacji głosów. Symfony oferuje trzy strategie, konfigurowalne w `security.yaml`. ```yaml # config/packages/security.yaml security: access_decision_manager: strategy: unanimous allow_if_all_abstain: false ``` Strategia `affirmative` (domyślna) przyznaje dostęp, gdy choć jeden voter zagłosuje pozytywnie. Strategia `unanimous` wymaga, aby wszyscy voterzy, którzy nie wstrzymali się od głosu, zagłosowali pozytywnie. Strategia `consensus` przyznaje dostęp, gdy większość voterów zagłosuje pozytywnie. Parametr `allow_if_all_abstain` określa zachowanie, gdy wszyscy voterzy się wstrzymają. W praktyce domyślna strategia `affirmative` wystarcza w większości aplikacji. Strategia `unanimous` narzuca się w kontekstach, gdzie bezpieczeństwo ma priorytet: aplikacje finansowe, dane medyczne, systemy krytyczne. Gwarantuje ona, że żaden voter nie sprzeciwia się dostępowi, dodając warstwę obrony w głąb. Voter weryfikacji IP, voter roli i voter własności muszą wszyscy zatwierdzić dostęp, aby został przyznany. ## Debugowanie decyzji dostępu w Twig (Symfony 7.4) Symfony 7.4 wzbogaca debugowanie systemu bezpieczeństwa, eksponując uzasadnienia decyzji dostępu bezpośrednio w szablonach Twig. Parametr `Vote` dodany do voterów nabiera pełnego sensu: uzasadnienia zadeklarowane przez `$vote->addReason()` pojawiają się w Profilerze i mogą być warunkowo wyświetlane w szablonach. ```twig {# templates/post/show.html.twig #} {% if is_granted('POST_EDIT', post) %} Edit {% endif %} {% if is_granted('POST_DELETE', post) %}
{% endif %} {% if app.debug %} {# Symfony 7.4: access decision debugging in Twig #} {% set decision = is_granted_debug('POST_EDIT', post) %}
Access Decision Debug
{% endif %} ``` Funkcja `is_granted()` pozostaje standardowym punktem wejścia do weryfikacji dostępu w Twig. Jej zastosowanie w warunkach wyświetlania gwarantuje, że elementy interfejsu (przyciski, linki) są widoczne wyłącznie dla autoryzowanych użytkowników. Blok debugowania warunkowany przez `app.debug` pozwala na inspekcję szczegółów głosowania w środowisku deweloperskim bez ujawniania tych informacji w produkcji. Ten mechanizm debugowania odpowiada na powtarzający się problem w projektach Symfony: identyfikację powodu odmowy dostępu dla użytkownika. Przed Symfony 7.4 takie dochodzenie wymagało ręcznej inspekcji Profilera lub dodawania tymczasowych logów w voterach. ## Testy jednostkowe voterów Votery enkapsulują krytyczne reguły biznesowe i wymagają wyczerpującego pokrycia testami. Każda kombinacja użytkownik/akcja/obiekt musi być zweryfikowana, aby zagwarantować spójność systemu autoryzacji. ```php // tests/Security/Voter/PostVoterTest.php namespace App\Tests\Security\Voter; use App\Entity\Post; use App\Entity\User; use App\Security\Voter\PostVoter; use PHPUnit\Framework\TestCase; use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface; final class PostVoterTest extends TestCase { private PostVoter $voter; protected function setUp(): void { $this->voter = new PostVoter(); } public function testAuthorCanEditOwnPost(): void { $user = new User(); $post = (new Post())->setAuthor($user); $token = new UsernamePasswordToken($user, 'main', ['ROLE_USER']); $this->assertSame( VoterInterface::ACCESS_GRANTED, $this->voter->vote($token, $post, [PostVoter::EDIT]), ); } public function testNonAuthorCannotEditPost(): void { $author = new User(); $otherUser = new User(); $post = (new Post())->setAuthor($author); $token = new UsernamePasswordToken($otherUser, 'main', ['ROLE_USER']); $this->assertSame( VoterInterface::ACCESS_DENIED, $this->voter->vote($token, $post, [PostVoter::EDIT]), ); } public function testAdminCanDeleteAnyPost(): void { $admin = new User(); $post = (new Post())->setAuthor(new User()); $token = new UsernamePasswordToken($admin, 'main', ['ROLE_ADMIN']); $this->assertSame( VoterInterface::ACCESS_GRANTED, $this->voter->vote($token, $post, [PostVoter::DELETE]), ); } public function testAuthorCanDeleteUnpublishedPost(): void { $user = new User(); $post = (new Post())->setAuthor($user)->setPublished(false); $token = new UsernamePasswordToken($user, 'main', ['ROLE_USER']); $this->assertSame( VoterInterface::ACCESS_GRANTED, $this->voter->vote($token, $post, [PostVoter::DELETE]), ); } public function testAuthorCannotDeletePublishedPost(): void { $user = new User(); $post = (new Post())->setAuthor($user)->setPublished(true); $token = new UsernamePasswordToken($user, 'main', ['ROLE_USER']); $this->assertSame( VoterInterface::ACCESS_DENIED, $this->voter->vote($token, $post, [PostVoter::DELETE]), ); } public function testOnlyEditorCanPublish(): void { $user = new User(); $post = (new Post())->setAuthor($user); $token = new UsernamePasswordToken($user, 'main', ['ROLE_EDITOR']); $this->assertSame( VoterInterface::ACCESS_GRANTED, $this->voter->vote($token, $post, [PostVoter::PUBLISH]), ); } public function testVoterAbstainsOnUnsupportedAttribute(): void { $user = new User(); $post = new Post(); $token = new UsernamePasswordToken($user, 'main', ['ROLE_USER']); $this->assertSame( VoterInterface::ACCESS_ABSTAIN, $this->voter->vote($token, $post, ['UNSUPPORTED']), ); } } ``` Każdy test izoluje konkretną regułę biznesową. Test `testAuthorCannotDeletePublishedPost` weryfikuje ograniczenie, zgodnie z którym autor traci prawo do usunięcia po publikacji — regułę łatwo pomijaną w przypadku braku testów. Test abstencji na nieobsługiwanym atrybucie potwierdza, że voter nie ingeruje w decyzje innych voterów. Votery to czyste klasy PHP bez zależności od frameworka (w tym przykładzie brak wstrzykiwanych serwisów), co umożliwia proste testy jednostkowe z PHPUnit. W przypadku bardziej złożonych voterów wymagających serwisów (weryfikacja subskrypcji, limity kwotowe), mockowanie zależności za pomocą `createMock()` pozostaje standardowym podejściem. > **Zdarzenia bezpieczeństwa** > > Symfony emituje zdarzenia na każdym etapie procesu bezpieczeństwa: `AuthenticationSuccessEvent`, `LoginSuccessEvent`, `LogoutEvent`, `SwitchUserEvent` oraz `AccessDeniedEvent`. Zdarzenia te pozwalają dodawać logowanie, powiadomienia lub dodatkowe weryfikacje bez modyfikowania istniejących authenticatorów czy voterów. W produkcji nasłuchiwanie `AccessDeniedEvent` zasila systemy monitoringu i wykrywa próby nieautoryzowanego dostępu. ## Wzmacnianie bezpieczeństwa: UserChecker i najlepsze praktyki Poza voterami i firewallami wzmacnianie bezpieczeństwa Symfony odbywa się poprzez kilka komplementarnych mechanizmów. Interfejs `UserCheckerInterface` pozwala dodawać dodatkowe weryfikacje podczas uwierzytelniania, przed i po walidacji danych uwierzytelniających. ```php // src/Security/UserEnabledChecker.php namespace App\Security; use App\Entity\User; use Symfony\Component\Security\Core\Exception\CustomUserMessageAccountStatusException; use Symfony\Component\Security\Core\User\UserCheckerInterface; use Symfony\Component\Security\Core\User\UserInterface; final class UserEnabledChecker implements UserCheckerInterface { public function checkPreAuth(UserInterface $user): void { if (!$user instanceof User) { return; } if ($user->isBanned()) { throw new CustomUserMessageAccountStatusException( 'Your account has been banned. Contact support.' ); } } public function checkPostAuth(UserInterface $user): void { if (!$user instanceof User) { return; } if (!$user->isVerified()) { throw new CustomUserMessageAccountStatusException( 'Please verify your email address before logging in.' ); } } } ``` `UserChecker` rozdziela weryfikacje na dwie fazy: `checkPreAuth` wykonuje się przed weryfikacją hasła (przypadek zbanowanego użytkownika — nie ma sensu sprawdzać jego danych uwierzytelniających), `checkPostAuth` wykonuje się po (przypadek niezweryfikowanego e-maila — hasło jest poprawne, ale konto nie jest jeszcze aktywne). Ta separacja optymalizuje przepływ uwierzytelniania i dostarcza komunikaty błędów specyficzne dla każdej sytuacji. Uzupełniające najlepsze praktyki wzmacniania bezpieczeństwa obejmują: - **Rate limiting**: komponent RateLimiter zintegrowany z Symfony pozwala ograniczać próby logowania według IP lub identyfikatora użytkownika, chroniąc przed atakami brute force - **Ochrona CSRF**: aktywacja ochrony CSRF na wszystkich formularzach, w tym formularzu logowania, poprzez parametr `enable_csrf` firewalla - **Hashowanie haseł**: stosowanie hashera `auto`, który automatycznie wybiera najbezpieczniejszy dostępny algorytm (bcrypt lub Argon2id w zależności od instalacji PHP) - **Rotacja sekretów**: wykorzystanie Vault Symfony dla wrażliwych danych uwierzytelniających, z automatyzowaną rotacją kluczy szyfrowania - **Nagłówki bezpieczeństwa**: konfiguracja nagłówków HTTP `Content-Security-Policy`, `X-Frame-Options`, `Strict-Transport-Security` poprzez event listener na `kernel.response` ## Pytania rekrutacyjne Bezpieczeństwo Symfony stanowi nieodzowny temat rozmów kwalifikacyjnych z zakresu backendu PHP. Poniższe pytania obejmują zagadnienia oceniane przez rekruterów, od poziomu średniozaawansowanego po seniorski. **Jaka jest różnica między uwierzytelnianiem a autoryzacją w Symfony?** Uwierzytelnianie odpowiada na pytanie "Kim jesteś?" — identyfikuje użytkownika na podstawie jego danych uwierzytelniających (login/hasło, token API, certyfikat). Autoryzacja odpowiada na pytanie "Czy masz prawo?" — weryfikuje uprawnienia uwierzytelnionego użytkownika za pomocą ról, voterów i wyrażeń bezpieczeństwa. W Symfony firewall zarządza uwierzytelnianiem, podczas gdy `AccessDecisionManager` i votery zarządzają autoryzacją. **Jak działa voter i kiedy stosować go zamiast access_control?** Voter implementuje `VoterInterface` i odpowiada trzema wartościami: `ACCESS_GRANTED`, `ACCESS_DENIED` lub `ACCESS_ABSTAIN`. Votery są preferowane, gdy decyzja o dostępie zależy od kontekstu: obiektu docelowego (Post, Zamówienie), stanu tego obiektu (opublikowany, zarchiwizowany) lub relacji między użytkownikiem a obiektem (autor, menedżer). Reguły `access_control` w `security.yaml` są ograniczone do statycznych weryfikacji roli i wzorca URL. **Jaka jest rola parametru Vote wprowadzonego w Symfony 7.1?** Parametr `Vote` pozwala voterom dołączać tekstowe uzasadnienia do swoich decyzji. Uzasadnienia te pojawiają się w Profilerze Symfony i mogą być wykorzystywane w szablonach Twig do debugowania. W produkcji zasilają logi audytowe. Parametr jest opcjonalny i nullable (`?Vote $vote = null`), aby zachować wsteczną kompatybilność z istniejącymi voterami. **Jak zabezpieczyć API stateless w Symfony?** Firewall musi być skonfigurowany z `stateless: true`, aby wyłączyć zarządzanie sesją. Uwierzytelnianie opiera się na `AccessTokenHandler`, który wyciąga i waliduje token z każdego żądania. Tokeny powinny mieć ograniczony czas życia, być bezpiecznie przechowywane po stronie klienta i możliwe do unieważnienia po stronie serwera. Flaga `#[\SensitiveParameter]` na parametrze tokenu zapobiega jego wyciekowi do logów i stack trace'ów. **Jak testować votery w sposób wyczerpujący?** Votery testuje się jak standardowe klasy PHP za pomocą PHPUnit. Każdy test instancjonuje votera, buduje `UsernamePasswordToken` symulujący użytkownika z określonymi rolami, tworzy obiekt docelowy z pożądanym stanem i weryfikuje, że `vote()` zwraca oczekiwaną wartość (`ACCESS_GRANTED`, `ACCESS_DENIED` lub `ACCESS_ABSTAIN`). Pokrycie powinno obejmować wszystkie obsługiwane atrybuty, przypadki graniczne (nieuwierzytelniony użytkownik, nieobsługiwany atrybut) oraz kombinacje ról. ## Podsumowanie Komponent Security Symfony oferuje architekturę jednocześnie rygorystyczną i rozszerzalną, zdolną do obsługi scenariuszy od klasycznego uwierzytelniania formularzowego po wielokryterialne systemy autoryzacji w środowiskach rozproszonych. Jego opanowanie stanowi niekwestionowany wyznacznik poziomu senior podczas rozmów kwalifikacyjnych. Kluczowe zagadnienia do zapamiętania: - **Firewalle**: kolejność deklaracji determinuje dopasowanie; `security: false` stosować wyłącznie dla tras deweloperskich, nigdy w produkcji - **Stateless kontra stateful**: wybór zależy od architektury (monolit vs mikroserwisy), a nie od preferencji technicznej - **Access Token Handler**: interfejs `AccessTokenHandlerInterface` unifikuje zarządzanie tokenami jedną metodą, ułatwiając testy i utrzymanie - **Votery**: preferować votery dla każdej kontekstowej logiki autoryzacji; strukturyzować każdy voter metodami prywatnymi per akcja - **Parametr Vote**: wykorzystywać `addReason()` do debugowania i audytowalności decyzji dostępu - **IsGranted**: stosować kontrolę dostępu deklaratywnie na poziomie kontrolera dla maksymalnej czytelności - **Strategie decyzyjne**: wybierać `unanimous` w kontekstach wrażliwych, gdzie wymagana jest obrona w głąb - **Testy**: pokrywać każdą kombinację użytkownik/akcja/obiekt w testach jednostkowych voterów - **Wzmacnianie**: łączyć `UserChecker`, rate limiting, CSRF, nagłówki bezpieczeństwa i rotację sekretów dla obrony wielowarstwowej --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/pl/blog/symfony/symfony-security-voters-firewalls-interview-questions