我正在实现一个自定义身份验证提供程序,以使用外部 api,大致遵循 symfony 网站上的食谱。它几乎可以正常工作,侦听器正确侦听登录表单,然后调用返回经过身份验证的令牌的身份验证函数,问题是即使我为securityContextInterface设置了经过身份验证的令牌,系统也会返回错误的登录页面证书。在我使用的代码下它会是什么?
安全.yml
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
login:
pattern: ^/app/login$
security: false
api_secured:
provider: in_memory
pattern: ^/app
form_login:
login_path: /app/login
check_path: /app/login_check
logout:
path: /app/logout
target: /
api: true
服务.yml
api.security.authentication.provider:
class: Manuel\Myapp\MyAppBundle\Security\Authentication\Provider\ApiProvider
arguments: ['', %kernel.cache_dir%/security/nonces]
api.security.authentication.listener:
class: Manuel\Myapp\MyAppBundle\Security\Firewall\ApiListener
arguments: [@security.context, @security.authentication.manager, %api.url%]
ApiFactory.php
namespace Manuel\Myapp\MyAppBundle\DependencyInjection\Security\Factory;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\DefinitionDecorator;
use Symfony\Component\Config\Definition\Builder\NodeDefinition;
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\SecurityFactoryInterface;
class ApiFactory implements SecurityFactoryInterface
{
public function create(ContainerBuilder $container, $id, $config, $userProvider, $defaultEntryPoint)
{
$providerId = 'security.authentication.provider.api.'.$id;
$container
->setDefinition($providerId, new DefinitionDecorator('api.security.authentication.provider'))
->replaceArgument(0, new Reference($userProvider))
;
$listenerId = 'security.authentication.listener.api.'.$id;
$listener = $container->setDefinition($listenerId, new DefinitionDecorator('api.security.authentication.listener'));
return array($providerId, $listenerId, $defaultEntryPoint);
}
public function getPosition()
{
return 'pre_auth';
}
public function getKey()
{
return 'api';
}
public function addConfiguration(NodeDefinition $node)
{
}
}
ApiListener.php
namespace Manuel\Myapp\MyAppBundle\Security\Firewall;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\Security\Http\Firewall\ListenerInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\SecurityContextInterface;
use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface;
use Manuel\Myapp\MyAppBundle\Security\Authentication\Token\ApiUserToken;
use Httpful\Request;
class ApiListener implements ListenerInterface {
protected $securityContext;
protected $authenticationManager;
protected $container;
public function __construct(SecurityContextInterface $securityContext, AuthenticationManagerInterface $authenticationManager, $api)
{
$this->securityContext = $securityContext;
$this->authenticationManager = $authenticationManager;
//Prendo l'url delle api
//Viene passato da services.yml alla classe
$this->api = $api;
}
public function handle(GetResponseEvent $event)
{
$request = $event->getRequest();
$data = $request->request->all();
//Esiste username e password ?
if(!array_key_exists('_username', $data) || !array_key_exists('_password', $data)) {
//Ritorna alla pagina di login con bad credentials
$this->securityContext->setToken(null);
return;
}
//Autentico in remoto
$username = $data['_username'];
$password = $data['_password'];
$response = Request::post($this->api."/token/new.json")
->body(array(
'username'=> $username,
'password'=> $password))
->expectsJson()
->sendsForm()
->send();
$decode = json_decode($response);
//Se esiste allora vado avanti se no muoio
if(!$decode->success) {
$this->securityContext->setToken(null);
return;
}
$token = new ApiUserToken();
$token->setUser(''.$decode->user);
$token->token = $decode->token;
try {
$authToken = $this->authenticationManager->authenticate($token);
$this->securityContext->setToken($authToken);
} catch (AuthenticationException $failed) {
// ... si potrebbe loggare qualcosa in questo punto
// Per negare l'autenticazione, pulire il token. L'utente sarà rinviato alla pagina di login.
$this->securityContext->setToken(null);
return;
// Negare l'autenticazione con una risposta HTTP '403 Forbidden'
//$response = new Response();
//$response->setStatusCode(403);
//$event->setResponse($response);
}
}
}
如果我写:
$authToken = $this->authenticationManager->authenticate($token);
var_dump($authToken); die();
$this->securityContext->setToken($authToken);
结果是:
object(Manuel\Myapp\MyAppBundle\Security\Authentication\Token\ApiUserToken)#4780 (5) {["user":"Symfony\Component\Security\Core\Authentication\Token\AbstractToken":private]=> object(Symfony\Component\Security\Core\User\User)#4782 (7) { ["username":"Symfony\Component\Security\Core\User\User":private]=> string(4) "user" ["password":"Symfony\Component\Security\Core\User\User":private]=> string(15) "10dmao!?postino" ["enabled":"Symfony\Component\Security\Core\User\User":private]=> bool(true) ["accountNonExpired":"Symfony\Component\Security\Core\User\User":private]=> bool(true) ["credentialsNonExpired":"Symfony\Component\Security\Core\User\User":private]=> bool(true) ["accountNonLocked":"Symfony\Component\Security\Core\User\User":private]=> bool(true) ["roles":"Symfony\Component\Security\Core\User\User":private]=> array(1) { [0]=> string(9) "ROLE_USER" } } ["roles":"Symfony\Component\Security\Core\Authentication\Token\AbstractToken":private]=> array(1) { [0]=> object(Symfony\Component\Security\Core\Role\Role)#4779 (1) { ["role":"Symfony\Component\Security\Core\Role\Role":private]=> string(9) "ROLE_USER" } } ["authenticated":"Symfony\Component\Security\Core\Authentication\Token\AbstractToken":private]=> bool(true) ["attributes":"Symfony\Component\Security\Core\Authentication\Token\AbstractToken":private]=> array(0) { } }
所以是正确的。
ApiUserToken.php
namespace Manuel\Myapp\MyAppBundle\Security\Authentication\Token;
use Symfony\Component\Security\Core\Authentication\Token\AbstractToken;
class ApiUserToken extends AbstractToken
{
public $token;
public function __construct(array $roles = array())
{
parent::__construct($roles);
// If the user has roles, consider it authenticated
$this->setAuthenticated(true);
}
public function getCredentials()
{
return '';
}
}
ApiProvider.php
namespace Manuel\Myapp\MyAppBundle\Security\Authentication\Provider;
use Symfony\Component\Security\Core\Authentication\Provider\AuthenticationProviderInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\Exception\NonceExpiredException;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Manuel\Myapp\MyAppBundle\Security\Authentication\Token\ApiUserToken;
class ApiProvider implements AuthenticationProviderInterface
{
private $userProvider;
private $cacheDir;
public function __construct(UserProviderInterface $userProvider, $cacheDir)
{
$this->userProvider = $userProvider;
$this->cacheDir = $cacheDir;
}
public function authenticate(TokenInterface $token)
{
//Devo aggiungere utente
$user = $this->userProvider->loadUserByUsername("user");
if ($user) {
$authenticatedToken = new ApiUserToken($user->getRoles());
$authenticatedToken->setUser($user);
return $authenticatedToken;
}
throw new AuthenticationException('The API authentication failed.');
}
public function supports(TokenInterface $token) {
return $token instanceof ApiUserToken;
}
}