Symfony Security Component is a very complex/flexible system, there are a lot of concepts with a lot of features and require some time to figure out it's workflow.
- First of all the Security is designed as a firewall, each Request.route is matched against Firewall.patterns
- The Firewall is no more than a Listener that waits for requests (onKernelRequest)
- All Requests are analised and if there is a Firewall.pattern who match the Request.route then an Security.Context is generated with the defined rules.
- The Firewall rules define the Security workflow actions that are taken before Request main goal is reached.
- Each Firewall.pattern define its own Security.Context.
- Each Security.Context is defined for one or more Authentication.Listeners and one or More Authentication.Providers
- The Authentication.Listeners when dispatched they try Authenticate the Token
- The Authentication.Providers when called by the Authentication.Listeners and case the Token is supported then they try Authenticate the Token against to the Users list provided by the UserProvider.
# Custom Symfony Security Authentication and Silex 2
# Register Security Service and define Firewalls
$app->register(new SecurityServiceProvider());
$app['security.firewalls'] = array(
'myhttpsecured' => array(
'pattern' => '/howto-security/case1/admin/*',
'myhttp' => true,
),
)
# Define Custom Autentication Listener and Custom Authentication Provider
$app['security.authentication_listener.factory.myhttp'] = $app->protect(function ($name, $options) use ($app) {
// define the authentication provider object
$app['security.authentication_provider.'.$name.'.myhttp'] = function () use ($app) {
return new ArrayAuthenticationProvider(array('admin2' => array('password' => 'adminpasswd', 'roles' => array('ROLE_ADMIN'))));
};
// define the authentication listener object
$app['security.authentication_listener.'.$name.'.myhttp'] = function () use ($app) {
return new HttpBasicAuthenticationListener($app['security.authentication_manager'], $app['security.token_storage']);
};
return array(
// the authentication provider id
'security.authentication_provider.'.$name.'.myhttp',
// the authentication listener id
'security.authentication_listener.'.$name.'.myhttp',
// the entry point id
null,
// the position of the listener in the stack
'pre_auth'
);
});
# HttpBasicAuthenticationListener
<?php
namespace app\SimpleSecurity\AuthenticationListener;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface;
use Symfony\Component\Security\Http\Firewall\ListenerInterface;
use Symfony\Component\HttpFoundation\Response;
use app\SimpleSecurity\Token\LoginPasswordToken;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use app\SimpleSecurity\AuthenticationEntryPoint\HttpBasicAuthenticationEntryPoint;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
class HttpBasicAuthenticationListener implements ListenerInterface
{
private $authenticationManager;
private $tokenStorage;
private $authenticationEntryPoint;
public function __construct(AuthenticationManagerInterface $authenticationManager, TokenStorageInterface $tokenStorage)
{
$this->authenticationManager = $authenticationManager;
$this->tokenStorage = $tokenStorage;
$this->authenticationEntryPoint = new HttpBasicAuthenticationEntryPoint();
}
public function handle(GetResponseEvent $event)
{
$request = $event->getRequest();
if (false === $username = $request->headers->get('PHP_AUTH_USER', false)) {
$event->setResponse($this->authenticationEntryPoint->start($request));
return;
}
if (null !== $token = $this->tokenStorage->getToken()) {
if ($token->isAuthenticated() && $token->getUsername() === $username) {
return;
}
}
// We retrieve info required to authenticate current user from request and encapsulate them into a Token.
$token = new LoginPasswordToken($request->headers->get('PHP_AUTH_USER'), $request->headers->get('PHP_AUTH_PW'));
try {
$authenticatedToken = $this->authenticationManager->authenticate($token);
$this->tokenStorage->setToken($authenticatedToken);
//
} catch (AuthenticationException $e) {
$event->setResponse($this->authenticationEntryPoint->start($request, $e));
}
}
}
# ArrayAuthenticationProvider
<?php
namespace app\SimpleSecurity\AuthenticationProvider;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authentication\Provider\AuthenticationProviderInterface;
use app\SimpleSecurity\Token\LoginPasswordToken;
/**
* This authentication provider can authenticate ONLY a LoginPasswordToken
*/
class ArrayAuthenticationProvider implements AuthenticationProviderInterface
{
private $users;
public function __construct(array $users)
{
$this->users = $users;
}
public function authenticate(TokenInterface $token)
{
foreach ($this->users as $username => $info) {
if ($username === $token->getUsername() && $info['password'] === $token->getCredentials()) {
return new LoginPasswordToken($username, $info['password'], $info['roles']); // authenticated token
}
}
}
public function supports(TokenInterface $token)
{
return $token instanceof LoginPasswordToken;
}
}
# HttpBasicAuthenticationEntryPoint
<?php
namespace app\SimpleSecurity\AuthenticationEntryPoint;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;
/**
* Goal: If the current user is not identified "an HTTP login box should appear".
*/
class HttpBasicAuthenticationEntryPoint implements AuthenticationEntryPointInterface
{
public function start(Request $request, AuthenticationException $authException = null)
{
return new Response(null, 401, array('WWW-Authenticate' => 'Basic realm="You are accessing a restricted area"'));
}
}
# LoginPasswordToken
<?php
namespace app\SimpleSecurity\Token;
use Symfony\Component\Security\Core\Authentication\Token\AbstractToken;
use app\SimpleSecurity\User\Entity\User as UserEntity;
class LoginPasswordToken extends AbstractToken
{
public function __construct($login, $password, array $roles = array())
{
parent::__construct($roles);
parent::setAuthenticated(count($roles) > 0); // this avoid to mark a token authenticated in AuthenticationListener
$this->setUser(new UserEntity(array('username' => $login, 'password' => $password, 'roles' => $roles )));
$this->credentials = $password;
}
public function getCredentials()
{
return $this->credentials;
}
}
## References:
* http://silex.sensiolabs.org/doc/master/providers/security.html
* http://symfony.com/doc/current/cookbook/security/index.html
* https://github.com/silexphp/Silex/blob/master/doc/providers/security.rst
* http://symfony.com/doc/current/cookbook/security/custom_authentication_p...
* https://github.com/quazardous/silex-user-pack
* http://www.jasongrimes.org/2014/09/simple-user-management-in-silex/
* http://symfony.com/blog/new-in-symfony-2-6-security-component-improvements
* https://www.youtube.com/watch?v=C1y6fxetP5k
* https://speakerdeck.com/rouffj/mastering-the-security-components-authent...
* https://www.youtube.com/watch?v=xQyEXzug7P8
* http://www.slideshare.net/kriswallsmith/love-and-loss-a-symfony-security...