meccanismo di autenticazione basata Dottrina nel progetto Symfony2
-
11-10-2019 - |
Domanda
Sto lavorando su un piccolo progetto Doctrine2-backed usando Symfony2 per la prima volta. Attualmente sto lottando con la componente di sicurezza di Symfony2, per l'esattezza con il meccanismo di autenticazione descritto nella documentazione .
Vorrei utilizzare l'autenticazione basata su form e ha fatto tutto indicato nei documenti:
Ho un file di configurazione security.yml che assomiglia a questo:
security.config:
firewalls:
admin:
pattern: /admin/.*
form-login: true
logout: true
login_path: /login
check_path: /validateLogin
always_use_default_target_path: false
target_path_parameter: target
check_page:
pattern: /validateLogin
form-login: true
login_path: /login
check_path: /validateLogin
always_use_default_target_path: false
target_path_parameter: target
public:
pattern: /.*
security: false
providers:
admin:
password_encoder: md5
entity:
class: AdminBundle:User
property: username
access_control:
- { path: /admin/.*, role: ROLE_ADMIN }
- { path: /validateLogin, role: IS_AUTHENTICATED_ANONYMOUSLY }
role_hierarchy:
ROLE_ADMIN: ROLE_USER
Il check_page è esclusa dalla zona "secureless" dopo aver letto un thread simile devcomments .
Nella mia configurazione di routing ho includono due regole per l'autenticazione:
_security_login:
pattern: /login
defaults:
_controller: PublicBundle:Auth:index
_security_check:
pattern: /validateLogin
La classe di entità che sto usando per rappresentare un utente è un'entità Doctrine2 e implementa le AccountInterface:
<?php
namespace Application\AdminBundle\Entity;
use Symfony\Component\Security\User\AccountInterface;
/**
* @orm:Entity
*/
class User implements AccountInterface
{
/**
* @orm:Id
* @orm:Column(type="integer")
* @orm:GeneratedValue(strategy="IDENTITY")
*/
protected $id;
/**
* @orm:Column(type="string", length="255")
*/
protected $username;
/**
* @orm:Column(type="string", length="40")
*/
protected $password;
public function getId()
{
return $this->id;
}
public function setId($id)
{
$this->id = $id;
}
public function getUsername()
{
return $this->username;
}
public function setUsername($username)
{
$this->username = $username;
}
public function getPassword()
{
return $this->password;
}
public function setPassword($password)
{
$this->password = $password;
}
/**
* Implementing the AccountInterface interface
*/
public function __toString()
{
return $this->getUsername();
}
public function getRoles()
{
return array('ROLE_ADMIN');
}
public function eraseCredentials()
{
}
public function getSalt()
{
return $this->getId();
}
}
Nella classe AuthController Sto usando il codice di esempio dai documenti di Symfony2:
public function indexAction()
{
if ($this->get('request')->attributes->has(SecurityContext::AUTHENTICATION_ERROR)) {
$error = $this->get('request')->attributes->get(SecurityContext::AUTHENTICATION_ERROR);
} else {
$error = $this->get('request')->getSession()->get(SecurityContext::AUTHENTICATION_ERROR);
}
return
$this->render(
'PublicBundle:Auth:index.twig',
array(
'last_username' => $this->get('request')->getSession()->get(SecurityContext::LAST_USERNAME),
'error' => $error));
}
Ora arriva il problema: La regola di reindirizzamento da http: //symfony2.localhost/app_dev.php/admin/test di http: opere //symfony2.localhost/app_dev.php/login ma dopo aver inserito nome utente / password e compila il form di login, mi viene reindirizzato all'URL di login di nuovo senza un messaggio di errore.
So che questo è probabilmente una questione veramente di base, ma dal momento che non v'è ancora molta documentazione su Symfony2, credo che questo sia un buon posto per fare domande come questa. In generale, ci sono alcuni punti all'interno di un progetto Symfony2 che sembrano funzionare magicamente (ovviamente DI-backed) che rendono il processo di apprendimento un po 'difficile. I miei pensieri su come funziona l'autenticazione è che v'è una certa magica controller che cattura l'azione validateLogin, cerca un repository di entità per la mia entità User, chiama findOneBy ( 'username' => $ username) e mette a confronto le password ... è questo giusto?
Grazie in anticipo per qualsiasi suggerimento, ho googling questo problema per più alcune ore ormai ... :)
Paul
Soluzione
I miei pensieri su come funziona l'autenticazione è che v'è una certa magica controller che cattura l'azione validateLogin, cerca un repository di entità per la mia entità User, chiama findOneBy ( 'username' => $ username) e mette a confronto le password .. . è questo diritto?
Ti sbagli. L'autenticazione non comporta alcun controllo, che è il motivo per cui non si specifica alcun percorso in _security_check
. Auth si basa su EventDispatcher
. Ogni volta che si specifica una certa ascoltatore nel firewall (ad es. form_login
, anonymous
, logout
ecc) è in realtà registra un nuovo listener per l'evento core.security
. Symfony\Component\HttpKernel\Security\Firewall::handle()
è un luogo dove questi ascoltatori sono in realtà registrati.
Il generale, di flusso semplificato:
- utente riempie form di login (
_username
e campi_password
). - richiesta viene gestita da Symfony2.
- evento
core.security
viene licenziato. - EventDispatcher notifica a tutti gli ascoltatori.
-
UsernamePasswordFormAuthenticationListener
viene licenziato (metodohandle()
) e verifica se:- URL corrisponde opzione
check_path
. - Richiesta ha entrambi i parametri
_username
e_password
.
- URL corrisponde opzione
- Listener cerca di utente autenticazione (metodo
attemptAuthentication()
). - Gestore incendi di autenticazione tutti i fornitori registrati.
- Infine,
DaoAuthenticationProvider
viene licenziato e cerca di recuperare utente utilizzando classe repository utente di Doctrine. - Se tutto va bene
UsernamePasswordToken
(che contengono oggetto$user
restituito dal metodoloadUserByUsername()
) viene restituito e l'utente viene reindirizzato.
In effetti meccanismo di sicurezza è piuttosto complessa e difficile da comprendere (documentazione non è ancora finito). Ma quando finalmente capito come funziona, allora vedrete come potente meccanismo sia.
Ho scritto il mio meccanismo di autenticazione e funziona benissimo.
-
Configurazione:
sto usando provider personalizzato ed encoder.
security.config: providers: main: id: project.user_repository # DI id. Doctrine's UserRepositry check_path: /login-check encoders: main: class: Project\SiteBundle\Entity\User id: security.encoder.sha512 # DI id. Service %security.encoder.digest.class% (with "sha512" as first parameter) firewalls: restricted: pattern: /panel/.* form_login: check_path: /login-check public: pattern: /.* anonymous: true form_login: check_path: /login-check logout: true access_control: - { path: /panel/.*, role: ROLE_USER } - { path: /.*, role: IS_AUTHENTICATED_ANONYMOUSLY }
Come si può vedere
/panel/*
è limitato, mentre/*
è pubblico. -
Servizio
security.encoder.sha512
è un built-in encoder:<service id="security.encoder.sha512" class="%security.encoder.digest.class%"> <argument>sha512</argument> </service>
-
Project\SiteBundle\Entity\User
:/** * @orm:Entity(repositoryClass="Project\SiteBundle\Repository\UserRepository") */ class User implements AdvancedAccountInterface { /** * @orm:Id @orm:Column(type="integer") * @orm:GeneratedValue(strategy="AUTO") */ protected $id; /** * @orm:Column(unique=true, nullable=true) */ protected $email; /** * @orm:Column(unique=true, nullable=true) */ protected $xmpp; /** * @orm:Column(length=128) */ protected $password; /** * @orm:Column(length=16) */ protected $salt; // User can be logged in using email address or xmpp adress. // Dozens of getters/setters here. }
-
Project\SiteBundle\Repository\UserRepository
class UserRepository extends EntityRepository implements UserProviderInterface { public function loadUserByUsername($username) { $dql = sprintf(' SELECT u FROM %s u WHERE u.email = :id OR u.xmpp = :id ', $this->_entityName); $user = null; try { $user = $this->_em->createQuery($dql)->setParameter('id', $username)->getSingleResult(); } catch (ORMException $e) { throw new UsernameNotFoundException("User $username not found.", $e->getCode(), $e); } return $user; } public function loadUserByAccount(AccountInterface $user) { return $this->loadUserByUsername($user->getUsername()); } }
-
percorsi di sicurezza e di controllo sono uguali ai suoi.
Altri suggerimenti
Si dovrebbe utilizzare le https://github.com/FriendsOfSymfony/FOSUserBundle FOS UserBundle, implementa tutto questo con Doctrine 2 e ha tonnellate di caratteristiche.
La ragione, in sostanza, il motivo per cui la pagina di accesso carichi di nuovo con alcun messaggio di errore è perché, ironia della sorte, le impostazioni di protezione non sono impostati per consentire l'accesso anonimo alla pagina di login.