Sicurezza¶
La sicurezza è una procedura che avviene in due fasi, il cui obiettivo è quello di impedire a un utente di accedere a una risorsa a cui non dovrebbe avere accesso.
Nella prima fase del processo, il sistema di sicurezza identifica chi è l’utente, chiedendogli di presentare una sorta di identificazione. Quest’ultima è chiamata autenticazione e significa che il sistema sta cercando di scoprire chi sia l’utente.
Una volta che il sistema sa chi è l’utente, il passo successivo è quello di determinare se può avere accesso a una determinata risorsa. Questa parte del processo è chiamato autorizzazione e significa che il sistema verifica se l’utente disponga dei privilegi per eseguire una certa azione.
Il modo migliore per imparare è quello di vedere un esempio, iniziamo proteggendo l’applicazione con l’autenticazione base HTTP.
Note
Il componente Security </components/security/introduction> di Symfony è disponibile come libreria PHP a sé stante, per l’utilizzo all’interno di qualsiasi progetto PHP.
Esempio di base: l’autenticazione HTTP¶
Il componente Security può essere configurato attraverso la configurazione dell’applicazione.
In realtà, per molte configurazioni standard di sicurezza basta solo usare la giusta
configurazione. La seguente configurazione dice a Symfony di proteggere qualunque URL
corrispondente a /admin/*
e chiedere le credenziali all’utente utilizzando l’autenticazione
base HTTP (cioè il classico vecchio box nome utente/password):
Tip
Una distribuzione standard di Symfony pone la configurazione di sicurezza
in un file separato (ad esempio app/config/security.yml
). Se non si ha
un file di sicurezza separato, è possibile inserire la configurazione direttamente
nel file di configurazione principale (ad esempio app/config/config.yml
).
Il risultato finale di questa configurazione è un sistema di sicurezza pienamente funzionale, simile al seguente:
- Ci sono due utenti nel sistema (
ryan
eadmin
); - Gli utenti si autenticano tramite autenticazione HTTP;
- Qualsiasi URL corrispondente a
/admin/*
è protetto e solo l’utenteadmin
può accedervi; - Tutti gli URL che non corrispondono ad
/admin/*
sono accessibili da tutti gli utenti (e all’utente non viene chiesto il login).
Di seguito si vedrà brevemente come funziona la sicurezza e come ogni parte della configurazione entra in gioco.
Come funziona la sicurezza: autenticazione e autorizzazione¶
Il sistema di sicurezza di Symfony funziona determinando l’identità di un utente (autenticazione) e poi controllando se l’utente deve avere accesso a una risorsa specifica o URL.
Firewall (autenticazione)¶
Quando un utente effettua una richiesta a un URL che è protetto da un firewall, viene attivato il sistema di sicurezza. Il compito del firewall è quello di determinare se l’utente deve o non deve essere autenticato e se deve autenticarsi, rimandare una risposta all’utente, avviando il processo di autenticazione.
Un firewall viene attivato quando l’URL di una richiesta in arrivo corrisponde
al valore pattern
dell’espressione regolare del firewall configurato. In questo esempio,
pattern
(^/
) corrisponderà a ogni richiesta in arrivo. Il fatto che il
firewall venga attivato non significa tuttavia che venga visualizzato
il box di autenticazione con nome utente e password per ogni URL. Per esempio, qualunque utente
può accedere a /foo
senza che venga richiesto di autenticarsi.
Tip
Si può anche far corrispondere una richiesta in base ad altri dettagli (p.e. l’host o il metodo). Per maggiori informazioni ed esempi, leggere /cookbook/security/firewall_restriction.
Questo funziona in primo luogo perché il firewall consente utenti anonimi, attraverso
il parametro di configurazione anonymous
. In altre parole, il firewall non richiede
all’utente di fare immediatamente un’autenticazione. E poiché non è
necessario nessun ruolo
speciale per accedere a /foo
(sotto la sezione access_control
), la richiesta
può essere soddisfatta senza mai chiedere all’utente di autenticarsi.
Se si rimuove la chiave anonymous
, il firewall chiederà sempre
l’autenticazione all’utente.
Controlli sull’accesso (autorizzazione)¶
Se un utente richiede /admin/foo
, il processo ha un diverso comportamento.
Questo perché la sezione di configurazione access_control
dice
che qualsiasi URL che corrispondono allo schema dell’espressione regolare ^/admin
(cioè /admin
o qualunque URL del tipo /admin/*
) richiede il ruolo ROLE_ADMIN
. I ruoli
sono la base per la maggior parte delle autorizzazioni: un utente può accedere /admin/foo
solo
se ha il ruolo ROLE_ADMIN
.
Come prima, quando l’utente effettua inizialmente la richiesta, il firewall non
chiede nessuna identificazione. Tuttavia, non appena il livello di controllo di accesso
nega l’accesso all’utente (perché l’utente anonimo non ha il ruolo
ROLE_ADMIN
), il firewall entra in azione e avvia il processo di autenticazione.
Il processo di autenticazione dipende dal meccanismo di autenticazione in uso.
Per esempio, se si sta utilizzando il metodo di autenticazione tramite form di login,
l’utente verrà rinviato alla pagina di login. Se si utilizza l’autenticazione HTTP,
all’utente sarà inviata una risposta HTTP 401 e verrà visualizzato una finestra del browser
con nome utente e password.
Ora l’utente ha la possibilità di inviare le credenziali all’applicazione. Se le credenziali sono valide, può essere riprovata la richiesta originale.
In questo esempio, l’utente ryan
viene autenticato con successo con il firewall.
Ma poiché ryan
non ha il ruolo ROLE_ADMIN
, viene ancora negato
l’accesso a /admin/foo
. In definitiva, questo significa che l’utente vedrà un
qualche messaggio che indica che l’accesso è stato negato.
Tip
Quando Symfony nega l’accesso all’utente, l’utente vedrà una schermata di errore e
riceverà un codice di stato HTTP 403 (Forbidden
). È possibile personalizzare la
schermata di errore di accesso negato seguendo le istruzioni sulle
pagine di errore presenti nel ricettario
per personalizzare la pagina di errore 403.
Infine, se l’utente admin
richiede /admin/foo
, avviene un processo
simile, solo che adesso, dopo essere stato autenticato, il livello di controllo di accesso
lascerà passare la richiesta:
Il flusso di richiesta quando un utente richiede una risorsa protetta è semplice, ma incredibilmente flessibile. Come si vedrà in seguito, l’autenticazione può essere gestita in molti modi, come un form di login, un certificato X.509, o da un’autenticazione dell’utente tramite Twitter. Indipendentemente dal metodo di autenticazione, il flusso di richiesta è sempre lo stesso:
- Un utente accede a una risorsa protetta;
- L’applicazione rinvia l’utente al form di login;
- L’utente invia le proprie credenziali (ad esempio nome utente / password);
- Il firewall autentica l’utente;
- L’utente autenticato riprova la richiesta originale.
Note
L’esatto processo in realtà dipende un po’ da quale meccanismo di
autenticazione si sta usando. Per esempio, quando si utilizza il form di login, l’utente
invia le sue credenziali a un URL che elabora il form (ad esempio /login_check
)
e poi viene rinviato all’URL originariamente richiesto (ad esempio /admin/foo
).
Ma con l’autenticazione HTTP, l’utente invia le proprie credenziali direttamente
all’URL originale (ad esempio /admin/foo
) e poi la pagina viene restituita
all’utente nella stessa richiesta (cioè senza rinvio).
Questo tipo di idiosincrasie non dovrebbe causare alcun problema, ma è bene tenerle a mente.
Tip
Più avanti si imparerà che in Symfony2 qualunque cosa può essere protetta, tra cui controllori specifici, oggetti o anche metodi PHP.
Utilizzo di un form di login tradizionale¶
Tip
In questa sezione, si imparerà come creare un form di login di base, che continua a usare
gli utenti inseriti manualmente nel file security.yml
.
Per caricare utenti da una base dati, si legga /cookbook/security/entity_provider. Leggendo quell’articolo e questa sezione, si può creare un form di login completo, che carichi utenti da una base dati.
Finora, si è visto come proteggere l’applicazione con un firewall e poi proteggere l’accesso a determinate aree tramite i ruoli. Utilizzando l’autenticazione HTTP, si può sfruttare senza fatica il box nativo nome utente/password offerto da tutti i browser. Tuttavia, Symfony supporta nativamente molti meccanismi di autenticazione. Per i dettagli su ciascuno di essi, vedere il Riferimento sulla configurazione di sicurezza.
In questa sezione, si potrà proseguire l’apprendimento, consentendo all’utente di autenticarsi attraverso un tradizionale form di login HTML.
In primo luogo, abilitare il form di login sotto il firewall:
Tip
Se non è necessario personalizzare i valori login_path
o check_path
(i valori usati qui sono i valori predefiniti), è possibile accorciare
la configurazione:
Ora, quando il sistema di sicurezza inizia il processo di autenticazione,
rinvierà l’utente al form di login (/login
per impostazione predefinita). Implementare
visivamente il form di login è compito dello sviluppatore. In primo luogo, bisogna creare le due rotte usate
nella configurazione della sicurezza: : la rotta login, che visualizzerà il form di login (cioè
/login
) e la rotta login_check
, che gestirà l’invio del form di login
(cioè /login_check
):
Note
Non è necessario implementare un controllore per l’URL /login_check
perché il firewall catturerà ed elaborerà qualunque form inviato
a questo URL. Tuttuavia, occorre avere una rotta (come mostrato qui) per questo
URL, come anche per il percorso di logout (vedere Logging Out).
Notare che il nome della rotta login
corrisponde al valore di configurazione login_path
,
in quanto è lì che il sistema di sicurezza rinvierà gli utenti che necessitano di
effettuare il login.
Successivamente, creare il controllore che visualizzerà il form di login:
// src/Acme/SecurityBundle/Controller/SecurityController.php;
namespace Acme\SecurityBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\SecurityContextInterface;
class SecurityController extends Controller
{
public function loginAction(Request $request)
{
$session = $request->getSession();
// verifica di eventuali errori
if ($request->attributes->has(SecurityContextInterface::AUTHENTICATION_ERROR)) {
$error = $request->attributes->get(
SecurityContextInterface::AUTHENTICATION_ERROR
);
} elseif (null !== $session && $session->has(SecurityContextInterface::AUTHENTICATION_ERROR)) {
$error = $session->get(SecurityContextInterface::AUTHENTICATION_ERROR);
$session->remove(SecurityContextInterface::AUTHENTICATION_ERROR);
} else {
$error = '';
}
// ultimo nome utente inserito
$lastUsername = (null === $session) ? '' : $session->get(SecurityContextInterface::LAST_USERNAME);
return $this->render(
'AcmeSecurityBundle:Security:login.html.twig',
array(
// ultimo nome utente inserito
'last_username' => $lastUsername,
'error' => $error,
)
);
}
}
Non bisogna farsi confondere da questo controllore. Come si vedrà a momenti, quando l’utente compila il form, il sistema di sicurezza lo gestisce automaticamente. Se l’utente ha inviato un nome utente o una password non validi, questo controllore legge l’errore di invio del form dal sistema di sicurezza, in modo che possano essere visualizzati all’utente.
In altre parole, il compito dello sviluppatore è quello di visualizzare il form di login e gli eventuali errori di login che potrebbero essersi verificati, ma è il sistema di sicurezza stesso che si prende cura di verificare il nome utente e la password inviati e di autenticare l’utente.
Infine, creare il template corrispondente:
Caution
Questo form di login al momento non è protetto da attacchi CSRF. Leggere /cookbook/security/csrf_in_login_form per sapere come proteggere i form.
Tip
La variabile error
passata nel template è un’istanza di
Symfony\Component\Security\Core\Exception\AuthenticationException
.
Potrebbe contenere informazioni, anche sensibili, sull’errore
di autenticazione: va quindi usata con cautela.
Il form ha pochi requisiti. In primo luogo, inviando il form a /login_check
(tramite la rotta login_check
), il sistema di sicurezza intercetterà l’invio
del form e lo processerà automaticamente. In secondo luogo, il sistema
di sicurezza si aspetta che i campi inviati siano chiamati _username
e _password
(questi nomi di campi possono essere configurati).
E questo è tutto! Quando si invia il form, il sistema di sicurezza controllerà automaticamente le credenziali dell’utente e autenticherà l’utente o rimanderà l’utente al form di login, dove sono visualizzati gli errori.
Rivediamo l’intero processo:
- L’utente prova ad accedere a una risorsa protetta;
- Il firewall avvia il processo di autenticazione rinviando
l’utente al form di login (
/login
); - La pagina
/login
rende il form di login, attraverso la rotta e il controllore creato in questo esempio; - L’utente invia il form di login
/login_check
; - Il sistema di sicurezza intercetta la richiesta, verifica le credenziali inviate dall’utente, autentica l’utente se sono corrette e, se non lo sono, lo rinvia al form di login.
Per impostazione predefinita, se le credenziali inviate sono corrette, l’utente verrà rinviato
alla pagina originale che è stata richiesta (ad esempio /admin/pippo
). Se l’utente
originariamente è andato direttamente alla pagina di login, sarà rinviato alla pagina iniziale.
Questo comportamento può essere personalizzato, consentendo, ad esempio, di rinviare
l’utente a un URL specifico.
Per maggiori dettagli su questo e su come personalizzare in generale il processo di login con il form, vedere /cookbook/security/form_login.
Autorizzazione¶
Il primo passo per la sicurezza è sempre l’autenticazione. Una volta che l’utente è stato autenticato, l’autorizzazione ha inizio. L’autorizzazione fornisce un metodo standard e potente per decidere se un utente può accedere a una qualche risorsa (un URL, un oggetto del modello, una chiamata a metodo, ...). Questo funziona tramite l’assegnazione di specifici ruoli a ciascun utente e quindi richiedendo ruoli diversi per differenti risorse.
Il processo di autorizzazione ha due diversi lati:
- L’utente ha un insieme specifico di ruoli;
- Una risorsa richiede un ruolo specifico per poter accedervi.
In questa sezione, ci si concentrerà su come proteggere risorse diverse (ad esempio gli URL, le chiamate a metodi, ecc) con ruoli diversi. Più avanti, si imparerà di più su come i ruoli sono creati e assegnati agli utenti.
Protezione di specifici schemi di URL¶
Il modo più semplice per proteggere parte dell’applicazione è quello di proteggere un intero
schema di URL. Si è già visto questo nel primo esempio di questo capitolo,
dove tutto ciò a cui corrisponde lo schema di espressione regolare ^/admin
richiede
il ruolo ROLE_ADMIN
.
È possibile definire tanti schemi di URL quanti ne occorrono, ciascuno è un’espressione regolare.
Tip
Anteporre il percorso con il simbolo ^
assicura che corrispondano solo gli URL che iniziano con
lo schema. Per esempio, un semplice percorso /admin
(senza
simbolo ^
) corrisponderebbe correttamente a /admin/foo
, ma corrisponderebbe anche a URL
come /foo/admin
.
Capire come funziona access_control
¶
Per ogni richiesta in arrivo, Symfony2 verifica ogni voce di access_control
per trovarne una che corrisponda alla richiesta attuale. Se ne trova una corrispondente,
si ferma, quindi solo la prima voce di access_control
corrispondente
verrà usata per garantire l’accesso.
Ogni access_control
ha varie opzioni che configurano varie
cose:
- se la richiesta in arrivo deve corrispondere a questa voce di controllo di accesso
- una volta corrisposta, se alcune restrizioni di accesso debbano essere applicate:
1. Opzioni di corrispondenza¶
Symfony2 crea un’istanza di Symfony\Component\HttpFoundation\RequestMatcher
per ogni voce di access_control
, che determina se un dato controllo di accesso
vada usato o meno su questa richiesta. Le seguenti opzioni di access_control
sono usate per le corrispondenze:
path
ip
oips
host
methods
Si prende il seguente access_control
come esempio:
Per ogni richiesta in arrivo, Symfony2 deciderà quale access_control
usare in base a URI, indirizzo IP del client, nome host in arrivo,
metodo della richiestsa. Si ricordi, viene usata la prima regola corrispondnete e,
se ip
, host
o method
non sono specificati per una voce, access_control
corrisponderà per qualsiasi ip
, host
o method
:
URI | IP | HOST | METODO | access_control |
Perché? |
---|---|---|---|---|---|
/admin/user |
127.0.0.1 | example.com | GET | regola #1 (ROLE_USER_IP ) |
L’URI corrisponde a path e l’IP a ip . |
/admin/user |
127.0.0.1 | symfony.com | GET | regola #1 (ROLE_USER_IP ) |
path e ip corrispondono. Corrisponderebbe anche
ROLE_USER_HOST , ma solo se si usa la prima
corrispondenza di access_control . |
/admin/user |
168.0.0.1 | symfony.com | GET | regola #2 (ROLE_USER_HOST ) |
ip non corrisponde alla prima regola, quindi viene
usata la seconda (che corrisponde). |
/admin/user |
168.0.0.1 | symfony.com | POST | regola #2 (ROLE_USER_HOST ) |
La seconda regola corrisponde. Corrisponderebbe anche la
terza regola (ROLE_USER_METHOD ), ma solo la prima
corrispondenza di access_control viene usata. |
/admin/user |
168.0.0.1 | example.com | POST | reg. #3 (ROLE_USER_METHOD ) |
ip e host non corrispondono alle prime due voci,
la terza, ROLE_USER_METHOD , corrisponde e viene usata. |
/admin/user |
168.0.0.1 | example.com | GET | regola #4 (ROLE_USER ) |
ip , host e method non fanno corrispondere le
prime tre voci. Ma siccome l’URI corrisponde a path di
ROLE_USER , viene usata. |
/foo |
127.0.0.1 | symfony.com | POST | nessuna corrispondenza | Non corrisponde ad alcune regola di access_control ,
poiché l’URI non corrisponde ad alcun valore di path . |
2. Controllo dell’accesso¶
Una volta che Symfony2 ha deciso quale voce di access_control
corrisponda,
applica restrizioni di accesso in base alle opzioni roles
, allow_if
e
requires_channel
:
role
Se l’utente non ha il ruolo fornito, l’accesso viene negato (internamente, viene lanciataSymfony\Component\Security\Core\Exception\AccessDeniedException
);allow_if
Se l’espressione restituiscefalse
, l’accesso viene negato;requires_channel
Se il canale della richiesta in arrivo (p.e.http
) non corrisponde a questo valore (p.e.https
), l’utente sarà rinviato (p.e. rinviato dahttp
ahttps
, o viceversa).
Tip
In caso di accesso negato, il sistema proverà ad autenticare l’utente, se non lo è già (p.e. rinviare l’utente alla pagina di login). Se l’utente è già entrato, verrà mostrata la pagina di errore 403 “access denied”. Si veda /cookbook/controller/error_pages per ulteriori informazioni.
Protezione tramite IP¶
In certe situazioni può succedere di limitare l’accesso a una data rotta basata su IP. Questo è particolarmente rilevante nel caso di Edge Side Includes (ESI), per esempio. Quando ESI è abilitato, si raccomanda di proteggere l’accesso agli URL ESI. Infatti, alcuni ESI possono contenere contenuti privati, come informazioni sull’utente attuale. Per prevenire un accesso diretto a tali risorse inserendo direttamnte l’URL nel browser, la rotta ESI deve essere protetta e resa visibile solo dalla cache del reverse proxy.
New in version 2.3: La versione 2.3 consente più indirizzi IP in una singola regola, con il costrutto ips: [a, b]
.
Prima della 2.3, era necessario creare una regola per ogni indirizzo IP e usare
la chiave ip
al posto di ips
.
Caution
Come si può vedere nella spiegazione sotto all’esempio, l’opzione ip
non limita a uno specifico indirizzo IP. Invece, usando la chiave ip
si ottiene che la voce access_control
avrà una corrispondenza solo per il corrispondente indirizzo IP
e gli utenti che accedono da diversi indirizzi IP continueranno nelle successive
voci dell’elenco acces_control
.
Ecco un esempio di come si possano garantire tutte le rotte ESI che iniziano per
un certo prefisso, /esi
, da intrusioni esterne:
Ecco come funziona quando il percorso è /esi/qualcosa
dall’IP
10.0.0.1
:
- La prima regola di controllo di accesso non corrisponde e viene ignorata, perché
path
corrisponde, maip
no; - La seconda regola di controllo di accesso non corrisponde (essendoci solo
path
, che corrisponde): non avendo l’utente il ruoloROLE_NO_ACCESS
, perché non definito, l’accesso è negato (il ruoloROLE_NO_ACCESS
può essere qualsiasi cosa che non sia un ruolo esistente, serve solo come espediente per negare sempre l’accesso).
Se ora la stessa richiesta arriva da 127.0.0.1
o da ::1
(l’indirizzo locale
in IPv6):
- Ora, la prima regola di controllo di accesso corrisponde sia per
path
che perip
: l’accesso è consentito, perché l’utente ha sempre il ruoloIS_AUTHENTICATED_ANONYMOUSLY
. - La seconda regola di accesso non viene esaminata, perché la prima corrispondeva.
Protezione tramite espressione¶
New in version 2.4: La funzionalità allow_if
è stata introdotta in Symfony 2.4.
Una volta corrisposta una voce access_control
, si può negare l’accesso tramite la chiave
roles
oppure usare una logica più complessa, con un’espressione nella chiave
allow_if
:
In questo caso, quando l’utente prova ad accedere a un URL che inizia per /_internal/secure
,
gli sarà consentito l’accesso solo se l’indirizzo IP è 127.0.0.1
o se
l’utente ha il ruolo ROLE_ADMIN
.
All’interno dell’espressione, si ha accesso a diverse variabili
e funzioni, inclusa request
, che è l’oggetto
Symfony\Component\HttpFoundation\Request
di Symfony (vedere
component-http-foundation-request).
Per una lista di altre funzioni e variabili, vedere funzioni e variabili.
Forzare un canale (http, https)¶
Si può anche richiedere di accedere a un URL tramite SSL, basta
usare la voce aggiungere il parametro requires_channel
in una voce access_control
. Se
tale access_control
trova corrispondenza e la richiesta usa il canale http
,
l’utente sarà rinviato a https
:
Proteggere un controllore¶
Proteggere l’applicazione basandosi su schemi di URL è semplice, ma in alcuni casi può non essere abbastanza granulare. Quando necessario, si può facilmente forzare l’autorizzazione dall’interno di un controllore:
// ...
public function helloAction($name)
{
if (false === $this->get('security.context')->isGranted('ROLE_ADMIN')) {
throw $this->createAccessDeniedException('Unable to access this page!');
}
// ...
}
New in version 2.5: Il metodo createAccessDeniedException
è stato introdotto in Symfony 2.5.
Il metodo :method:`Symfony\\Bundle\\FrameworkBundle\\Controller\\Controller::createAccessDeniedException`
crea un oggetto speciale Symfony\Component\Security\Core\Exception\AccessDeniedException
,
che alla fine lancia una risposta HTTP 403 in Symfony.
Con SensioFrameworkExtraBundle, si possono anche proteggere i controllori 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.
Protezione degli altri servizi¶
In realtà, con Symfony si può proteggere qualunque cosa, utilizzando una strategia simile a quella vista nella sezione precedente. Per esempio, si supponga di avere un servizio (ovvero una classe PHP) il cui compito è quello di inviare email da un utente all’altro. È possibile limitare l’uso di questa classe, non importa dove è stata utilizzata, per gli utenti che hanno un ruolo specifico.
Per ulteriori informazioni su come utilizzare il componente Security per proteggere servizi e metodi diversi nell’applicazione, vedere /cookbook/security/securing_services.
Access Control List (ACL): protezione dei singoli oggetti della base dati¶
Si immagini di progettare un sistema di blog, in cui gli utenti possono commentare i messaggi. Si vuole che un utente possa modificare i propri commenti, ma non quelli degli altri. Inoltre, come utente admin, si vuole essere in grado di modificare tutti i commenti.
Il componente Security viene fornito con un sistema opzionale di access control list (ACL), che è possibile utilizzare quando è necessario controllare l’accesso alle singole istanze di un oggetto nel sistema. Senza ACL, è possibile proteggere il sistema in modo che solo certi utenti possono modificare i commenti sui blog. Ma con ACL, si può limitare o consentire l’accesso commento per commento.
Per maggiori informazioni, vedere l’articolo del ricettario: /cookbook/security/acl.
Utenti¶
Nelle sezioni precedenti, si è appreso come sia possibile proteggere diverse risorse, richiedendo una serie di ruoli per una risorsa. In questa sezione, esploreremo l’altro lato delle autorizzazioni: gli utenti.
Da dove provengono gli utenti? (fornitori di utenti)¶
Durante l’autenticazione, l’utente invia un insieme di credenziali (di solito un nome utente e una password). Il compito del sistema di autenticazione è quello di soddisfare queste credenziali con l’insieme degli utenti. Quindi da dove proviene questa lista di utenti?
In Symfony2, gli utenti possono arrivare da qualsiasi parte: un file di configurazione, una tabella di una base dati, un servizio web o qualsiasi altra cosa si può pensare. Qualsiasi cosa che prevede uno o più utenti nel sistema di autenticazione è noto come “fornitore di utenti”. Symfony2 dispone dei due fornitori di utenti più diffusi: uno che carica gli utenti da un file di configurazione e uno che carica gli utenti da una tabella di una base dati.
Definizione degli utenti in un file di configurazione¶
Il modo più semplice per specificare gli utenti è direttamente in un file di configurazione. In effetti, questo si è già aver visto nell’esempio di questo capitolo.
Questo fornitore di utenti è chiamato “in-memory” , dal momento che gli utenti
non sono memorizzati in una base dati. L’oggetto utente effettivo è fornito
da Symfony (Symfony\Component\Security\Core\User\User
).
Tip
Qualsiasi fornitore utenti può caricare gli utenti direttamente dalla configurazione, specificando
il parametro di configurazione users
ed elencando gli utenti sotto di esso.
Caution
Se il nome utente è completamente numerico (ad esempio 77
) o contiene un trattino
(ad esempio user-name
), è consigliabile utilizzare la seguente sintassi alternativa quando si specificano
utenti in YAML:
users:
- { name: 77, password: pass, roles: 'ROLE_USER' }
- { name: user-name, password: pass, roles: 'ROLE_USER' }
Per i siti più piccoli, questo metodo è semplice e veloce da configurare. Per sistemi più complessi, si consiglia di caricare gli utenti dalla base dati.
Caricare gli utenti da una base dati¶
Se si vuole caricare gli utenti tramite l’ORM Doctrine, si può farlo facilmente
attraverso la creazione di una classe User
e configurando il fornitore entity
.
Con questo approccio, bisogna prima creare la propria classe User
, che
sarà memorizzata nella base dati.
// src/Acme/UserBundle/Entity/User.php
namespace Acme\UserBundle\Entity;
use Symfony\Component\Security\Core\User\UserInterface;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
*/
class User implements UserInterface
{
/**
* @ORM\Column(type="string", length=255)
*/
protected $username;
// ...
}
Per come è stato pensato il sistema di sicurezza, l’unico requisito per
la classe utente personalizzata è che implementi l’interfaccia Symfony\Component\Security\Core\User\UserInterface
.
Questo significa che il concetto di “utente” può essere qualsiasi cosa, purché
implementi questa interfaccia.
Note
L’oggetto utente verrà serializzato e salvato nella sessione durante le richieste,
quindi si consiglia di implementare l’interfaccia Serializable
nel proprio oggetto utente. Ciò è particolarmente importante se la classe User
ha una classe genitore con proprietà private.
Quindi, configurare un fornitore utenti entity
e farlo puntare alla classe
User
:
Con l’introduzione di questo nuovo fornitore, il sistema di autenticazione
tenterà di caricare un oggetto User
dalla base dati, utilizzando il campo
username
di questa classe.
Note
Questo esempio ha come unico scopo quello di mostrare l’idea di base dietro al fornitore entity
.
Per un esempio completamente funzionante, vedere /cookbook/security/entity_provider.
Per ulteriori informazioni sulla creazione di un fornitore personalizzato (ad esempio se è necessario caricare gli utenti tramite un servizio web), vedere /cookbook/security/custom_provider.
Codificare la password dell’utente¶
Finora, per semplicità, tutti gli esempi hanno memorizzato le password dell’utente
in formato testuale (se tali utenti sono memorizzati in un file di configurazione o in
una base dati). Naturalmente, in un’applicazione reale si consiglia, per ragioni
di sicurezza, di codificare le password degli utenti. Questo è facilmente realizzabile
mappando la classe User in uno dei numerosi “encoder” disponibili. Per esempio,
per salvare gli utenti in memoria, ma oscurare le loro password tramite bcrypt
,
si può fare come segue:
Ora si può calcolare la password cifrata, manualmente
(p.e. password_hash('ryanpass', PASSWORD_BCRYPT, array('cost' => 12));
)
oppure usando uno strumento online.
Gli algoritmi supportati da questo metodo dipendono dalla versione di PHP. Un elenco completo è disponibile richiamando la funzione :phpfunction:`hash_algos`.
Tip
Si possono anche usare diversi algoritmi di hash per utenti diversi. Vedere /cookbook/security/named_encoders per maggiori dettagli.
Determinare la password con hash¶
Se si memorizzano gli utenti sulla base dati e si ha un form di registrazione per gli utenti, è necessario essere in grado di determinare l’hash della password, in modo che sia possibile impostarla per l’utente prima di inserirlo. Indipendentemente dall’algoritmo configurato per l’oggetto User, l’hash della password può essere determinato nel seguente modo da un controllore:
$factory = $this->get('security.encoder_factory');
$user = new Acme\UserBundle\Entity\User();
$encoder = $factory->getEncoder($user);
$password = $encoder->encodePassword('ryanpass', $user->getSalt());
$user->setPassword($password);
Per poter funzionare, assicurarsi di avere il codificatore per la
classe utente, (p.e. Acme\UserBundle\Entity\User
) configurato sotto la chiave encoders
in app/config/security.yml
.
Caution
Quando si consente all’utente di inviare una password in chiaro (p.e. form di registrazione, form di cambio password), occorre avere una validazione, che garantisca che la password non superi i 4096 caratteri. Per maggiori dettagli, vedere implementare un semplice form di registrazione.
Recuperare l’oggetto User¶
Dopo l’autenticazione, si può accedere all’oggetto User
per l’utente corrente
tramite il servizio security.context
. Da dentro un controllore, assomiglierà
a questo:
public function indexAction()
{
$user = $this->get('security.context')->getToken()->getUser();
}
In un controllore, si può usare una scorciatoia:
public function indexAction()
{
$user = $this->getUser();
}
Note
Gli utenti anonimi sono tecnicamente autenticati, nel senso che il metodo isAuthenticated()
dell’oggetto di un utente anonimo restituirà true
. Per controllare se
l’utente sia effettivamente autenticato, verificare il ruolo
IS_AUTHENTICATED_FULLY
.
In un template Twig, si può accedere a questo oggetto tramite la chiave app.user
,
che richiama il metodo
:method:`GlobalVariables::getUser() <Symfony\\Bundle\\FrameworkBundle\\Templating\\GlobalVariables::getUser>`:
Utilizzare fornitori utenti multipli¶
Ogni meccanismo di autenticazione (ad esempio l’autenticazione HTTP, il form di login, ecc.) utilizza esattamente un fornitore utenti e, per impostazione predefinita, userà il primo fornitore dichiarato. Ma cosa succede se si desidera specificare alcuni utenti tramite configurazione e il resto degli utenti nella base dati? Questo è possibile attraverso la creazione di un nuovo fornitore, che li unisca:
Ora, tutti i meccanismi di autenticazione utilizzeranno il chain_provider
, dal momento che
è il primo specificato. Il chain_provider
, a sua volta, tenta di caricare
l’utente da entrambi i fornitori in_memory
e user_db
.
È anche possibile configurare il firewall o meccanismi di autenticazione individuali per utilizzare un provider specifico. Ancora una volta, a meno che un provider sia specificato esplicitamente, viene sempre utilizzato il primo fornitore:
In questo esempio, se un utente cerca di accedere tramite autenticazione HTTP, il sistema di
autenticazione utilizzerà il fornitore utenti in_memory
. Ma se l’utente tenta di
accedere tramite il form di login, sarà usato il fornitore user_db
(in quanto
è l’impostazione predefinita per il firewall).
Per ulteriori informazioni su fornitori utenti e configurazione del firewall, vedere il /reference/configuration/security.
Ruoli¶
L’idea di un “ruolo” è la chiave per il processo di autorizzazione. A ogni utente viene assegnato un insieme di ruoli e quindi ogni risorsa richiede uno o più ruoli. Se l’utente ha i ruoli richiesti, l’accesso è concesso. In caso contrario, l’accesso è negato.
I ruoli sono abbastanza semplici e sono fondamentalmente stringhe che si possono inventare e
utilizzare secondo necessità (anche se i ruoli internamente sono oggetti). Per esempio, se
è necessario limitare l’accesso alla sezione admin del sito web del blog ,
si potrebbe proteggere quella parte con un ruolo ROLE_BLOG_ADMIN
. Questo ruolo
non ha bisogno di essere definito ovunque, è sufficiente iniziare a usarlo.
Note
Tutti i ruoli devono iniziare con il prefisso ROLE_
per poter essere gestiti da
Symfony2. Se si definiscono i propri ruoli con una classe Role
dedicata
(caratteristica avanzata), non bisogna usare il prefisso ROLE_
.
I ruoli gerarchici¶
Invece di associare molti ruoli agli utenti, è possibile definire regole di ereditarietà dei ruoli creando una gerarchia di ruoli:
Nella configurazione sopra, gli utenti con ruolo ROLE_ADMIN
avranno anche il
ruolo ROLE_USER
. Il ruolo ROLE_SUPER_ADMIN
ha ROLE_ADMIN
, ROLE_ALLOWED_TO_SWITCH
e ROLE_USER
(ereditati da ROLE_ADMIN
).
Verifica dell’accesso¶
Una volta che si dispone di utenti e di ruoli, si può andare oltre l’autorizzazione basata su schemi di URL.
Verifica dell’accesso nei controllori¶
Proteggere l’applicazione basandosi su schemi di URL è semplice, ma in alcuni casi può non essere abbastanza granulare. Quando necessario, si può facilmente forzare l’autorizzazione dall’interno di un controllore:
// ...
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
public function helloAction($name)
{
if (false === $this->get('security.context')->isGranted('ROLE_ADMIN')) {
throw new AccessDeniedException();
}
// ...
}
Caution
Un firewall deve essere attivo o verrà lanciata un’eccezione quando viene
chiamato il metodo isGranted
. Spesso è una buona idea avere un firewall principale,
che copra tutti gli URL (come mostrato in questo capitolo).
Controlli di accesso complessi con espressioni¶
New in version 2.4: La funzionalità delle espressioni è stata introdotta in Symfony 2.4.
Oltre a un ruolo, come ROLE_ADMIN
, il metodo isGranted
accetta
anche un oggetto Symfony\Component\ExpressionLanguage\Expression
:
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
use Symfony\Component\ExpressionLanguage\Expression;
// ...
public function indexAction()
{
if (!$this->get('security.context')->isGranted(new Expression(
'"ROLE_ADMIN" in roles or (user and user.isSuperAdmin())'
))) {
throw new AccessDeniedException();
}
// ...
}
In questo esempio, se l’utente ha ROLE_ADMIN
o se il metodo
isSuperAdmin()
dell’oggetto utente restituisce true
, sarà garantito
l’accesso (nota: l’oggetto User potrebbe non avere un metodo isSuperAdmin
,
tale metodo è stato inventato per questo esempio).
Si può approfondire la sintassi del linguaggio delle espressioni in /components/expression_language/syntax.
All’interno dell’espressione si ha accesso a diverse variabili:
user
L’oggetto utente (o la stringaanon
se non autenticato);roles
L’array di ruoli dell’utente, inclusi quelli provenienti dalla gerarchia dei ruoli ma esclusi gli attributiIS_AUTHENTICATED_*
(vedere le funzioni, qui sotto);object
: L’eventuale oggetto passato come secondo parametro aisGranted
;token
L’oggetto token;trust_resolver
: L’oggettoSymfony\Component\Security\Core\Authentication\AuthenticationTrustResolverInterface
: probabilmente si useranno le funzioniis_*
al suo posto.
Inoltre, si ha accesso a varie funzioni:
is_authenticated
: Restituiscetrue
se l’utente è autenticato tramite “ricordami” o autenticato “pienamente”, in pratica dice se l’utente è entrato;is_anonymous
: Equivalente all’uso diIS_AUTHENTICATED_ANONYMOUSLY
con la funzioneisGranted
;is_remember_me
: Simile, ma non uguale aIS_AUTHENTICATED_REMEMBERED
, vedere sotto;is_fully_authenticated
: Simile, ma non uguale aIS_AUTHENTICATED_FULLY
, vedere sotto;has_role
: Verifica se l’utente ha il ruolo dato, equivalente a un’espressione come'ROLE_ADMIN' in roles
.
Protezione degli altri servizi¶
In realtà, con Symfony si può proteggere qualunque cosa, utilizzando una strategia simile a quella vista nella sezione precedente. Per esempio, si supponga di avere un servizio (ovvero una classe PHP) il cui compito è quello di inviare email da un utente all’altro. È possibile limitare l’uso di questa classe, non importa dove è stata utilizzata, per gli utenti che hanno un ruolo specifico.
Per ulteriori informazioni su come utilizzare il componente Security per proteggere servizi e metodi diversi nell’applicazione, vedere /cookbook/security/securing_services.
Controllare l’accesso nei template¶
Nel caso si voglia controllare all’interno di un template se l’utente corrente ha un ruolo, usare la funzione aiutante:
Note
Se si utilizza questa funzione e non si è in un URL dove c’è un firewall attivo, viene lanciata un’eccezione. Anche in questo caso, è quasi sempre una buona idea avere un firewall principale che copra tutti gli URL (come si è visto in questo capitolo).
New in version 2.4: La funzionalità expression
è stata introdotta in Symfony 2.4.
Si possono anche usare espressioni all’interno dei template:
Per maggiori dettagli su espressioni e sicurezza, vedere Controlli di accesso complessi con espressioni.
Access Control List (ACL): protezione dei singoli oggetti della base dati¶
Si immagini di progettare un sistema di blog, in cui gli utenti possono commentare i messaggi. Si vuole che un utente possa modificare i propri commenti, ma non quelli degli altri. Inoltre, come utente admin, si vuole essere in grado di modificare tutti i commenti.
Il componente Security viene fornito con un sistema opzionale di access control list (ACL), che è possibile utilizzare quando è necessario controllare l’accesso alle singole istanze di un oggetto nel sistema. Senza ACL, è possibile proteggere il sistema in modo che solo certi utenti possono modificare i commenti sui blog. Ma con ACL, si può limitare o consentire l’accesso commento per commento.
Per maggiori informazioni, vedere l’articolo del ricettario: /cookbook/security/acl.
Logging Out¶
Generalmente, si vuole che gli utenti possano disconnettersi tramite logout. Fortunatamente,
il firewall può gestire automaticamente questo caso quando si attiva il
parametro di configurazione logout
:
Una volta inserita questa condigurazione in un firewall, inviare un utente a /logout
(o a un altro percorso configurato in path
) lo farà uscire dall’autenticazione.
L’utente sarà quindi rinviato alla pagina iniziale (il valore definito
nel parametro target
). Entrambi i parametri path
e target
hanno come valore
predefinito quello specificato qui. In altre parole, a meno che non si desideri personalizzarli,
possono essere omessi e quindi abbreviare la configurazione:
Si noti che non occorre implementare un controllore per l’URL /logout
,
perché il firewall se ne occuperà. Tuttavia, occorre creare una
rotta, in modo da poterla usare per generare l’URL:
Una volta che l’utente sia uscito, sarà rinviato al percorso
definito in target
(per esempio alla pagina iniziale). Per
maggiori informazioni, vedere il
riferimento alla configurazione della sicurezza.
Autenticazione senza stato¶
Per impostazione predefinita, Symfony2 si basa su un cookie (Session) per persistere il contesto di sicurezza dell’utente. Ma se si utilizzano certificati o l’autenticazione HTTP, per esempio, la persistenza non è necessaria, in quanto le credenziali sono disponibili a ogni richiesta. In questo caso e se non è necessario memorizzare nient’altro tra le richieste, è possibile attivare l’autenticazione senza stato (il che significa Symfony non creerà alcun cookie):
Note
Se si usa un form di login, Symfony2 creerà un cookie anche se si imposta
stateless
a true
.
Utilità¶
Il componente Security di Symfony dispone di una serie di utilità che riguardano la sicurezza. Queste utilità sono usate da Symfony2, ma si possono usare anche direttamente, se occorre risolvere il problemi di cui si occupano.
Confronto tra stringhe¶
Il tempo impiegato nel confronto tra due stringhe dipende dalle rispettive differenze. Il tempo può essere usato da un attaccante, quando le due stringhe rappresentano una password, per esempio. È noto come Timing attack.
Internamente, quando si confrontano due password, Symfony usa un algoritmo a
tempo costante. Si può usare la stessa strategia nel codice, grazie alla classe
Symfony\Component\Security\Core\Util\StringUtils
:
use Symfony\Component\Security\Core\Util\StringUtils;
// password1 è uguale a password2?
$bool = StringUtils::equals($password1, $password2);
Generazione di un numero casuale¶
Ogni volta che occorre generare un numero casuale sicuro, si raccomanda
di usare la classe
Symfony\Component\Security\Core\Util\SecureRandom
:
use Symfony\Component\Security\Core\Util\SecureRandom;
$generator = new SecureRandom();
$random = $generator->nextBytes(10);
Il metodo :method:`Symfony\\Component\\Security\\Core\\Util\\SecureRandom::nextBytes` restituisce una stringa casuale, composta dal numero di caratteri passati come parametro (10, nell’esempio appena visto).
La classe SecureRandom
funziona meglio se è installato OpenSSL, ma, nel caso in cui
non lo sia, si appoggia a un algoritmo interno, che ha bisogno di un file seme
per funzionare. Basta passare il nome di un file, per abilitarlo:
$generator = new SecureRandom('/un/percorso/dove/memorizzare/il/seme.txt');
$random = $generator->nextBytes(10);
Note
Si può anche accedere a un’stanza di un numero casuale direttametne dal contenitore
di Symfony: il suo nome è security.secure_random
.
Considerazioni finali¶
La sicurezza può essere un problema profondo e complesso nell’applicazione da risolvere in modo corretto. Per fortuna, il componente Security di Symfony segue un ben collaudato modello di sicurezza basato su autenticazione e autorizzazione. L’autenticazione, che avviene sempre per prima, è gestita da un firewall il cui compito è quello di determinare l’identità degli utenti attraverso diversi metodi (ad esempio l’autenticazione HTTP, il form di login, ecc.). Nel ricettario, si trovano esempi di altri metodi per la gestione dell’autenticazione, includendo quello che tratta l’implementazione della funzionalità cookie “Ricorda i dati”.
Una volta che un utente è autenticato, lo strato di autorizzazione può stabilire se l’utente debba o meno avere accesso a una specifica risorsa. Più frequentemente, i ruoli sono applicati a URL, classi o metodi e se l’utente corrente non ha quel ruolo, l’accesso è negato. Lo strato di autorizzazione, però, è molto più profondo e segue un sistema di “voto”, in modo che tutte le parti possono determinare se l’utente corrente dovrebbe avere accesso a una data risorsa. Ulteriori informazioni su questo e altri argomenti nel ricettario.
Saperne di più con il ricettario¶
- Forzare HTTP/HTTPS
- Impersonare un utente
- Blacklist di utenti per indirizzo IP
- Access Control List (ACL)
- /cookbook/security/remember_me
- How to Restrict Firewalls to a Specific Host