Segurança
=========
Segurança é um processo em dois passos principais. Seu objetivo é evitar que
um usuário tenha acesso a um recurso que ele não deveria ter.
No primeiro passo do processo, o sistema de segurança identifica quem o usuário é
exigindo que o mesmo envie algum tipo de identificação. Este primeiro passo é
chamado **autenticação** e signifcica que o sistema está tentando identificar
que é o usuário.
Uma vez que o sistema sabe quem está acessando, o próximo passo é determinar se o
usuário pode acessar determinado recurso. Este segundo passo é chamado
de **autorização** e significa que o sistema irá checar se o usuário tem permissão para
executar determinada ação.
.. image:: /images/book/security_authentication_authorization.png
:align: center
Como a melhor maneira de aprender é com um exemplo, vamos para ele.
.. note::
O `componente de segurança`_ do Symfony está disponível como uma biblioteca
PHP podendo ser utilizada em qualquer projeto PHP.
Exemplo: Autenticação Básica HTTP
---------------------------------
O componente de segurança pode ser configurado através da configuração de
sua aplicação. Na verdade, a maioria dos esquemas comuns de segurança podem
ser conseguidos apenas configurando adequadamente sua aplicação. A configuração
a seguir diz ao Symfony para proteger qualquer URL que satisfaça ``/admin/*``
através da autenticação básica HTTP, solicitando do usuário credenciais (login/senha).
.. configuration-block::
.. code-block:: yaml
# app/config/security.yml
security:
firewalls:
secured_area:
pattern: ^/
anonymous: ~
http_basic:
realm: "Secured Demo Area"
access_control:
- { path: ^/admin, roles: ROLE_ADMIN }
providers:
in_memory:
memory:
users:
ryan: { password: ryanpass, roles: 'ROLE_USER' }
admin: { password: kitten, roles: 'ROLE_ADMIN' }
encoders:
Symfony\Component\Security\Core\User\User: plaintext
.. code-block:: xml
.. code-block:: php
// app/config/security.php
$container->loadFromExtension('security', array(
'firewalls' => array(
'secured_area' => array(
'pattern' => '^/',
'anonymous' => array(),
'http_basic' => array(
'realm' => 'Secured Demo Area',
),
),
),
'access_control' => array(
array('path' => '^/admin', 'role' => 'ROLE_ADMIN'),
),
'providers' => array(
'in_memory' => array(
'memory' => array(
'users' => array(
'ryan' => array('password' => 'ryanpass', 'roles' => 'ROLE_USER'),
'admin' => array('password' => 'kitten', 'roles' => 'ROLE_ADMIN'),
),
),
),
),
'encoders' => array(
'Symfony\Component\Security\Core\User\User' => 'plaintext',
),
));
.. tip::
A distribuição padrão do Symfony coloca a configuração de segurança em
um arquivo separado (e.g. ``app/config/security.yml``). Se você não
tem um arquivo separado para as configurações de segurança, pode colocar
diretamente no arquivo de configuração principal (por exemplo, ``app/config/config.yml``).
O resultado final desta configuração é um completo sistema de segurança funcional
com as seguintes características:
* Há dois usuários no sistema (``ryan`` e ``admin``);
* Os usuários se autenticam através da janela de autenticação básica HTTP;
* Qualquer URL que comece com ``/admin/*`` será protegida e somente o usuário ``admin``
terá acesso;
* Todas URLs que *não* comecem com ``/admin/*`` são acessíveis a todos usuários
(e ao usuário nunca serão solicitadas as credenciais de acesso).
Vamos dar uma olhada como funciona a segurança e como cada parte da configuração influencia
no sistema.
Como funciona a segurança: Autenticação e Autorização
-----------------------------------------------------
O sistema de segurança do Symfony funciona determinando quem um usuário é (autenticação)
e depois checando se o usuário tem acesso ao recurso específico ou URL solicitado.
Firewalls (Autenticação)
~~~~~~~~~~~~~~~~~~~~~~~~~~
Quando um usuário requisita uma URL que está protegida por um firewall,
o sistema de segurança é ativado. O trabalho do firewall é determinar
se o usuário precisa ou não ser autenticado. Se ele precisar, envia a resposta
de volta e inicia o processo de autenticação.
Um firewall será ativado quando a URL requisitada corresponda ao ``padrão de caracteres`` da
expressão regular configurada na configuração de segurança. Neste exemplo, o
``padrão de caracteres`` (``^/``) corresponde a qualquer solicitação. O fato do
firewall ser ativado *não* significa, porém, que a janela de autenticação básica HTTP
(solicitando login e senha) será exibida para todas requisições. Por exemplo,
qualquer usuário poderá acessar ``/foo`` sem que seja solicitada sua autenticação.
.. image:: /images/book/security_anonymous_user_access.png
:align: center
Isto funciona primeiramente por que o firewall permite *usuários anônimos* através
do parâmetro ``anonymous`` da configuração. Em outras palavras, o firewall não
exige que o usuário se autentique completamente. E por que nenhum ``perfil`` (``role``)
é necessário para acessar ``/foo`` (na seção ``access_control``), a solicitação
pode ser realizada sem que o usuário sequer se identifique.
Se você remover a chave ``anonymous``, o firewall *sempre* fará o usuário se identificar
por completo imediatamente.
Controles de acesso (Autorização)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Se o usuário solicitar ``/admin/foo``, porém, o processo toma um rumo diferente.
Isto acontecerá por que a seção ``access_control`` da configuração indica
que qualquer URL que se encaixe no padrão de caracteres ``^/admin`` (isto é, ``/admin``
ou qualquer coisa do tipo ``/admin/*``) deve ser acessada somente por usuários
com o perfil ``ROLE_ADMIN``. Perfis são a base para a maioria das autorizações:
o usuário pode acessar ``/admin/foo`` somente se tiver o perfil ``ROLE_ADMIN``.
.. image:: /images/book/security_anonymous_user_denied_authorization.png
:align: center
Como antes, o firewall não solicita credenciais de acesso. Assim que a camada
de controle de acesso nega o acesso (por que o usuário não tem o perfil ``ROLE_ADMIN``),
porém, o firewall inicia o processo de autenticação. Este processo depende
do mecanismo de autenticação que estiver utilizando. Por exemplo, se estiver utilizando
o método de formulário de autenticação (``form login``), o usuário será redirecionado
para a página de login. Se estiver utilizando o método básico de autenticação HTTP,
o navegador recebe uma resposta do tipo HTTP 401 para que ao usuário seja exibida
a janela de login/senha do navegador.
O usuário agora tem a oportunidade de digitar suas credenciais no aplicativo. Se as
credenciais forem válidas, a requisição original será solicitada novamente.
.. image:: /images/book/security_ryan_no_role_admin_access.png
:align: center
No exemplo, o usuário ``ryan`` se autentica com sucesso pelo firewall. Como, porém,
``ryan`` não tem o perfil ``ROLE_ADMIN``, ele ainda terá seu acesso negado ao
recurso ``/admin/foo``. Infelizmente, isto significa que o usuário verá
uma mensagem indicando que o acesso foi negado.
.. tip::
Quando o Symfony nega acesso a um usuário, o usuário vê uma tela de
erro e o navegador recebe uma resposta com o HTTP status code 403 (``Forbidden``).
É possível personalizar a tela de erro de acesso negado seguindo as
instruções em :ref:`Error Pages` do
do texto do Symfony 2 - Passo-a-passo que ensina a personalizar a página
de erro 403.
Finalmente, se o usuário ``admin`` requisitar ``/admin/foo``, um processo similar
entra em ação, mas neste caso, após a autenticação, a camada de controle de acesso
permitirá que a requisição seja completada:
.. image:: /images/book/security_admin_role_access.png
:align: center
O fluxo de requisição quando um usuário solicita um recurso protegido é direto,
mas muito flexível. Como verá mais tarde, a autenticação pode acontecer de
diversas maneiras, incluindo formulário de login, certificado X.509, ou
autenticação pelo Twitter. Independente do método de autenticação, o fluxo
de requisiçao é sempre o mesmo:
#. Um usuário acessa um recurso protegido;
#. O aplicativo redireciona o usuário para o formulário de login;
#. O usuário envia suas credenciais (e.g. login/senha);
#. O firewall autentica o usuário;
#. O usuário autenticado é redirecionado para o recurso solicitado originalmente.
.. note::
O processo *exato* na verdade depende um pouco do mecanismo de autenticação
que estiver usando. Por exemplo, quando estiver utilizando formulário de login,
o usuário envia suas credenciais para a URL que processa o formulário (por exemplo,
``/login_check``) e depois é redirecionado de volta para a URL solicitada
originalmente (por exemplo, ``/admin/foo``). Se utilizar autenticação básica
HTTP, porém, o usuário envia suas credenciais diretamente para a URL original
(por exemplo, ``/admin/foo``) e depois a página é retornada para o usuário
na mesma requisição (isto significa que não há redirecionamentos).
Estes detalhes técnicos não devem ser relevantes no uso do sistema de
segurança, mas é bom ter uma idéia a respeito.
.. tip::
Você aprenderá mais tarde como *qualquer coisa* pode ser protegida no Symfony2,
incluindo controladores específicos, objetos, ou até métodos PHP.
.. _book-security-form-login:
Usando um formulário de login em HTML
-------------------------------------
Até agora, você viu como cobrir seu aplicativo depois do firewall e assim
restringir o acesso de certas áreas a certos perfis. Utilizando a autenticação
básica HTTP, é possível, sem esforços, submeter login/senha através da
janela do navegador. O Symfony, porém, suporta de fábrica muitos outros
mecanismos de autenticação. Para detalhes sobre todos eles, consulte
:doc:`Referência Da Configuração De Segurança`.
Nesta seção, você aprimorará o processo permitindo que o usuário se autentique através
de um formulário de login tradicional em HTML.
Primeiro habilite o formulário no seu firewall:
.. configuration-block::
.. code-block:: yaml
# app/config/security.yml
security:
firewalls:
secured_area:
pattern: ^/
anonymous: ~
form_login:
login_path: /login
check_path: /login_check
.. code-block:: xml
.. code-block:: php
// app/config/security.php
$container->loadFromExtension('security', array(
'firewalls' => array(
'secured_area' => array(
'pattern' => '^/',
'anonymous' => array(),
'form_login' => array(
'login_path' => '/login',
'check_path' => '/login_check',
),
),
),
));
.. tip::
Se não precisar de personlizar os valores de ``login_path`` ou ``check_path``
(os valores utilizados acima são os valores padrão), você pode encurtar
seu configuração:
.. configuration-block::
.. code-block:: yaml
form_login: ~
.. code-block:: xml
.. code-block:: php
'form_login' => array(),
Agora, quando o sistema de segurança inicia o processo de autenticação,
ele redirecionará o usuário para o formulário de login (``/login`` por padrão).
É sua tarefa implementar o visual desse formulário. Primeiro, crie duas rotas:
uma para a exibição do formulário de login (no caso, ``/login``) e outra
para processar a submissão do formulário (no caso, ``/login_check``):
.. configuration-block::
.. code-block:: yaml
# app/config/routing.yml
login:
pattern: /login
defaults: { _controller: AcmeSecurityBundle:Security:login }
login_check:
pattern: /login_check
.. code-block:: xml
AcmeSecurityBundle:Security:login
.. code-block:: php
// app/config/routing.php
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\Route;
$collection = new RouteCollection();
$collection->add('login', new Route('/login', array(
'_controller' => 'AcmeDemoBundle:Security:login',
)));
$collection->add('login_check', new Route('/login_check', array()));
return $collection;
.. note::
*Não* é preciso implementar o controller para a URL ``/login_check``
pois o firewall interceptará e processará o que foi submitido para essa URL.
É opcional, porém útil, criar uma rota para que você possa gerar o link
de submissão na template do formulário de login.
.. versionadded:: 2.1
Com o Symfony 2.1, você *deve* possuir rotas configuradas para suas URLs ``login_path``
(ex. ``/login``), ``check_path`` (ex. ``/login_check``) e ``logout``
(ex. ``/logout`` - veja `Logging Out`_).
Observe que o nome da rota ``login`` não é importante. O que importa é que a
URL da rota corresponda o que foi colocado na configuração ``login_path``, pois
é para onde o sistema de segurança redirecionará os usuários que precisarem
se autenticar.
O próximo passo é criar o controller que exibirá o formulário de login:
.. code-block:: php
// src/Acme/SecurityBundle/Controller/Main;
namespace Acme\SecurityBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\Security\Core\SecurityContext;
class SecurityController extends Controller
{
public function loginAction()
{
$request = $this->getRequest();
$session = $request->getSession();
// get the login error if there is one
if ($request->attributes->has(SecurityContext::AUTHENTICATION_ERROR)) {
$error = $request->attributes->get(SecurityContext::AUTHENTICATION_ERROR);
} else {
$error = $session->get(SecurityContext::AUTHENTICATION_ERROR);
}
return $this->render('AcmeSecurityBundle:Security:login.html.twig', array(
// last username entered by the user
'last_username' => $session->get(SecurityContext::LAST_USERNAME),
'error' => $error,
));
}
}
Não se confunda com esse controller. Como verá, quando o usuário submete o formulário,
o sistema de segurança automaticamente processar a submissão para você. Se o usuário
entrou com login e/ou senha inválidos, este controller pega o erro ocorrido do sistema
de segurança para poder exibir ao usuário.
Em outras palavras, seu trabalho é exibir o formulário de login e qualquer
erro ocorrido durante a tentativa de autenticação, mas o sistema de segurança
já toma conta de checar se as credenciais são válidas e de autenticar o usuário.
Finalmente crie a template correspondente:
.. configuration-block::
.. code-block:: html+jinja
{# src/Acme/SecurityBundle/Resources/views/Security/login.html.twig #}
{% if error %}
{{ error.message }}
{% endif %}
.. code-block:: html+php
getMessage() ?>
.. tip::
A variável ``error`` passada para a template é uma instância de
:class:`Symfony\\Component\\Security\\Core\\Exception\\AuthenticationException`.
Esta pode conter mais informações - ou até informações sensíveis - sobre a
falha na autenticação, por isso use-a com sabedoria!
O formulário tem que atender alguns requisitos. Primeiro, ao submeter o formulário
para ``/login_check`` (através da rota ``login_check``), o sistema de segurança
interceptará a submissão do formulário e o processará. Segundo, o sistema de segurança
espera que os campos submetidos sejam chamados ``_username`` e ``_password``
(estes nomes podem ser :ref:`configured`).
E é isso! Quando submeter um formulário, o sistema de segurança irá automaticamente
checar as credenciais do usuário e autenticá-lo ou enviar o ele de volta ao
formulário de login para o erro ser exibido.
Vamos revisar o processo inteiro:
#. O usuário tenta acessar um recurso que está protegido;
#. O firewall inicia o processo de autenticação redirecionando o
usuário para o formulário de login(``/login``);
#. A página ``/login`` produz o formulário de login através da rota
e controlador criados neste exemplo;
#. O usuário submete o formulário de login para ``/login_check``;
#. O sistema de segurança intercepta a solicitação, verifica as credenciais
submetidas pelo usuário, autentica o mesmo se tiverem corretas ou envia
de volta para o formulário de login caso contrário;
Por padrão, se as credenciais estiverem corretas, o usuário será redirecionado
para a página que solicitou originalmente (e.g. ``/admin/foo``). Se o usuário
originalmente solicitar a página de login, ele será redirecionado para a página
principal. Isto pode ser modificado se necessário, o que permitiria você
redirecionar o usuário para um outra URL específica.
Para maiores detalhes sobre isso e como personalizar o processamento do
formulário de login acesse
:doc:`/cookbook/security/form_login`.
.. _book-security-common-pitfalls:
.. sidebar:: Evite os erros comuns
Quando estiver configurando seu formulário de login, fique atendo
aos seguintes erros comuns.
**1. Crie as rotas corretas**
Primeiro, tenha certeza que definiu as rotas ``/login`` e ``/login_check``
corretamente e que elas correspondem aos calores das configurações
``login_path`` e ``check_path``. A configuração errada pode significar que
você será redirecionado para a página de erro 404 ao invés da página
de login ou a submissão do formulário de login não faça nada (você
sempre vê o formulário sem sair dele).
**2. Tenha certeza que a página de login não é protegida**
Também tenha certeza que a página de login *não* precisa de qualquer
perfil para ser vizualizada. Por exemplo, a seguinte configuração,
que exige o perfil ``ROLE_ADMIN`` para todas as URLs (incluindo a
URL ``/login``), causará um redirecionamento circular:
.. configuration-block::
.. code-block:: yaml
access_control:
- { path: ^/, roles: ROLE_ADMIN }
.. code-block:: xml
.. code-block:: php
'access_control' => array(
array('path' => '^/', 'role' => 'ROLE_ADMIN'),
),
Removendo o controle de acesso para a URL ``/login`` resolve o problema:
.. configuration-block::
.. code-block:: yaml
access_control:
- { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/, roles: ROLE_ADMIN }
.. code-block:: xml
.. code-block:: php
'access_control' => array(
array('path' => '^/login', 'role' => 'IS_AUTHENTICATED_ANONYMOUSLY'),
array('path' => '^/', 'role' => 'ROLE_ADMIN'),
),
Além disso, se o seu firewall *não* permite usuários anônimos, você precisará
criar um firewall especial para permitir usuários anônimos para a página de login:
.. configuration-block::
.. code-block:: yaml
firewalls:
login_firewall:
pattern: ^/login$
anonymous: ~
secured_area:
pattern: ^/
form_login: ~
.. code-block:: xml
.. code-block:: php
'firewalls' => array(
'login_firewall' => array(
'pattern' => '^/login$',
'anonymous' => array(),
),
'secured_area' => array(
'pattern' => '^/',
'form_login' => array(),
),
),
**3. Tenha certeza que ``/login_check`` está protegida por um firewall**
Certifique-se que a URL indicada em ``check_path`` (no caso, ``/login_check``)
esteja protegida por um firewall que está utilizando seu formulário de login
(neste exemplo, um único firewall filtra *todas* as URLs, incluindo ``/login_check``).
Se ``/login_check`` não estiver atrás de nenhum firewall, uma exceção
será gerada ``Unable to find the controller for path "/login_check"``.
**4. Múltiplos firewalls não compartilham o mesmo contexto de segurança**
Se estiver utilizando múltiplos firewalls e se autenticar em um firewall,
você *não* estará autenticado nos outros firewalls automaticamente.
Firewalls diferentes funcionam como sistemas de segurança diferente. Isto
acontece por que para a maioria dos aplicativos ter somente um
firewall é o suficiente.
Autorização
-------------
O primeiro passo na segurança é sempre a autenticação: o processo de verficar quem
o usuário é. No Symfony, a autenticação pode ser feita de várias maneiras - via
formulário de login, autenticação básica HTTP ou até mesmo pelo Facebook.
Uma vez que o usuário está autenticado, a autorização começa. Autorização fornece
uma maneira padrão e poderosa de decidir se o usuário pode acessar algum recurso
(uma URL, um objeto do modelo, um método...). Isto funciona com perfis atribuídos
para cada usuário e exigindo perfis diferentes para diferentes recursos.
O processo de autorização tem dois lados diferentes:
#. O usuário tem um conjunto de perfis específico;
#. Um recurso requer um perfil específico para ser acessado.
Nesta seção, o foco será em como tornar seguros diferentes recursos (por exemplo URLs,
chamadas a métodos, etc) com diferentes perfis. Mais tarde, você aprenderá mais
como perfis são criados e atribuídos aos usuários.
Protegendo padrões de URLs
~~~~~~~~~~~~~~~~~~~~~~~~~~
A maneira mais básica de proteger seu aplicativo é proteger um padrão de URL.
Você já viu no primeiro exemplo deste capítulo que qualquer requisição que
se encaixasse na expressão regular ``^/admin`` exigiria o perfil ``ROLE_ADMIN``.
Você pode definir quantos padrões precisar. Cada um é uma expressão regular.
.. configuration-block::
.. code-block:: yaml
# app/config/config.yml
security:
# ...
access_control:
- { path: ^/admin/users, roles: ROLE_SUPER_ADMIN }
- { path: ^/admin, roles: ROLE_ADMIN }
.. code-block:: xml
.. code-block:: php
// app/config/config.php
$container->loadFromExtension('security', array(
// ...
'access_control' => array(
array('path' => '^/admin/users', 'role' => 'ROLE_SUPER_ADMIN'),
array('path' => '^/admin', 'role' => 'ROLE_ADMIN'),
),
));
.. tip::
Iniciando o padrão com ``^`` garante que somente URLs *começando* com o padrão
terá uma comparação positiva. Por exemplo, o padrão simples ``/admin`` (sem
o ``^``) resultaria em uma comparação positiva para ``/admin/foo``, mas
também para URLs como ``/foo/admin``.
Para cada requisição que chega, o Symfony2 tenta encontrar uma regra de acesso
correspondente, com comparação positiva do padrão (a primeira que encontrar ganha).
Se o usuário não estiver autenticado ainda, a autenticação é iniciada (isto é,
o usuário tem a chance de fazer login). Se o usuário, porém, já *estiver* autenticado,
mas não tiver o perfil exigido, uma exceção é disparada
:class:`Symfony\\Component\\Security\\Core\\Exception\\AccessDeniedException` ,
que você pode tratar e transformar em uma apresentável página de "Acesso Negado"
para o usuário. Veja :doc:`/cookbook/controller/error_pages` para mais informações.
Como o Symfony utiliza a primeira regra de acesso que der uma comparação positiva, uma
URL como ``/admin/users/new`` corresponderá a primeira regra e exigirá somente o perfil
``ROLE_SUPER_ADMIN``. Qualquer URL como ``/admin/blog`` corresponderá a segunda regra e
exigirá o perfil ``ROLE_ADMIN``.
.. _book-security-securing-ip:
Protegendo por IP
~~~~~~~~~~~~~~~~~
Algumas situações podem exigir que você restrinja o acesso de uma determinada rota com base no IP.
Isto é particularmente relevante no caso de :ref:`Edge Side Includes` (ESI),
por exemplo, que utiliza a rota com nome "_internal". Quanto ESI é utilizado, a rota
_internal é requerida pelo gateway cache (gerente de cache) para possibilitar diferentes
opções de caching para subseções dentro de uma determinada página. Esta rota vem com o
prefixo ^/_internal por padrão na edição padrão (assumindo que você ativou estas linhas
do seu arquivo de configuração de rotas - routing.yml).
Aqui está um exemplo de como poderia proteger esta rota de acesso externo:
.. configuration-block::
.. code-block:: yaml
# app/config/security.yml
security:
# ...
access_control:
- { path: ^/_internal, roles: IS_AUTHENTICATED_ANONYMOUSLY, ip: 127.0.0.1 }
.. code-block:: xml
.. code-block:: php
'access_control' => array(
array('path' => '^/_internal', 'role' => 'IS_AUTHENTICATED_ANONYMOUSLY', 'ip' => '127.0.0.1'),
),
.. _book-security-securing-channel:
Protegendo por canal
~~~~~~~~~~~~~~~~~~~~
Assim como a proteção por IP, exigir o uso de SSL é tão simples quanto adicionar uma
nova entrar em access_control:
.. configuration-block::
.. code-block:: yaml
# app/config/security.yml
security:
# ...
access_control:
- { path: ^/cart/checkout, roles: IS_AUTHENTICATED_ANONYMOUSLY, requires_channel: https }
.. code-block:: xml
.. code-block:: php
'access_control' => array(
array('path' => '^/cart/checkout', 'role' => 'IS_AUTHENTICATED_ANONYMOUSLY', 'requires_channel' => 'https'),
),
.. _book-security-securing-controller:
Protegendo um Controller
~~~~~~~~~~~~~~~~~~~~~~~~
Proteger seu aplicativo baseado em padrões de URL é fácil, mas este método pode não ser
específico o bastante em certos casos. Quando necessário, você pode ainda facilmente
forçar autorização de dentro de um controller:
.. code-block:: php
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
// ...
public function helloAction($name)
{
if (false === $this->get('security.context')->isGranted('ROLE_ADMIN')) {
throw new AccessDeniedException();
}
// ...
}
.. _book-security-securing-controller-annotations:
Você pode ainda instalar e utilizar opcionalmente o ``JMSSecurityExtraBundle``,
que te permite proteger controllers através de anotações:
.. code-block:: php
use JMS\SecurityExtraBundle\Annotation\Secure;
/**
* @Secure(roles="ROLE_ADMIN")
*/
public function helloAction($name)
{
// ...
}
Para mais informações, veja a documentação `JMSSecurityExtraBundle`_ . Se você
a distribuição Standard do Symfony, este bundle está habilitado por padrão.
Se não estiver, você pode facilmente baixar e instalá-lo.
Protegendo outros serviços
~~~~~~~~~~~~~~~~~~~~~~~~~~
De fato, qualquer coisa pode ser protegida em Symfony utilizando uma estratégia
similar a apresentada na seção anterior. Por exemplo, suponha que você tem
um serviço (uma classe PHP, por exemplo) que seu trabalho é enviar e-mails
de um usuário para outro. Você pode restringir o uso dessa classe - não
importa de onde está sendo utilizada - a usuários que tenham um perfil específico.
Para mais informações sobre como você pode utilizar o componente de segurança
para proteger diferentes serviços e métodos de seu aplicativo, consulte :doc:`/cookbook/security/securing_services`.
Listas De Controle De Acesso (ACLs): Protegendo Objetos Específicos Do Banco De Dados
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Imagine que você está projetando um sistema de blog onde seus usuários podem
comentar seus posts. Agora, você quer que um usuário tenha a possibilidade de editar
seus próprios comentários, mas não aqueles de outros usuários. Além disso, como
administrador, você quer poder editar *todos* os comentários.
O componente de segurança possui um sistema de listas de controle de acesso (ACL)
que te permite controlar acesso a instâncias individuais de um objeto no seu
sistema. *Sem* ACL, você consegue proteger seu sistema para que
somente usuários específicos possam editar os comentários. *Com* ACL, porém, você
pode restringir ou permitir o acesso por comentário.
Para mais informação, veja o passo-a-passo: :doc:`/cookbook/security/acl`.
Usuários
--------
Nas seções anteriores, você aprendeu como proteger diferentes recursos exigindo
um conjunto de *perfis* para o acesso a um recurso. Nesta seção exploraremos
outro aspecto da autorização: os usuários.
De onde os usuários vêm? (*User Providers*)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Durante a autenticação, o usuário submete um conjunto de credenciais (normalmente
login e senha). O trabalho do sistema de autenticação é verificar essas credenciais
contra um conjunto de usuários. De onde essa lista de usuários vem então?
No Symfony2, usuários podem vir de qualquer lugar - um arquivo de configuração,
um banco de dados, um serviço web ou qualquer outra fonte que desejar. Qualquer
coisa que disponibiliza um ou mais usuários para o sistema de autenticação é conhecido
como "user provider". O Symfony2 vem por padrão com os dois mais comuns: um que
carrega os usuários do arquivo de configuração e outro que carrega os usuários do
banco de dados.
Especificando usuários no arquivo de configuração
.................................................
O jeito mais fácil de especificar usuários é diretamnete no arquivo de configuração.
De fato, você já viu isso em um exemplo neste capítulo.
.. configuration-block::
.. code-block:: yaml
# app/config/config.yml
security:
# ...
providers:
default_provider:
memory:
users:
ryan: { password: ryanpass, roles: 'ROLE_USER' }
admin: { password: kitten, roles: 'ROLE_ADMIN' }
.. code-block:: xml
.. code-block:: php
// app/config/config.php
$container->loadFromExtension('security', array(
// ...
'providers' => array(
'default_provider' => array(
'memory' => array(
'users' => array(
'ryan' => array('password' => 'ryanpass', 'roles' => 'ROLE_USER'),
'admin' => array('password' => 'kitten', 'roles' => 'ROLE_ADMIN'),
),
),
),
),
));
Este *user provider* é chamado de "in-memory" user provider, já que os
usuários não estão armazenados em nenhum banco de dados. O objeto usuário
é fornecido pelo Symfony (:class:`Symfony\\Component\\Security\\Core\\User\\User`).
.. tip::
Qualquer user provider pode carregar usuários diretamente da configuração se
especificar o parâmetro de configuração ``users`` e listar os usuários
abaixo dele.
.. caution::
Se seu login é todo numérico (``77``, por exemplo) ou contém hífen (``user-name``, por exemplo),
você deveria utilizar a sintaxe alternativa quando especificar usuários em YAML:
.. code-block:: yaml
users:
- { name: 77, password: pass, roles: 'ROLE_USER' }
- { name: user-name, password: pass, roles: 'ROLE_USER' }
Para sites menores, este método é rápido e fácil de configurar. Para sistemas mais complexos,
você provavelmente desejará carregar os usuários do banco de dados.
.. _book-security-user-entity:
Carregando usuários do banco de dados
.....................................
Se você desejar carregar seus usuários através do Doctrine ORM, você pode
facilmente o fazer criando uma classe ``User`` e configurando o ``entity`` provider
.. tip:
Um bundle open source de alta qualidade está disponível que permite armazenar
seus usuários com Doctrine ORM ou ODM. Leia mais sobre o `FOSUserBundle`_ no GitHub.
Nessa abordagem, você primeiro precisa criar sua própria classe ``User``, que
será persistida no banco de dados.
.. code-block:: php
// 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;
// ...
}
Ao que diz respeito ao sistema de segurança, o único requisito para sua
classe ``User`` personalizada é que ela implemente a interface
:class:`Symfony\\Component\\Security\\Core\\User\\UserInterface` . Isto significa
que conceito de usuário pode ser qualquer um, desde que implemente essa interface.
.. versionadded:: 2.1
No Symfony 2.1, o método ``equals`` foi removido do ``UserInterface``.
Se você precisa sobrescrever a implementação default da lógica de comparação,
implemente a nova interface :class:`Symfony\\Component\\Security\\Core\\User\\EquatableInterface`
.
.. note::
O objeto User será serializado e salvo na sessão entre requisições, por isso
é recomendado que você `implemente a interface \Serializable`_ em sua classe User.
Isto é especialmente importante se sua classe ``User`` tem uma classe pai com
propriedades private.
Em seguida, configure um user provider ``entity`` e aponte-o para sua classe ``User``:
.. configuration-block::
.. code-block:: yaml
# app/config/security.yml
security:
providers:
main:
entity: { class: Acme\UserBundle\Entity\User, property: username }
.. code-block:: xml
.. code-block:: php
// app/config/security.php
$container->loadFromExtension('security', array(
'providers' => array(
'main' => array(
'entity' => array('class' => 'Acme\UserBundle\Entity\User', 'property' => 'username'),
),
),
));
Com a introdução desse novo provider, o sistema de autenticação tentará
carregar o objeto ``User`` do banco de dados a partir do campo ``username`` da classe.
.. note::
Este exemplo é somente para demonstrar a idéia básica por trás do provider ``entity``.
Para um exemplo completo, consulte :doc:`/cookbook/security/entity_provider`.
Para mais informações sobre como criar seu próprio provider (se precisar carregar usuários
do seu serviço web por exemplo), consulte :doc:`/cookbook/security/custom_provider`.
Protegendo a senha do usuário
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Até agora, por simplicidade, todos os exemplos armazenavam as senhas dos usuários
em texto puro (sendo armazenados no arquivo de configuração ou no banco de dados).
Claro que em um aplicativo profissional você desejará proteger as senhas dos seus
usuários por questões de segurança. Isto é facilmente conseguido mapeando sua
classe User para algum "encoder" disponível. Por exemplo, para armazenar seus
usuário em memória, mas proteger a senha deles através da função de hash `sha1``,
faça o seguinte:
.. configuration-block::
.. code-block:: yaml
# app/config/config.yml
security:
# ...
providers:
in_memory:
memory:
users:
ryan: { password: bb87a29949f3a1ee0559f8a57357487151281386, roles: 'ROLE_USER' }
admin: { password: 74913f5cd5f61ec0bcfdb775414c2fb3d161b620, roles: 'ROLE_ADMIN' }
encoders:
Symfony\Component\Security\Core\User\User:
algorithm: sha1
iterations: 1
encode_as_base64: false
.. code-block:: xml
.. code-block:: php
// app/config/config.php
$container->loadFromExtension('security', array(
// ...
'providers' => array(
'in_memory' => array(
'memory' => array(
'users' => array(
'ryan' => array('password' => 'bb87a29949f3a1ee0559f8a57357487151281386', 'roles' => 'ROLE_USER'),
'admin' => array('password' => '74913f5cd5f61ec0bcfdb775414c2fb3d161b620', 'roles' => 'ROLE_ADMIN'),
),
),
),
),
'encoders' => array(
'Symfony\Component\Security\Core\User\User' => array(
'algorithm' => 'sha1',
'iterations' => 1,
'encode_as_base64' => false,
),
),
));
Ao definir ``iterations`` como ``1`` e ``encode_as_base64`` como false, a senha codificada
é simplesmente obtida como o resultado de ``sha1`` após uma iteração apenas, sem codificação
extra. Você pode agora calcular a senha codificada por código PHP (e.g. ``hash('sha1', 'ryanpass')``)
ou através de alguma ferramenta online como `functions-online.com`_ .
Se você estiver criando seus usuário dinamicamente e os armazenando no banco de dados,
você pode usar algoritmos the hash ainda mais complexos e então delegar em um objeto
encoder para ajudar a codificar as senhas. Por exemplo, suponha que seu objeto User é
``Acme\UserBundle\Entity\User`` (como no exemplo acima). Primeiro, configure o encoder para
aquele usuário:
.. configuration-block::
.. code-block:: yaml
# app/config/config.yml
security:
# ...
encoders:
Acme\UserBundle\Entity\User: sha512
.. code-block:: xml
.. code-block:: php
// app/config/config.php
$container->loadFromExtension('security', array(
// ...
'encoders' => array(
'Acme\UserBundle\Entity\User' => 'sha512',
),
));
Neste caso, você está utilizando um algoritmo mais forte ``sha512``. Além disso,
desde que você especificou o algoritmo (``sha512``) como um texto, o sistema
irá, por padrão, utilizar a função de hash 5000 vezes em uma linha e então o codificar
como base64. Em outras palavras, a senha foi muito codificada de maneira que a senha
não pode ser decodificada (isto é, você não pode determinar qual a senha
a partir da senha codificada).
Se você tem alguma espécie de formulário de registro para os visitantes, você precisará
a senha codificada para poder armazenar. Não importa o algoritmo que configurar para
sua classe User, a senha codificada pode sempre ser determinada da seguinte maneira
a partir de um controller:
.. code-block:: php
$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);
Obtendo o objeto User
~~~~~~~~~~~~~~~~~~~~~
Após a autenticação, o objeto ``User`` do usuário atual pode ser acessado através
do serviço ``security.context``. De dentro de um controller, faça o seguinte:
.. code-block:: php
public function indexAction()
{
$user = $this->get('security.context')->getToken()->getUser();
}
No controller, também existe o atalho:
.. code-block:: php
public function indexAction()
{
$user = $this->getUser();
}
.. note::
Usuários anônimos são tecnicamente autenticados, significando que o médodo ``isAuthenticated()``
de um objeto User autenticado anonimamente retornará verdadeiro. Para verificar se seu usuário está
realmente autenticado, verifique se o perfil ``IS_AUTHENTICATED_FULLY`` está atribuído ao mesmo.
Utilizando múltiplos User Providers
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Cada mecanismo de autenticação (exemplos: Autenticação HTTP, formulário de login, etc)
usa exatamente um user provider, e utilizará, por padrão, o primeiro user provider configurado. O que acontece
se você quiser que alguns de seus usuários sejam autenticados por arquivo de configuração e o
resto por banco de dados? Isto é possível criando um novo user provider que ativa os dois juntos:
.. configuration-block::
.. code-block:: yaml
# app/config/security.yml
security:
providers:
chain_provider:
chain:
providers: [in_memory, user_db]
in_memory:
users:
foo: { password: test }
user_db:
entity: { class: Acme\UserBundle\Entity\User, property: username }
.. code-block:: xml
in_memory
user_db
.. code-block:: php
// app/config/config.php
$container->loadFromExtension('security', array(
'providers' => array(
'chain_provider' => array(
'chain' => array(
'providers' => array('in_memory', 'user_db'),
),
),
'in_memory' => array(
'users' => array(
'foo' => array('password' => 'test'),
),
),
'user_db' => array(
'entity' => array('class' => 'Acme\UserBundle\Entity\User', 'property' => 'username'),
),
),
));
Agora, todos mecanismos de autenticação utilizarão o ``chain_provider``, já que é
o primeiro configurado. O ``chain_provider`` tentará carregar o usuário de ambos
providers ``in_memory`` e ``user_db``.
.. tip::
Se você não tem razões para separar seus usuários ``in_memory`` dos seus
usuários ``user_db``, você pode conseguir o mesmo resultado mais facilmente,
combinando as duas origens em um único provider:
.. configuration-block::
.. code-block:: yaml
# app/config/security.yml
security:
providers:
main_provider:
memory:
users:
foo: { password: test }
entity:
class: Acme\UserBundle\Entity\User,
property: username
.. code-block:: xml
.. code-block:: php
// app/config/config.php
$container->loadFromExtension('security', array(
'providers' => array(
'main_provider' => array(
'memory' => array(
'users' => array(
'foo' => array('password' => 'test'),
),
),
'entity' => array('class' => 'Acme\UserBundle\Entity\User', 'property' => 'username'),
),
),
));
Você pode ainda configurar o firewall ou mecanismos de autenticação individuais para utilizar
um user provider específico. Novamente, a menos que um provider seja especificado explicitamente,
o primeiro será sempre utilizado:
.. configuration-block::
.. code-block:: yaml
# app/config/config.yml
security:
firewalls:
secured_area:
# ...
provider: user_db
http_basic:
realm: "Secured Demo Area"
provider: in_memory
form_login: ~
.. code-block:: xml
.. code-block:: php
// app/config/config.php
$container->loadFromExtension('security', array(
'firewalls' => array(
'secured_area' => array(
// ...
'provider' => 'user_db',
'http_basic' => array(
// ...
'provider' => 'in_memory',
),
'form_login' => array(),
),
),
));
Neste exemplo, se um usuário tentar se autenticar através de autenticação HTTP, o sistema
utilizará o user provider ``in_memory``. Se o usuário tentar, porém, se autenticar através
do formulário de login, o provider ``user_db`` será usado (pois é o padrão para todo o firewall).
Para mais informações sobre a configuração do user provider e do firewall,
veja :doc:`/reference/configuration/security`.
Perfis (Roles)
--------------
A idéia de um "perfil" é chave no processo de autorização. Para cada usuário é atribuído
um conjunto de perfis e então cada recurso exige um ou mais perfis. Se um usuário
tem os perfis requeridos, o acesso é concedido. Caso contrário, o acesso é negado.
Perfis são muito simples e basicamente textos que você pode inventar e utilizar
de acordo com suas necessidades (embora perfis sejam objetos PHP internamente).
Por exemplo, se precisar limitar acesso a uma seção administrativa do blog
de seu website, você pode proteger a seção utilizando o perfil ``ROLE_BLOG_ADMIN``.
Este perfil não precisa de estar definido em lugar nenhum - você pode simplesmente
usar o mesmo.
.. note::
Todos os perfis **devem** começar com o prefixo ``ROLE_`` para serem gerenciados
pelo Symfony2. Se você definir seus próprios perfis com uma classe ``Role``
dedicada (mais avançado), não utilize o prefixo ``ROLE_``.
Hierarquia de Perfis
~~~~~~~~~~~~~~~~~~~~
Ao invés de associar muitos perfis aos usuários, você pode defined regras
de herança ao criar uma hierarquia de perfis:
.. configuration-block::
.. code-block:: yaml
# app/config/security.yml
security:
role_hierarchy:
ROLE_ADMIN: ROLE_USER
ROLE_SUPER_ADMIN: [ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH]
.. code-block:: xml
ROLE_USER
ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH
.. code-block:: php
// app/config/security.php
$container->loadFromExtension('security', array(
'role_hierarchy' => array(
'ROLE_ADMIN' => 'ROLE_USER',
'ROLE_SUPER_ADMIN' => array('ROLE_ADMIN', 'ROLE_ALLOWED_TO_SWITCH'),
),
));
Na configuração acima, usuários com o perfil ``ROLE_ADMIN`` terão também o perfil ``ROLE_USER``.
O perfil ``ROLE_SUPER_ADMIN`` tem os ``ROLE_ADMIN``, ``ROLE_ALLOWED_TO_SWITCH`` e ``ROLE_USER``
(herdado do ``ROLE_ADMIN``).
Saindo do sistema
-----------------
Normalmente, você também quer que seus usuários possam sair do sistema.
Felizmente, o firewall consegue lidar com isso automaticamente quando o parâmetro
de configuração ``logout`` está ativo:
.. configuration-block::
.. code-block:: yaml
# app/config/config.yml
security:
firewalls:
secured_area:
# ...
logout:
path: /logout
target: /
# ...
.. code-block:: xml
.. code-block:: php
// app/config/config.php
$container->loadFromExtension('security', array(
'firewalls' => array(
'secured_area' => array(
// ...
'logout' => array('path' => 'logout', 'target' => '/'),
),
),
// ...
));
Uma vez que está configurado no seu firewall, redirecionando o usuário para ``/logout``
(ou qualquer outro caminho que configurar em ``path``), o usuário não estará
mais autenticado. O usuário será então redirecionado para a página principal
(o valor definido no parâmetro ``target``). Ambas configurações ``path`` e ``target``
tem valor padrão iguais ao especificado aqui. Em outras palavras, a menos que
precise personalizar, você pode simplesmente os omitir completamente e simplificar
sua configuração:
.. configuration-block::
.. code-block:: yaml
logout: ~
.. code-block:: xml
.. code-block:: php
'logout' => array(),
Note que você *não* precisará implementar o controller para a URL ``/logout``
já que o firewall cuida disso. Você *deve*, entretante, precisar criar uma rota para que
possa usar para gerar a URL:
.. warning::
Com o Symfony 2.1, você *deve* ter uma rota que corresponde ao seu caminho para
logout. Sem esta rota, o logout não irá funcionar.
.. configuration-block::
.. code-block:: yaml
# app/config/routing.yml
logout:
pattern: /logout
.. code-block:: xml
.. code-block:: php
// app/config/routing.php
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\Route;
$collection = new RouteCollection();
$collection->add('logout', new Route('/logout', array()));
return $collection;
Uma vez que o usuário não está mais autenticado, ele será redirecionado para o
que tiver definido no parâmetro ``target``. Para mais informações sobre
a configuração de logout, veja
:doc:`Security Configuration Reference`.
Controle de Acesso em Templates
-------------------------------
Se você quiser checar se o usuário atual tem um determinado perfil de dentro de
uma template, use a função:
.. configuration-block::
.. code-block:: html+jinja
{% if is_granted('ROLE_ADMIN') %}
Delete
{% endif %}
.. code-block:: html+php
isGranted('ROLE_ADMIN')): ?>
Delete
.. note::
Se você usar esta função e *não* estiver em uma URL que está atrás de um
firewall ativo, uma exceção será gerada. Novamente, quase sempre é uma boa
idéia ter um firewall principal que protege todas as URLs (como visto neste
capítulo).
Controle de Acesso em Controllers
---------------------------------
Se você quer verificar se o usuário atual tem um perfil de dentro de um controller,
use o método ``isGranted`` do contexto de segurança:
.. code-block:: php
public function indexAction()
{
// show different content to admin users
if ($this->get('security.context')->isGranted('ADMIN')) {
// Load admin content here
}
// load other regular content here
}
.. note::
Um firewall deve estar ativo ou uma exceção será gerada quanto o método ``isGranted``
for chamado. Veja a nota acima sobre templates para mais detalhes.
Passando por outro usuário
--------------------------
As vezes, é útil poder trocar de um usuário para outro sem ter que sair e se
autenticar novamente (por exemplo quando você está depurando or tentando entender
uma falha que um usuário vê e você não consegue reproduzir). Isto pode ser feito
ativando o listener ``switch_user`` do firewall:
.. configuration-block::
.. code-block:: yaml
# app/config/security.yml
security:
firewalls:
main:
# ...
switch_user: true
.. code-block:: xml
.. code-block:: php
// app/config/security.php
$container->loadFromExtension('security', array(
'firewalls' => array(
'main'=> array(
// ...
'switch_user' => true
),
),
));
Para mudar para outro usuário, basta adicionar o parâmetro de URL ``_switch_user``
indicando o usuário (username) na URL atual:
http://example.com/somewhere?_switch_user=thomas
Para voltar ao usuário original, use como nome de usuário o texto ``_exit``:
http://example.com/somewhere?_switch_user=_exit
Claro que esta funcionalidade precisar estar disponível para um grupo reduzido de usuários.
Por padrão, o acesso é restrito a usuários que tem o perfil ``ROLE_ALLOWED_TO_SWITCH``.
O nome deste perfil pode ser modificado através do parâmetro de configuração ``role``.
Para segurança extra, você pode ainda mudar o nome do parâmetro de URL através da
configuração ``parameter``:
.. configuration-block::
.. code-block:: yaml
# app/config/security.yml
security:
firewalls:
main:
// ...
switch_user: { role: ROLE_ADMIN, parameter: _want_to_be_this_user }
.. code-block:: xml
.. code-block:: php
// app/config/security.php
$container->loadFromExtension('security', array(
'firewalls' => array(
'main'=> array(
// ...
'switch_user' => array('role' => 'ROLE_ADMIN', 'parameter' => '_want_to_be_this_user'),
),
),
));
Autenticação Sem Estado
-----------------------
Por padrão, o Symfony2 confia a um cookie (a Session) para persistir o contexto de
segurança de um usuário. Se você utiliza, porém, certificados ou autenticação HTTP, por exemplo,
persistência não é necessário já que as credenciais estão disponíveis em
cada requisição. Neste caso, e se não precisar de armazenar nada entre as requisições,
você pode ativar a autenticação sem estado (que significa que nenhum cookie será criado pelo
Symfony2):
.. configuration-block::
.. code-block:: yaml
# app/config/security.yml
security:
firewalls:
main:
http_basic: ~
stateless: true
.. code-block:: xml
.. code-block:: php
// app/config/security.php
$container->loadFromExtension('security', array(
'firewalls' => array(
'main' => array('http_basic' => array(), 'stateless' => true),
),
));
.. note::
Se utiliza formulário de login, o Symfony2 criará um cookie mesmo se
você definir ``stateless`` como ``true``.
Palavras Finais
---------------
Segurança pode ser um assunto profundo e complexo de se resolver em uma aplicação.
Felizmente, o componente de segurança do Symfony segue um bom modelo
baseado em *autenticação* e *autorização*. Autenticação, que sempre acontece antes,
é gerenciada pelo firewall cujo trabalho é determinar a identidade do usuário
através de diversos possíveis métodos (exemplo, autenticação HTTP, formulário de login, etc).
No passo-a-passo, você encontrará exemplos de como outros métodos de autenticação
podem ser utilizados, incluindo como implementar o funcionalidade de
"Lembrar de mim" baseada em cookie.
Uma vez que o usuário está autenticado, a camada de autorização pode determinar
se o usuário deve ou não deve ter acesso a um recurso específico. Comumente,
*perfis* são aplicados a URLs, classes ou métodos e se o usuário atual não
possuir o perfil, o acesso é negado. A camada de autorização, porém,
é muito mais extensa e segue o sistema de votação onde várias partes
podem determinar se o usuário atual deve ter acesso a determinado recurso.
Saiba mais sobre este e outros tópicos no passo-a-passo.
Aprenda mais do Passo-a-Passo
-----------------------------
* :doc:`Forçando HTTP/HTTPS `
* :doc:`Coloque usuários por IP na lista negra com um voter personalizado `
* :doc:`Listas de Controle de Acesso (ACLs) `
* :doc:`/cookbook/security/remember_me`
.. _`componente de segurança`: https://github.com/symfony/Security
.. _`JMSSecurityExtraBundle`: https://github.com/schmittjoh/JMSSecurityExtraBundle
.. _`FOSUserBundle`: https://github.com/FriendsOfSymfony/FOSUserBundle
.. _`implemente a interface \Serializable`: http://php.net/manual/en/class.serializable.php
.. _`functions-online.com`: http://www.functions-online.com/sha1.html