2012-01-30 15 views
10

में कस्टम प्रमाणीकरण प्रदाता का उपयोग करके मैं अन्य अनुप्रयोगों के लिए उपलब्ध एक एपीआई के साथ एक सिम्फनी 2 एप्लिकेशन पर काम कर रहा हूं। मैं एपीआई तक पहुंच सुरक्षित करना चाहता हूं। इस भाग के लिए मुझे कोई समस्या नहीं है।Symfony2

लेकिन मुझे यह कनेक्शन सामान्य लॉगिन/पासवर्ड जोड़े के साथ उपलब्ध नहीं है बल्कि केवल एक एपीआई कुंजी के साथ बनाना है।

तो मैं 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; 
    } 
} 

बस FYI मेरे उपयोगकर्ता प्रदाता:

<?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 } 

वाह यह बहुत कोड है, मुझे उम्मीद है कि यह बहुत उबाऊ नहीं है।

मेरे यहाँ समस्या यह है कि मेरे कस्टम प्रमाणीकरण प्रदाता दो फायरवॉल एपीआई और इंटरफ़ेस के बजाय सिर्फ एपीआई एक के बाद से कहा जाता है। और निश्चित रूप से वे व्यवहार नहीं करते जैसा मैं चाहता था।

मुझे इस तरह के किसी मुद्दे के बारे में कुछ भी नहीं मिला। मुझे पता है कि मैंने गलती की है, अन्यथा यह काम करेगा, लेकिन मुझे कहां और क्यों नहीं पता।

मुझे this tutorial भी मिला लेकिन इससे अधिक मदद नहीं मिली।

बेशक, मुझे यह सुझाव देने में संकोच नहीं करें कि डिफ़ॉल्ट प्रमाणीकरण प्रदाता का उपयोग करने के लिए कोई अन्य समाधान है या नहीं।

+0

आप एपीआई और इंटरफेस प्रदाताओं की जरूरत क्यों होना करने के लिए? आप ApiProvider के पास कोई प्रमाणीकरण कोड नहीं है (उदाहरण के लिए उपयोगकर्ता द्वारा पारित कुंजी की तुलना करना) –

+0

क्योंकि मेरे आवेदन के एपीआई भाग के लिए मैं चाहता हूं कि उपयोगकर्ता एपीआई कुंजी के साथ प्रमाणीकृत करे, चाहे उसका पासवर्ड चाहे। यदि एपीआई कुंजी किसी उपयोगकर्ता से मेल खाती है तो वह स्वचालित रूप से प्रमाणित होता है। डिफ़ॉल्ट प्रमाणीकरण प्रदाता उपयोगकर्ता नाम के साथ पासवर्ड की जांच करता है, और मैं यह जांच करना चाहता हूं। – Maxime

+1

मुझे जो कुछ चाहिए वो करने का एक और तरीका मिला, लेकिन यह काम नहीं किया। मैंने आईडी के रूप में "security.authentication.provider.dao.api" के साथ एक सेवा बनाई और इस प्रमाणीकरण प्रदाता के अंदर मैंने उपरोक्त कोड को रखा। लेकिन मुझे एक ही समस्या मिली, यह अभी भी इंटरफ़ेस फ़ायरवॉल के साथ भी उपयोग किया जाता है। मैं पासवर्ड सत्यापन से बचने के लिए सिम्फनी \ घटक \ सुरक्षा \ कोर \ प्रमाणीकरण \ प्रदाता \ DaoAuthenticationProvider को अधिभारित करना चाहता हूं, यह इतना जटिल नहीं होना चाहिए, है ना? – Maxime

उत्तर

10

तो मैं अपने स्वयं के प्रश्न का उत्तर दूंगा क्योंकि मुझे मेरी समस्या का समाधान मिला और मैं आपको बताउंगा कि मैंने इसे कैसे हल किया।

मेरे उदाहरण में कुछ गलती हुई और मैंने उन्हें सिम्फनी कोड में खोजना समझा।

फ़ैक्टरी वर्ग की getKey विधि द्वारा लौटाई गई कुंजी की तरह। मैंने पाया कि मेरे द्वारा बनाई गई एपीआई मेरे लिए मेरी सुरक्षा.आईएमएल फ़ाइल के लिए एक अन्य पैरामीटर नहीं था, लेकिन http_basic एक के प्रतिस्थापन था। यही कारण है कि मुझे केवल एक के बजाय दो प्रदाताओं का उपयोग करने में कुछ परेशानी हो रही है, क्योंकि मुझे दो कुंजी (एपीआई और http_basic) मिली हैं जो दोनों प्रदाता का उपयोग करते हैं। वास्तव में मुझे लगता है कि यह उस समस्या का कारण है।

इसे आसान बनाने के लिए मैं टोकन क्लास को छोड़कर सिम्फनी ट्यूटोरियल का पालन करता हूं लेकिन मैंने सिम्फनी कक्षाओं के कोड द्वारा नए वर्गों के कोड को प्रतिस्थापित किया। एक तरह से मैंने सिम्फनी के http मूल प्रमाणीकरण को फिर से लोड करने के लिए संभव बनाने के लिए बनाया। और यहां मैं हूं, मैं जो चाहता हूं वह कर सकता हूं, सिम्फनी पर आधारित एक अलग प्रकार के http प्रमाणीकरण को कॉन्फ़िगर करें लेकिन कई बदलावों के साथ।

इस कहानी ने मेरी मदद की क्योंकि मुझे पता है कि मुझे पता है कि सिम्फनी सिद्धांतों को समझने का सबसे अच्छा तरीका कोड में गहराई से जाना और देखना है।

+1

उत्तर के रूप में चिह्नित करें यदि यह उत्तर है तो कृपया –

0

मुझे बहुत आसान समाधान मिला है। config.yml में आप अपने कस्टम ऑथ को इंगित कर सकते हैं। प्रदाता वर्ग, इस तरह:

security.authentication.provider.dao.class: App\Security\AuthenticationProvider\MyDaoAuthenticationProvider 
बेशक

MyDaoAuthenticationProvider Symfony \ घटक \ सुरक्षा \ कोर \ प्रमाणीकरण \ प्रदाता \ UserAuthenticationProvider का विस्तार करने के

0

मैं आपकी समस्या पर आ गए हैं, और यह है कि आप लगता है आपका कोड अच्छी तरह से किया। ऐसी चीज जो समस्याएं पैदा कर सकती है सुरक्षा 0xml में फ़ायरवॉल परिभाषाओं के आदेश है।

कल्पना करने का प्रयास करें, अगर आपके कस्टमलिस्टर से पहले कुछ परिभाषित श्रोता (फ़ायरवॉल-एंट्री) है और यह कुछ प्रतिक्रिया देता है, तो यह हैंडलर लूप को तोड़ देगा।
आखिरकार यह कारण होगा कि आपका कस्टमलिस्ट पंजीकृत है, लेकिन हैंडल विधि कभी नहीं बुलाई जाएगी।

0

शायद थोड़ा देर हो चुकी है (वास्तव में 5 साल बाद), लेकिन आपके कारखाने में एक टाइपो है। आप ने लिखा है: $providerId = 'security.authentification.provider.api.'.$id;

कहाँ "authentification" है प्रमाणीकरण