Sicurezza¶
Il sistema di sicurezza di Symfony è incredibilmente potente, ma può anche essere difficile da configurare. In questo capitolo si vedrà come impostare passo-passo la sicurezza di un’applicazione, dalla configurazione del firewall a come caricare utenti per negare l’accesso e recuperare un oggetto utente. A seconda dei bisogni, a volte la prima configurazione potrebbe essere difficoltosa. Ma, una volta a posto, il sistema di sicurezza di Symfony sarà flessibile e (speriamo) divertente.
Questa guida è divisa in alcune sezioni:
- Preparazione di
security.yml
(autenticazione); - Negare l’accesso all’applicazione (autorizzazione);
- Recuperare l’oggetto corrispondente all’utente corrente
Successivamente ci saranno un certo numero di piccole (ma interessanti) sezioni, come logout e codifica delle password.
1) Preparazione di security.yml (autenticazione)¶
Il sistema di sicurezza è configurato in app/config/security.yml
. La configurazione
predefinita è simile a questa:
La voce firewalls
è il cuore della configurazione della sicurezza. Il firewall
dev
non è importante, serve solo ad assicurarsi che gli strumenti di sviluppo di Symfony,
che si trovano sotto URL come /_profiler
e /_wdt
, non siano
bloccati.
Tip
Si può anche far corrispondere la richiesta ad altri dettagli (p.e. l’host). Per maggiori informazioni ed esempi, leggere /cookbook/security/firewall_restriction.
Tutti gli altri URL saranno gestiti dal firewall default
(l’assenza della chiave pattern
vuol dire che corrisponde a ogni URL). Si può pensare al firewall come il proprio
sistema di sicurezza e quindi solitamente ha senso avere un singolo firewall.
Ma questo non vuol dire che ogni URL richieda autenticazione e quindi la voce anonymous
si occupa di questo. In effetti, se ora si apre l’homepage, si potrà
accedere e si vedrà che si è “autenticati” come anon.
. Non lasciarsi
ingannare dalla parola “Yes” vicino ad “Authenticated”, si è ancora un utente anonimo:
Più avanti si vedrà come negare l’accesso ad alcuni URL o controllori.
Tip
La sicurezza è altamente configurabile e c’è una guida di riferimento alla configurazione della sicurezza, che mostra tutte le opzioni, con spiegazioni aggiuntive.
A) Configurare il modo in cui gli utenti si autenticano¶
Il lavoro principale di un firewall è quello di configurare il modo in cui gli utenti si autenticheranno. Useranno un form? Http Basic? Il token di un’API? Tutti questi metodi insieme?
Iniziamo con Http Basic (il caro vecchio popup).
Per attivarlo, aggiungere la voce http_basic
nel firewall:
Facile! Per fare una prova, si deve richiedere che un utente sia connesso per poter vedere
una pagina. Per rendere le cose interessanti, creare una nuova pagina su /admin
. Per
esempio, se si usano le annotazioni, creare qualcosa come questo:
// src/AppBundle/Controller/DefaultController.php
// ...
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Component\HttpFoundation\Response;
class DefaultController extends Controller
{
/**
* @Route("/admin")
*/
public function adminAction()
{
return new Response('Pagina admin!');
}
}
Quindi, aggiungere a security.yml
una voce access_control
che richieda all’utente
di essere connesso per poter accedere a tale URL:
Note
La questione ROLE_ADMIN
e l’accesso negato saranno analizzati
più avanti, nella sezione 2) Accesso negato, ruoli e altre autorizzazioni.
Ottimo! Ora, se si va su /admin
, si vedrà il popup HTTP Basic:
Ma come si può entrare? Da dove vengono gli utenti?
Tip
E se invece si volesse un form di login tradizionoale? Nessun problema! Vedere /cookbook/security/form_login_setup. Che altri metodi sono supportati? Vedere riferimento sulla configurazione oppure costruire un proprio.
B) Configurare come vengono caricati gli utenti¶
Quando si inserisce il proprio nome utente, Symfony deve caricare le informazioni da qualche parte. Questo viene chiamato “fornitore di utenti” ed è compito dello sviluppatore configurarlo. Symfony ha un modo predefinito di caricare utenti dalla base dati, ma si può anche creare il proprio fornitore di utenti.
Il modo più facile (ma anche più limitato) è di configurare Symfony per caricare utenti inseriti
direttamente nel file security.yml
. Questo fornitore è chiamato “in memoria”,
ma è meglio pensare a esso come fornitore “in configurazione”:
Come per i firewalls
, si possono avere più providers
, ma probabilmente
ne basterà uno solo. Se si ha bisogno di più fornitori, si può configurare il fornitore
usato dal firewall, sotto la voce provider
(p.e.
provider: in_memory
).
Provare a entrare con nome utente admin
e password kitten
. Si dovrebbe
vedere un errore!
No encoder has been configured for account “SymfonyComponentSecurityCoreUserUser”
Per risolvere, aggiungere una chiave encoders
:
I fornitori di utenti caricano le informazioni dell’utente e le inseriscono in un oggetto User
. Se
si caricano utenti dalla base dati
o da altre sorgenti, si userà una propria
classe personalizzata. Se invece si usa il fornitore “in memoria”, esso
restituirà un oggetto Symfony\Component\Security\Core\User\User
.
Qualunque sia la classe di User
, si deve dire a Symfony quale algoritmo sia stato
usato per codificare le password. In questo caso, le password sono in chiaro,
ma tra un attimo faremo in modo di usare bcrypt
.
Se ora si aggiorna, ci si troverà dentro! La barra di debug del web fornirà informazioni sul nome dell’utente e sui suoi ruoli:
Poiché questo URL richiede ROLE_ADMIN
, se si prova a entrare come ryan
ci si
vedrà negato l’accesso. Lo vedremo più avanti (Proteggere schemi di URL (access_control)).
Caricare utenti dalla base dati¶
Se si vogliono caricare gli utenti usando l’ORM di Doctrine, è facile! Vedere /cookbook/security/entity_provider per tutti i dettagli.
C) Codifica delle password¶
Che gli utenti siano dentro a security.yml
, in una base dati o da qualsiasi altra
parte, se ne vorranno codificare le password. Il miglior algoritmo da usare è
bcrypt
:
Ovviamente, sarà ora necessario codificare le password con tale algoritmo. Per gli utenti inseriti a mano, si può usare uno strumento online, che restituirà qualcosa del genere:
Tutto funzionerà come prima. Ma se si hanno utenti dinamici (p.e. da base dati), come si fa a codificare ogni password prima dell’inserimento? Nessun problema, vedere Codifica dinamica di una password per i dettagli.
Tip
Gli algoritmi supportati dipendono dalla versione di PHP, ma
includono gli algoritmi restituiti dalla funzione :phpfunction:`hash_algos`,
più alcuni altri (come bcrypt). Vedere la voce encoders
nella sezione
riferimento della sicurezza
per degli esempi.
Si possono anche usare algoritmi differenti per singolo utente. Vedere /cookbook/security/named_encoders per maggiori dettagli.
D) Configurazione conclusa!¶
Congratulazioni! Ora di dispone un sistema di autenticazione funzionante, che usa Http
Basic e carica utenti dal file security.yml
.
I prossimi passi possono variare:
- Configurare un modo diverso per il login, come un form di login o qualcosa di completamente personalizzato;
- Caricare utenti da un’altra sorgente, come la base dati o un’altra sorgente;
- Imparare come negare l’accesso, caricare l’oggetto
User
e trattare con i ruoli nella sezione autorizzazione.
2) Accesso negato, ruoli e altre autorizzazioni¶
Ora gli utenti possono accedere all’applicazione usando http_basic
o un altro metodo.
Ottimo! Ora, occorre imparare come negare l’accesso e lavorare con l’oggetto User
.
Questo processo prende il nome di autorizzazione e spetta a esso decidere se un utente possa
accedere a una determinata risorsa (un URL, un oggetto del modello, una chiamata a un metodo, ...).
Il processo di autorizzazione ha due lati:
- L’utente riceve uno specifico insieme di ruoli, quando entra (p.e.
ROLE_ADMIN
). - Si aggiunge codice in modo che una risorsa (come un URL o un controllore) richieda uno specifico
“attributo” (solitamente un ruolo, come
ROLE_ADMIN
) per potervi accedere.
Tip
Oltre ai ruoli (come ROLE_ADMIN
), si può proteggere una risorsa
tramite altri attributi/stringhe (come EDIT
) e usare i votanti o il sistema
ACL di Symfony per dar loro un significato. Questo può essere utile nel caso serva
verificare se l’utente A possa modificare un oggetto B (p.e. un prodotto con un determinato id).
Vedere Access Control List (ACL): proteggere singoli oggetti della base dati.
Ruoli¶
Quando un utente entra, riceve un insieme di ruoli (p.e. ROLE_ADMIN
). Nell’esempio
precedente, tali ruoli sono scritti a mano in security.yml
. Se si caricano
utenti dalla base dati, probabilmente saranno memorizzati in una colonna
della tabella.
Caution
Tutti i ruoli assegnati devono avere il prefisso ROLE_
.
In caso contrario, non possono essere gestiti dal sistema di sicurezza di Symfony
(a meno che non si faccia qualcosa di avanzato, assegnare un
ruolo come PIPPO
a un utente e poi verificare PIPPO
, come descritto
successivamente non funzionerà).
I ruoli sono semplici e sono di base stringhe inventate e usate come necessario.
Per esempio, per poter iniziare a limitare l’accesso alla sezione amministrativa di un blog,
si può proteggere tale sezione usando un ruolo ROLE_BLOG_ADMIN
.
Non occorre definire tale ruolo in altri posti, basta iniziare a
usarlo.
Tip
Assicurarsi che ciascun utente abbia almeno un ruolo, altrimenti sembrerà che l’utente
non sia autenticato. Una convenzione tipica consiste nell’assegnare a ogni
utente ROLE_USER
.
Si può anche specificare una gerarchia di ruoli, in cui determinati ruoli ne hanno automaticamente anche altri.
Aggiungere codice per negare l’accesso¶
Ci sono due modi per negare accesso a qualcosa:
- access_control in security.yml
consente di proteggere schemi di URL (p.e.
/admin/*
). È facile, ma meno flessibile; - nel codice, tramite il servizio security.context.
Proteggere schemi di URL (access_control)¶
Il modo più semplice per proteggere parti di un’applicazione è proteggere un intero
schema di URL. L’abbiamo visto in precedenza, quando abbiamo richiesto che ogni URL
corrispondente all’espressione regolare ^/admin
richieda ROLE_ADMIN
:
Questo va benissimo per proteggere intere sezioni, ma si potrebbero anche voler proteggere singoli controllori.
Si possono definire quanti schemi di URL si vuole, ciascuno con un’espressione regolare.
Tuttavia, solo uno di questi avrà una corrispondenza. Symfony inizierà cercando
dalla cima e si fermerà non appena troverà una voce di access_control
che
corrisponda all’URL.
Aggiungendo un ^
iniziale, solo gli URL che iniziano con lo
schema corrisponderanno. Per esempio, un percorso /admin
(senza
^
) corrisponderebbe ad /admin/pippo
, ma anche a URL come /pippo/admin
.
Proteggere controllori e altro codice¶
Si può negare accesso da dentro un controllore:
// ...
public function helloAction($name)
{
// Il secondo parametro si usa per specificare l'oggetto su cui si testa il ruolo
$this->denyAccessUnlessGranted('ROLE_ADMIN', null, 'Non si può accedere a questa pagina!');
// Vecchio modo :
// if (false === $this->get('security.authorization_checker')->isGranted('ROLE_ADMIN')) {
// throw $this->createAccessDeniedException('Non si può accedere a questa pagina!');
// }
// ...
}
New in version 2.6: Il metodo denyAccessUnlessGranted()
è stato introdotto in Symfony 2.6. In precedenza (ma anche
ora), si poteva verificare l’accesso direttamente e sollevare AccessDeniedException
, come
mostrato nell’esempio preedente).
New in version 2.6: Il servizio security.authorization_checker
è stato introdotto in Symfony 2.6. Prima
di Symfony 2.6, si doveva usare il metodo isGranted()
del servizio security.context
.
Il metodo :method:`Symfony\\Bundle\\FrameworkBundle\\Controller\\Controller::createAccessDeniedException`
crea uno speciale oggetto Symfony\Component\Security\Core\Exception\AccessDeniedException
,
che alla fine lancia una risposta HTTP 403 in Symfony.
Ecco fatto! Se l’utente non è ancora loggato, gli sarà richiesto il login (p.e. rinviato alla pagina di login). Se invece è loggato, gli sarà mostrata una pagina di errore 403 (che si può personalizzare).
Grazie a SensioFrameworkExtraBundle, si può anche proteggere un controllore tramite annotazioni:
// ...
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
/**
* @Security("has_role('ROLE_ADMIN')")
*/
public function helloAction($name)
{
// ...
}
Per maggiori informazioni, vedere la documentazione di FrameworkExtraBundle.
Controllo degli accessi nei template¶
Se si vuole verificare in un template che l’utente corrente abbia un ruolo, usare la funzione aiutante predefinita:
Se si usa questa funzione non essendo dietro a un firewall, sarà lanciata un’ecceezione. È quindi sempre una buona idea avere almeno un firewall principale, che copra tutti gli URL (come mostrato in questo capitolo).
Caution
Prestare attenzione nel layout e nelle pagine di errore! A causa di alcuni dettagli
interno di Symfony, per evitare di rompere le pagine di errore in ambiente prod
,
verificare prima se sia definito app.user
:
{% if app.user and is_granted('ROLE_ADMIN') %}
Proteggere altri servizi¶
In Symfony, ogni cosa può essere protetta facendo qualcosa di simile a questo. Per esempio, si supponga di avere un servizio (cioè una classe PHP), il cui compito è inviare email. Si può restringere l’uso di questa classe, non importa dove venga usata, solo ad alcuni utenti.
Per maggiori informazioni, vedere /cookbook/security/securing_services.
Verificare se un utente sia connesso (IS_AUTHENTICATED_FULLY)¶
Finora, i controlli sugli accessi sono stati basati su ruoli, stringhe che iniziano con
ROLE_
e assegnate agli utenti. Se invece si vuole solo verificare se un
utente sia connesso (senza curarsi dei ruoli), si può usare
IS_AUTHENTICATED_FULLY
:
// ...
public function helloAction($name)
{
if (!$this->get('security.authorization_checker')->isGranted('IS_AUTHENTICATED_FULLY')) {
throw $this->createAccessDeniedException();
}
// ...
}
Tip
Si può usare questo metodo anche in access_control
.
IS_AUTHENTICATED_FULLY
non è un ruolo, ma si comporta come tale ed è assegnato a ciascun
utente che sia sia connesso. In effetti, ci sono tre attributi
speciali di questo tipo:
IS_AUTHENTICATED_REMEMBERED
: Assegnato a tutti gli utenti connessi, anche se si sono connessi tramite un cookie “ricordami”. Anche se non si usa la funzionalità “ricordami”, lo si può usare per verificare se l’utente sia connesso.IS_AUTHENTICATED_FULLY
: Simile aIS_AUTHENTICATED_REMEMBERED
, ma più forte. Gli utenti connessi tramite un cookie “ricordami” avrannoIS_AUTHENTICATED_REMEMBERED
, ma nonIS_AUTHENTICATED_FULLY
.IS_AUTHENTICATED_ANONYMOUSLY
: Assegnato a tutti gli utenti (anche quelli anonimi). Utile per mettere URL in una lista bianca per garantire accesso, alcuni dettagli sono in /cookbook/security/access_control.
Si possono anche usare espressioni nei template:
Per maggiori dettagli su espressioni e sicurezza, vedere book-security-expressions.
Access Control List (ACL): proteggere singoli oggetti della base dati¶
Si immagini di progettare un blog in cui gli utenti possono commentare i post. Si vuole anche che un utente sia in grado di modificare i propri commenti, ma non quelli di altri utenti. Inoltre, come utente amministratore, si vuole essere in grado di modificare tutti i commenti.
Per la realizzazione, si hanno due opzioni:
- I votanti consentono di usare logica di business (p.e. l’utente può modificare i suoi commenti perché ne è il creatore) per stabilire l’accesso. Probabilmente si userà questa opzione, è abbastanza flessibile per risolvere la situazione.
- Le ACL consentono di creare una struttura di base dati in cui si può assegnare qualsiasi accesso (p.e. EDIT, VIEW) a quasliasi utente su qualsiasi oggetto del sistema. Usarla se si ha bisogno che l’utente amministratore possa garantire accessi personalizzati nel sistema, tramite una qualche interfaccia di amministrazione.
In entrambi i casi, occorre comunque negare l’accesso usando metodi simili a quelli visti in precedenza.
Recuperare l’oggetto utente¶
New in version 2.6: Il servizio security.token_storage
è stato introdotti in Symfony 2.6. Prima
di Symfony 2.6, si doveva usare il metodo getToken()
del servizio security.context
.
Dopo l’autenticazione, si può accedere all’oggetto User
dell’uente corrente
tramite il servizio security.context
. Da dentro un controllore, Sarà una
cosa simile:
public function indexAction()
{
if (!$this->get('security.authorization_checker')->isGranted('IS_AUTHENTICATED_FULLY')) {
throw $this->createAccessDeniedException();
}
$user = $this->getUser();
// il precedente è una scorciatoia per questo
$user = $this->get('security.token_storage')->getToken()->getUser();
}
Tip
L’oggetto e la classe dell’utente dipenderanno dal proprio fornitore di utenti.
Ora si possono chiamare i metodi desiderati sul proprio oggetto utente. Per esempio,
se il proprio oggetto utente ha un metodo getFirstName()
, lo si può usare:
use Symfony\Component\HttpFoundation\Response;
public function indexAction()
{
// ...
return new Response('Ciao '.$user->getFirstName());
}
Verificare sempre se l’utente è connesso¶
È importante verificare prima se l’utente sia autenticato. Se non lo è,
$user
sarà null
oppure la stringa anon.
. Come? Esatto,
c’è una stranezza. Se non si è leggati, l’utente è tecnicamente la stringa
anon.
, anche se la scorciatoia getUser()
del controllore la converte in
null
per convenienza.
Il punto è questo: verificare sempre se l’utente sia connesso, prima di usare
l’oggetto User
e usare il metodo isGranted
(o
access_control) per farlo:
// Usare questo per vedere se l'utente sia connesso
if (!$this->get('security.context')->isGranted('IS_AUTHENTICATED_FULLY')) {
throw new AccessDeniedException();
}
// Non verificare mai l'oggetto User per verdere se l'utente sia connesso
if ($this->getUser()) {
}
Recuperare l’utente in un template¶
In un template Twig, si può accedere all’oggetto tramite :ref:`app.user <reference-twig-global-app>`_:
Logout¶
Solitamente, si desidera che gli utenti possano eseguire un logout. Per fortuna,
il firewall può gestirlo automaticamente, se si attiva il parametro
logout
nella configurazione:
Quindi, si deve creare una rotta per tale URL (non serve invece un controllore):
Ecco fatto! Se l’utente va su /logout
(o sull’URL configurato
in path
), Symfony disconnetterà l’utente corrente.
Una volta eseguito il logout, l’utente sarà rinviato al percorso
definito nel parametro target
(p.e. su homepage
).
Tip
Se si ha bisogno di fare qualcosa d’altro dopo il logout, si può
specificare un gestore di logout, aggiungendo la voce success_handler
e puntandola a un servizio, che implementi
Symfony\Component\Security\Http\Logout\LogoutSuccessHandlerInterface
.
Vedere Security Configuration Reference.
Codifica dinamica di una password¶
Se, per esempio, gli utenti sono memorizzati in una base dati, occorrerà codificare le loro password, prima di inserirle. Non importa quale algoritmo sia configurato per l’oggetto utente, l’hash della password può sempre essere determinato nel modo seguente, in un controllore:
// qualunque sia il *proprio* oggetto User
$user = new AppBundle\Entity\User();
$plainPassword = 'ryanpass';
$encoder = $this->container->get('security.password_encoder');
$encoded = $encoder->encodePassword($user, $plainPassword);
$user->setPassword($encoded);
New in version 2.6: Il servizio security.password_encoder
è stato introdotto in Symfony 2.6.
Per poter funzionare, assicurarsi di avere un codificatore per la classe
utente (p.e. AppBundle\Entity\User
) configurato sotto la voce encoders
in app/config/security.yml
.
L’oggetto $encoder
ha anche un metodo isPasswordValid
, che accetta
l’oggetto User
come primo parametro e la password in chiaro, da verificare,
come secondo parametro.
Caution
Quando si consente a un utente di inviare una password in chiaro (p.e. un form di registrazione, un form di cambio password), si deve avere una validazione che garantisca una lunghezza massima della password di 4096 caratteri. Maggiori dettagli su implementare una semplice form di registrazione.
Gerarchia dei ruoli¶
Invece di associare molti ruoli agli utenti, si possono definire regole di ereditarietà dei ruoli, creando una gerarchia:
In questa configurazione, gli utenti con il ruolo ROLE_ADMIN
avranno anche il ruolo
ROLE_USER
. Il ruolo ROLE_SUPER_ADMIN
ha ROLE_ADMIN
, ROLE_ALLOWED_TO_SWITCH
e ROLE_USER
(ereditato da ROLE_ADMIN
).
Autenticazione senza stato¶
Symfony si appoggia a un cookie (la sessione) per persistere il contesto di sicurezza dell’utente. Se però si usano certificati o autenticazione HTTP, per esempio, non serve persistenza, perché le credenziali sono disponibili a ogni richiesta. In tal caso, e non si ha bisogno di memorizzare altro tra una richiesta e l’altro, si può attivare l’autenticazione senza stato (che vuol dire che Symfony non creerà alcun cookie):
Note
Se si usa un form di login, Symfony creerà un cookie anche se si imposta
stateless
a true
.
Verificare vulnerabilità note nelle dipendenze¶
New in version 2.5: Il comando security:check
è stato introdotto in Symfony 2.5. Questo comando è
incluso in SensioDistributionBundle
, bundle che va registrato nell’applicazione
per consentire l’utilizzo del comando stesso.
Quando si hanno molte dipendenze in progetti Symfony, alcune potrebbero
contenere delle vulnerabilità. Per questo motivo, Symfony include un comando chiamato
security:check
, che verifica il file composer.lock
e trova eventuali
vulnerabilità nelle dipendenze installate:
$ php app/console security:check
Una buona pratica di sicurezza consiste nell’eseguire regolarmente questo comando, per poter aggiornare o sostituire dipendenze compromesse il prima possibile. Internamente, questo comando usa la base dati degli avvisi di sicurezza pubblicato dall’organizzazione FriendsOfPHP.
Tip
Il comando security:check
esce con un codice diverso da zero, se alcune
dipendenze sono afflitte da problemi noti di sicurezza.
Quindi, si può facilmente integrare in un processo di build.
Considerazioni finali¶
Ora sappiamo più di qualche base sulla sicurezza. Le parti più difficili coinvolgono i requisiti personalizzati: una strategia di autenticazione personalizzata (p.e. token API), logica di autorizzazione complessa e molte altre cose (perché la sicurezza è complessa!).
Fortunatamente, ci sono molte ricette sulla sicurezza, che descrivono molte di queste situazioni. Inoltre, vedere la sezione di riferimento della sicurezza. Molte opzioni non hanno dettagli specifici, ma analizzare l’intero albero di configurazione potrebbe essere utile.
Buona fortuna!
Saperne di più con il ricettario¶
- Forzare HTTP/HTTPS
- Impersonare un utente
- /cookbook/security/voters_data_permission
- Access Control List (ACL)
- /cookbook/security/remember_me
- /cookbook/security/multiple_user_providers