2012-01-30 133 views
10

我正在使用适用于其他应用程序的API使用Symfony2应用程序。 我想确保对API的访问。对于这部分我没有问题。在Symfony2中使用自定义身份验证提供程序

但我必须使这个连接可用不与通常的登录/密码对,但只是与一个API密钥。

所以我去了官方网站和它的真棒食谱creating a custom authentication provider,正是我所需要的,我对自己说。

这个例子并不是我所需要的,但我决定根据自己的需要进行调整。

不幸的是我没有成功。

我给你我的代码,我会解释我的问题。

这里是我创建认证提供者和听者厂:

<?php 

namespace Pmsipilot\UserBundle\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 
{ 
    /** 
    * @param \Symfony\Component\DependencyInjection\ContainerBuilder $container 
    * @param string $id 
    * @param aray $config 
    * @param string $userProvider 
    * @param string $defaultEntryPoint 
    * @return array 
    */ 
    public function create(ContainerBuilder $container, $id, $config, $userProvider, $defaultEntryPoint) 
    { 
    $providerId = 'security.authentification.provider.api.'.$id; 
    $container 
     ->setDefinition($providerId, new DefinitionDecorator('api.security.authentification.provider')) 
     ->replaceArgument(0, new Reference($userProvider)) 
    ; 

    $listenerId = 'security.authentification.listener.api.'.$id; 
    $listener = $container->setDefinition($listenerId, new DefinitionDecorator('api.security.authentification.listener')); 

    return array($providerId, $listenerId, $defaultEntryPoint); 
    } 

    /** 
    * @return string 
    */ 
    public function getPosition() 
    { 
    return 'http'; 
    } 

    /** 
    * @return string 
    */ 
    public function getKey() 
    { 
    return 'api'; 
    } 

    /** 
    * @param \Symfony\Component\Config\Definition\Builder\NodeDefinition $node 
    * @return void 
    */ 
    public function addConfiguration(NodeDefinition $node) 
    { 
    } 
} 

下一页我的听众代码:

<?php 

namespace Pmsipilot\UserBundle\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 Symfony\Component\Security\Core\Authentication\Token\TokenInterface; 
use Pmsipilot\UserBundle\Security\WsseUserToken; 
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; 
use Symfony\Component\Security\Core\Exception\BadCredentialsException; 

class ApiListener implements ListenerInterface 
{ 
    protected $securityContext; 
    protected $authenticationManager; 

    /** 
    * Constructor for listener. The parameters are defined in services.xml. 
    * 
    * @param \Symfony\Component\Security\Core\SecurityContextInterface $securityContext 
    * @param \Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface $authenticationManager 
    */ 
    public function __construct(SecurityContextInterface $securityContext, AuthenticationManagerInterface $authenticationManager) 
    { 
    $this->securityContext = $securityContext; 
    $this->authenticationManager = $authenticationManager; 
    } 

    /** 
    * Handles login request. 
    * 
    * @param \Symfony\Component\HttpKernel\Event\GetResponseEvent $event 
    * @return void 
    */ 
    public function handle(GetResponseEvent $event) 
    { 
    $request = $event->getRequest(); 

    $securityToken = $this->securityContext->getToken(); 

    if($securityToken instanceof AuthenticationToken) 
    { 
     try 
     { 
     $this->securityContext->setToken($this->authenticationManager->authenticate($securityToken)); 
     } 
     catch(\Exception $exception) 
     { 
     $this->securityContext->setToken(null); 
     } 
    } 
    } 
} 

我的身份验证提供者的代码:

<?php 

namespace Pmsipilot\UserBundle\Security\Authentication\Provider; 

use Symfony\Component\Security\Core\Authentication\Provider\AuthenticationProviderInterface; 
use Symfony\Component\Security\Core\Encoder\EncoderFactoryInterface; 
use Symfony\Component\Security\Core\User\UserProviderInterface; 
use Symfony\Component\Security\Core\User\UserCheckerInterface; 
use Symfony\Component\Security\Core\User\UserInterface; 
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException; 
use Symfony\Component\Security\Core\Exception\AuthenticationServiceException; 
use Symfony\Component\Security\Core\Exception\BadCredentialsException; 
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; 
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; 

class ApiProvider implements AuthenticationProviderInterface 
{ 
    private $userProvider; 

    /** 
    * Constructor. 
    * 
    * @param \Symfony\Component\Security\Core\User\UserProviderInterface $userProvider An UserProviderInterface instance 
    */ 
    public function __construct(UserProviderInterface $userProvider) 
    { 
    $this->userProvider = $userProvider; 
    } 

    /** 
    * @param string $username 
    * @param \Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken $token 
    * @return mixed 
    * @throws \Symfony\Component\Security\Core\Exception\AuthenticationServiceException|\Symfony\Component\Security\Core\Exception\UsernameNotFoundException 
    */ 
    protected function retrieveUser($username, UsernamePasswordToken $token) 
    { 
    $user = $token->getUser(); 
    if($user instanceof UserInterface) 
    { 
     return $user; 
    } 

    try 
    { 
     $user = $this->userProvider->loadUserByApiKey($username, $token->getCredentials()); 

     if(!$user instanceof UserInterface) 
     { 
     throw new AuthenticationServiceException('The user provider must return a UserInterface object.'); 
     } 

     return $user; 
    } 
    catch (\Exception $exception) 
    { 
     throw new AuthenticationServiceException($exception->getMessage(), $token, 0, $exception); 
    } 
    } 

    /** 
    * @param TokenInterface $token 
    * @return null|\Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken 
    * @throws \Symfony\Component\Security\Core\Exception\AuthenticationServiceException|\Symfony\Component\Security\Core\Exception\BadCredentialsException|\Symfony\Component\Security\Core\Exception\UsernameNotFoundException 
    */ 
    function authenticate(TokenInterface $token) 
    { 
    $username = $token->getUsername(); 
    if(empty($username)) 
    { 
     throw new AuthenticationServiceException('No username given.'); 
    } 

    try 
    { 
     $user = $this->retrieveUser($username, $token); 

     if(!$user instanceof UserInterface) 
     { 
     throw new AuthenticationServiceException('retrieveUser() must return a UserInterface.'); 
     } 

     $authenticatedToken = new UsernamePasswordToken($user, null, 'api', $user->getRoles()); 
     $authenticatedToken->setAttributes($token->getAttributes()); 

     return $authenticatedToken; 
    } 
    catch(\Exception $exception) 
    { 
     throw $exception; 
    } 
    } 

    /** 
    * @param TokenInterface $token 
    * @return bool 
    */ 
    public function supports(TokenInterface $token) 
    { 
    return true; 
    } 
} 

要使用这两个对象我使用了一个yml文件来配置它们:

<container xmlns="http://symfony.com/schema/dic/services" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"> 

    <services> 
    <service id="pmsipilot.api.security.authentication.factory" class="Pmsipilot\UserBundle\DependencyInjection\Security\Factory\ApiFactory" public="false"> 
     <tag name="security.listener.factory" /> 
    </service> 
    </services> 
</container> 

现在的身份验证提供者的代码:

<?php 

namespace Pmsipilot\UserBundle\Security\Authentication\Provider; 

use Symfony\Component\Security\Core\Authentication\Provider\AuthenticationProviderInterface; 
use Symfony\Component\Security\Core\Encoder\EncoderFactoryInterface; 
use Symfony\Component\Security\Core\User\UserProviderInterface; 
use Symfony\Component\Security\Core\User\UserCheckerInterface; 
use Symfony\Component\Security\Core\User\UserInterface; 
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException; 
use Symfony\Component\Security\Core\Exception\AuthenticationServiceException; 
use Symfony\Component\Security\Core\Exception\BadCredentialsException; 
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; 
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; 

class ApiProvider implements AuthenticationProviderInterface 
{ 
    private $userProvider; 

    /** 
    * Constructor. 
    * 
    * @param \Symfony\Component\Security\Core\User\UserProviderInterface $userProvider An UserProviderInterface instance 
    */ 
    public function __construct(UserProviderInterface $userProvider) 
    { 
    $this->userProvider = $userProvider; 
    } 

    /** 
    * @param string $username 
    * @param \Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken $token 
    * @return mixed 
    * @throws \Symfony\Component\Security\Core\Exception\AuthenticationServiceException|\Symfony\Component\Security\Core\Exception\UsernameNotFoundException 
    */ 
    protected function retrieveUser($username, UsernamePasswordToken $token) 
    { 
    $user = $token->getUser(); 
    if($user instanceof UserInterface) 
    { 
     return $user; 
    } 

    try 
    { 
     $user = $this->userProvider->loadUserByApiKey($username, $token->getCredentials()); 

     if(!$user instanceof UserInterface) 
     { 
     throw new AuthenticationServiceException('The user provider must return a UserInterface object.'); 
     } 

     return $user; 
    } 
    catch (\Exception $exception) 
    { 
     throw new AuthenticationServiceException($exception->getMessage(), $token, 0, $exception); 
    } 
    } 

    /** 
    * @param TokenInterface $token 
    * @return null|\Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken 
    * @throws \Symfony\Component\Security\Core\Exception\AuthenticationServiceException|\Symfony\Component\Security\Core\Exception\BadCredentialsException|\Symfony\Component\Security\Core\Exception\UsernameNotFoundException 
    */ 
    function authenticate(TokenInterface $token) 
    { 
    $username = $token->getUsername(); 
    if(empty($username)) 
    { 
     throw new AuthenticationServiceException('No username given.'); 
    } 

    try 
    { 
     $user = $this->retrieveUser($username, $token); 

     if(!$user instanceof UserInterface) 
     { 
     throw new AuthenticationServiceException('retrieveUser() must return a UserInterface.'); 
     } 

     $authenticatedToken = new UsernamePasswordToken($user, null, 'api', $user->getRoles()); 
     $authenticatedToken->setAttributes($token->getAttributes()); 

     return $authenticatedToken; 
    } 
    catch(\Exception $exception) 
    { 
     throw $exception; 
    } 
    } 

    /** 
    * @param TokenInterface $token 
    * @return bool 
    */ 
    public function supports(TokenInterface $token) 
    { 
    return true; 
    } 
} 

仅供参考我的用户提供:

<?php 

namespace Pmsipilot\UserBundle\Security\Provider; 

use Propel\PropelBundle\Security\User\ModelUserProvider; 
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException; 
use \Symfony\Component\Security\Core\Encoder\MessageDigestPasswordEncoder; 

class ApiProvider extends ModelUserProvider 
{ 
    /** 
    * Constructeur 
    */ 
    public function __construct() 
    { 
    parent::__construct('Pmsipilot\UserBundle\Model\User', 'Pmsipilot\UserBundle\Proxy\User', 'username'); 
    } 

    /** 
    * @param string $apikey 
    * @return mixed 
    * @throws \Symfony\Component\Security\Core\Exception\UsernameNotFoundException 
    */ 
    public function loadUserByApiKey($apikey) 
    { 
    $queryClass = $this->queryClass; 
    $query  = $queryClass::create(); 

    $user = $query 
     ->filterByApiKey($apikey) 
     ->findOne() 
    ; 

    if(null === $user) 
    { 
     throw new UsernameNotFoundException(sprintf('User with "%s" api key not found.', $apikey)); 
    } 
    $proxyClass = $this->proxyClass; 
    return new $proxyClass($user); 
    } 
} 

而对于配置部分我security.yml:

security: 
    factories: 
    PmsipilotFactory: "%kernel.root_dir%/../src/Pmsipilot/UserBundle/Resources/config/security_factories.xml" 

    providers: 
    interface_provider: 
     id: pmsipilot.security.user.provider 
    api_provider: 
     id: api.security.user.provider 

    encoders: 
    Pmsipilot\UserBundle\Proxy\User: sha512 

    firewalls: 
    assets: 
     pattern:    ^/(_(profiler|wdt)|css|images|js|favicon.ico)/ 
     security:    false 

    api: 
     provider:    api_provider 
     access_denied_url:  /unauthorizedApi 
     pattern:    ^/api 
     api:     true 
     http_basic:    true 
     stateless:    true 

    interface: 
     provider:    interface_provider 
     access_denied_url:  /unauthorized 
     pattern:    ^/ 
     anonymous:    ~ 
     form_login: 
     login_path:   /login 
     check_path:   /login_check 
     use_forward:   true 
     default_target_path:/
     logout:     ~ 

    access_control: 
    - { path: ^/api, roles: IS_AUTHENTICATED_ANONYMOUSLY } 
    - { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY } 
    - { path: ^/, roles: SUPER_ADMIN } 

哇这是很多代码,我希望它不是太无聊。

我在这里的问题是,我的自定义身份验证提供由两个防火墙API接口,而不是仅仅由API一个叫。 当然,他们不像我想要的那样行事。

我没有发现任何关于这样的问题。 我知道我犯了一个错误,否则它会工作,但我在哪里以及为什么我不知道。

我也发现this tutorial但它没有帮助更多。

当然,不要犹豫,建议我是否有其他解决方案使用另一个身份验证提供程序而不是默认提供程序。

+0

为什么需要API和接口的提供程序?您的ApiProvider没有任何验证码(例如比较用户传递的密钥) – 2012-02-01 11:37:37

+0

因为对于我的应用程序的api部分,我只希望用户使用api密钥进行身份验证,而不管他的密码。 如果API密钥与用户匹配,他会自动进行身份验证。 默认身份验证提供程序使用用户名检查密码,并且我想要完成此检查。 – Maxime 2012-02-02 11:22:42

+1

我找到了另一种方法来做我想做的事,但没有奏效。 我创建了一个“security.authentication.provider.dao.api”作为id的服务,并且在这个认证提供程序中,我放置了与上面相同的代码。 但是我遇到了同样的问题,即使使用接口防火墙也仍然使用它。 我只是想重载Symfony \ Component \ Security \ Core \ Authentication \ Provider \ DaoAuthenticationProvider以避免密码验证,它不应该那么复杂,不是吗? – Maxime 2012-02-03 09:18:49

回答

10

所以我会回答我自己的问题,因为我找到了我的问题的解决方案,我会告诉你我是如何解决它的。

我的例子中有一些错误,我理解他们在Symfony代码中搜索。

就像Factory类的getKey方法返回的键一样。我发现我创建的api对我来说不是对我的security.yml文件的其他参数,而是对http_basic文件的替代。 这就是为什么我在使用两个提供者而不是一个提供者时遇到一些麻烦,因为我得到了两个使用提供者的密钥(api和http_basic)。事实上,我认为这是造成这个问题的原因。

为了简单起见,我遵循Symfony教程,除了令牌类,但我用Symfony类的代码替换了新类的代码。 以一种方式,我重新创建了Symfony的http基本认证,使其可以重载。 在这里,我可以做我想做的事情,根据Symfony配置一种不同类型的http身份验证,但需要进行一些更改。

这个故事对我很有帮助,因为我知道我知道理解Symfony原则的最好方法是深入代码并照顾。

+1

如果是答案,请标记为答案 – 2013-02-08 15:35:51

0

我发现更简单的解决方案。在config.yml您可以指向您的自定义身份验证。提供者类,像这样:

security.authentication.provider.dao.class: App\Security\AuthenticationProvider\MyDaoAuthenticationProvider 

当然MyDaoAuthenticationProvider不得不延长的Symfony \分量\安全\核心\认证\供应商\ UserAuthenticationProvider

0

我临到你的问题,看来你做好你的代码。 也可能导致问题的东西是安全性文件中防火墙定义的订单

试着想象一下,如果在你的CustomListener之前有一些定义好的Listener(防火墙入口),并且它返回一些响应,它将打破处理程序循环。
最终它会导致您的CustomListener被注册,但处理方法永远不会被调用。

0

也许有点晚(实际上是5年后),但是你的工厂里有一个错字。 您写道: $providerId = 'security.authentification.provider.api.'.$id;

其中“认证”必须认证

相关问题