0

我正在实现一个自定义身份验证提供程序,以使用外部 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;
    }
}
4

1 回答 1

1

我已经解决了修改 security.yml

firewalls:
        dev:
            pattern:  ^/(_(profiler|wdt)|css|images|js)/
            security: false

        login:
            pattern:  ^/app/login$
            security: false

        secured_area:
            pattern:    ^/app
            api: true
            logout:
                path:   /app/logout
                target: /

和 ApiListener.php

public function handle(GetResponseEvent $event) {

        if( $this->securityContext->getToken() ){
            return;
        }

因为在防火墙 (app/*) 下的每个 url 上,symfony 都会调用我的侦听器的句柄方法,如果用户已经登录,则安全令牌已经设置并且我返回

和 check_login 功能

public function securityCheckAction() {
        // The security layer will NOT intercept this request
        return $this->redirect($this->generateUrl('manuel_myapp_index_after_login'));

check_login 是登录表单的动作,check_login 动作在防火墙下,所以,我的listerner 的handle 方法将第一次被调用,如果凭据正确(使用我的外部api)我强迫symfony 使用in_memory 用户对于登录,然后将执行 check_login 操作。然后,当用户在防火墙下访问另一个页面时,handle 方法将被调用,但身份验证令牌已设置,因此 handle 方法将返回并且一切正常

外部 api 登录现在有效!

于 2013-04-05T14:39:33.003 回答