2012-01-30 27 views
10

Tôi đang làm việc trên ứng dụng Symfony2 có API có sẵn cho các ứng dụng khác. Tôi muốn bảo mật quyền truy cập vào API. Đối với phần này tôi không có vấn đề gì.Sử dụng nhà cung cấp xác thực tùy chỉnh trong Symfony2

Nhưng tôi phải làm cho kết nối này khả dụng không phải với cặp đăng nhập/mật khẩu thông thường mà chỉ với một khóa API.

Vì vậy, tôi đã đi đến trang web chính thức và sách dạy nấu ăn tuyệt vời của nó cho creating a custom authentication provider, chỉ là những gì tôi cần tôi tự nói với bản thân mình.

Ví dụ không phải là những gì tôi cần nhưng tôi quyết định điều chỉnh nó theo nhu cầu của mình.

Thật không may là tôi đã không thành công.

Tôi sẽ cung cấp cho bạn mã của tôi và sau này tôi sẽ giải thích vấn đề của mình.

Đây là Nhà máy của tôi để tạo các nhà cung cấp chứng thực và người nghe:

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

Tiếp đang nghe tôi:

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

đang cung cấp dịch vụ chứng thực của tôi:

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

Để sử dụng hai đối tượng này tôi đã sử dụng tệp yml để định cấu hình chúng:

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

Bây giờ mã nhà cung cấp chứng thực:

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

Just FYI cung cấp người dùng của tôi:

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

Và đối với phần cấu hình security.yml tôi:

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 } 

Wow nó rất nhiều mã, tôi hy vọng nó không quá nhàm chán.

Vấn đề của tôi ở đây là cung cấp dịch vụ chứng thực tùy chỉnh của tôi được gọi bằng hai bức tường lửa apigiao diện thay vì chỉ bởi api một. Và tất nhiên họ không cư xử như tôi muốn.

Tôi không tìm thấy bất kỳ điều gì về vấn đề như vậy. Tôi biết tôi đã phạm sai lầm, nếu không nó sẽ hoạt động, nhưng ở đâu và tại sao tôi không biết.

Tôi cũng tìm thấy this tutorial nhưng không giúp được gì nhiều.

Tất nhiên, đừng ngại đề xuất tôi nếu có giải pháp khác để sử dụng nhà cung cấp xác thực khác với nhà cung cấp mặc định.

+0

Tại sao bạn cần các nhà cung cấp api và giao diện? Bạn ApiProvider không có bất kỳ mã xác thực nào (ví dụ: so sánh khóa được người dùng thông qua) –

+0

Vì phần api của ứng dụng của tôi, tôi chỉ muốn người dùng xác thực bằng khóa api, bất kể mật khẩu của mình. Nếu khóa api khớp với người dùng, người đó sẽ tự động được xác thực. Nhà cung cấp xác thực mặc định kiểm tra mật khẩu với tên người dùng và tôi muốn kiểm tra này được thực hiện. – Maxime

+1

Tôi tìm thấy một cách khác để làm những gì tôi muốn, nhưng nó không hoạt động. Tôi đã tạo một dịch vụ với "security.authentication.provider.dao.api" làm id và bên trong nhà cung cấp xác thực này, tôi đặt cùng mã như trên. Nhưng tôi gặp vấn đề tương tự, nó vẫn được sử dụng ngay cả với tường lửa giao diện. Tôi chỉ muốn quá tải Symfony \ Component \ Security \ Core \ Authentication \ Provider \ DaoAuthenticationProvider để tránh việc xác minh mật khẩu, nó không nên quá phức tạp, phải không? – Maxime

Trả lời

10

Vì vậy, tôi sẽ trả lời câu hỏi của riêng mình vì tôi đã tìm ra giải pháp cho vấn đề của mình và tôi sẽ cho bạn biết cách tôi giải quyết nó.

Có một số sai lầm trong ví dụ của tôi và tôi hiểu họ đang tìm kiếm trong mã Symfony.

Giống như khóa được trả về bằng phương thức getKey của lớp Nhà máy. Tôi thấy rằng api mà tôi đã tạo ra không phải là một tham số khác đối với tệp security.yml của tôi, mà là một thay thế cho tệp http_basic. Đó là lý do tại sao tôi gặp một số rắc rối khi sử dụng hai nhà cung cấp thay vì chỉ một, bởi vì tôi có hai khóa (api và http_basic) mà cả hai đều sử dụng một nhà cung cấp. Trong thực tế, tôi nghĩ rằng đó là lý do cho vấn đề đó.

Để đơn giản, tôi làm theo hướng dẫn của Symfony, ngoại trừ lớp mã thông báo nhưng tôi đã thay thế mã của các lớp mới bằng mã của các lớp Symfony. Theo cách tôi tạo lại xác thực cơ bản http của Symfony để làm cho nó có thể bị quá tải. Và ở đây, tôi có thể làm những gì tôi muốn, cấu hình một kiểu xác thực http khác dựa trên Symfony nhưng với một số thay đổi.

Câu chuyện này đã giúp tôi bởi vì tôi biết rằng cách tốt nhất để hiểu các nguyên tắc của Symfony là đi sâu hơn vào mã và chăm sóc.

+1

đánh dấu là câu trả lời nếu đó là câu trả lời –

0

Tôi đã tìm thấy giải pháp đơn giản hơn nhiều. Trong config.yml bạn có thể trỏ tới auth tùy chỉnh của mình. nhà cung cấp lớp, như thế này:

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

Tất nhiên MyDaoAuthenticationProvider phải mở rộng Symfony \ Component \ Security \ Lõi \ Authentication \ Provider \ UserAuthenticationProvider

0

tôi đã đến khi vấn đề của bạn, và có vẻ như bạn đã làm mã của bạn tốt. Điều cũng có thể gây ra sự cố là thứ tự các định nghĩa tường lửa trong security.xml.

Hãy thử tưởng tượng, nếu có một số Trình nghe được xác định (mục nhập tường lửa) trước CustomListener của bạn và nó trả về một số Phản hồi, nó sẽ ngắt vòng xử lý.
Cuối cùng nó sẽ khiến cho CustomListener của bạn được đăng ký, nhưng phương thức xử lý sẽ không bao giờ được gọi.

0

Có thể muộn một chút (5 năm sau đó), nhưng bạn có lỗi đánh máy trong Nhà máy của mình. Bạn đã viết: $providerId = 'security.authentification.provider.api.'.$id;

đâu "xác thực" phải được xác thực

Các vấn đề liên quan