2016-02-18 25 views
7

Để lưu nội dung, tôi đang sử dụng PHP 7.0.0, trong hộp Vagrant, với PHPStorm. Oh, và Symfony 3.Symfony - Xác thực bằng Mã thông báo API - Người dùng mã thông báo yêu cầu là null

Tôi đang theo dõi tài liệu API Key Authentication. Mục tiêu của tôi là:

  • Để cho phép người sử dụng để cung cấp một chìa khóa như một tham số GET apiKey để xác thực cho bất kỳ tuyến đường, ngoại trừ vv phát triển hồ sơ rõ ràng
  • Để cho phép các nhà phát triển để viết $request->getUser() trong một bộ điều khiển để người dùng hiện đã đăng nhập

Vấn đề của tôi là mặc dù tôi tin rằng tôi vẫn đang theo dõi tài liệu, tôi vẫn nhận được null cho $request->getUser() trong bộ điều khiển.

Lưu ý: Tôi đã gỡ bỏ kiểm tra lỗi để giữ mã ngắn

ApiKeyAuthenticator.php

Điều mà xử lý phần của yêu cầu để lấy chìa khóa API từ nó . Nó có thể là một tiêu đề hoặc bất cứ điều gì, nhưng tôi đang gắn bó với apiKey từ GET.

Sự khác biệt về tài liệu, ngoài ra tôi còn cố gắng giữ cho người dùng được xác thực trong phiên sau this part tài liệu.

class ApiKeyAuthenticator implements SimplePreAuthenticatorInterface 
{ 
    public function createToken(Request $request, $providerKey) 
    { 
     $apiKey = $request->query->get('apiKey'); 

     return new PreAuthenticatedToken(
      'anon.', 
      $apiKey, 
      $providerKey 
     ); 
    } 

    public function authenticateToken(TokenInterface $token, UserProviderInterface $userProvider, $providerKey) 
    { 
     $apiKey = $token->getCredentials(); 
     $username = $userProvider->getUsernameForApiKey($apiKey); 

     // The part where we try and keep the user in the session! 
     $user = $token->getUser(); 
     if ($user instanceof ApiKeyUser) { 
      return new PreAuthenticatedToken(
       $user, 
       $apiKey, 
       $providerKey, 
       $user->getRoles() 
      ); 
     } 


     $user = $userProvider->loadUserByUsername($username); 

     return new PreAuthenticatedToken(
      $user, 
      $apiKey, 
      $providerKey, 
      $user->getRoles() 
     ); 
    } 

    public function supportsToken(TokenInterface $token, $providerKey) 
    { 
     return $token instanceof PreAuthenticatedToken && $token->getProviderKey() === $providerKey; 
    } 
} 

ApiKeyUserProvider.php nhà cung cấp sử dụng

Tục để tải một đối tượng người dùng từ bất cứ nơi nào nó có thể được nạp từ - Tôi gắn bó với việc thực hiện DB mặc định.

Sự khác biệt: chỉ có thực tế là tôi phải tiêm kho vào các nhà xây dựng để thực hiện cuộc gọi đến DB, như các tài liệu ám chỉ đến nhưng không hiển thị, và cũng có thể trở $user trong refreshUser().

class ApiKeyUserProvider implements UserProviderInterface 
{ 
    protected $repo; 

    // I'm injecting the Repo here (docs don't help with this) 
    public function __construct(UserRepository $repo) 
    { 
     $this->repo = $repo; 
    } 

    public function getUsernameForApiKey($apiKey) 
    { 
     $data = $this->repo->findUsernameByApiKey($apiKey); 

     $username = (!is_null($data)) ? $data->getUsername() : null; 

     return $username; 
    } 

    public function loadUserByUsername($username) 
    { 
     return $this->repo->findOneBy(['username' => $username]); 
    } 

    public function refreshUser(UserInterface $user) 
    { 
     // docs state to return here if we don't want stateless 
     return $user; 
    } 

    public function supportsClass($class) 
    { 
     return 'Symfony\Component\Security\Core\User\User' === $class; 
    } 
} 

ApiKeyUser.php

Đây là đối tượng của tôi người dùng tùy chỉnh.

Sự khác biệt duy nhất tôi có ở đây là nó chứa chú thích học thuyết (loại bỏ cho sự tỉnh táo của bạn) và trường tùy chỉnh cho mã thông báo. Ngoài ra, tôi đã xóa \Serializable vì nó dường như không hoạt động và Symfony chỉ cần giá trị $id để tạo lại người dùng mà chính nó có thể tự thực hiện.

class ApiKeyUser implements UserInterface 
{ 
    private $id; 
    private $username; 
    private $password; 
    private $email; 
    private $salt; 
    private $apiKey; 
    private $isActive; 

    public function __construct($username, $password, $salt, $apiKey, $isActive = true) 
    { 
     $this->username = $username; 
     $this->password = $password; 
     $this->salt = $salt; 
     $this->apiKey = $apiKey; 
     $this->isActive = $isActive; 
    } 

    //-- SNIP getters --// 
} 

security.yml

# Here is my custom user provider class from above 
providers: 
    api_key_user_provider: 
     id: api_key_user_provider 

firewalls: 
    # Authentication disabled for dev (default settings) 
    dev: 
     pattern: ^/(_(profiler|wdt)|css|images|js)/ 
     security: false 
    # My new settings, with stateless set to false 
    secured_area: 
     pattern: ^/ 
     stateless: false 
     simple_preauth: 
      authenticator: apikey_authenticator 
     provider: 
      api_key_user_provider 

services.yml

Rõ ràng tôi cần để có thể tiêm kho thành nhà cung cấp.

api_key_user_repository: 
    class: Doctrine\ORM\EntityRepository 
    factory: ["@doctrine.orm.entity_manager", getRepository] 
    arguments: [AppBundle\Security\ApiKeyUser] 

api_key_user_provider: 
    class: AppBundle\Security\ApiKeyUserProvider 
    factory_service: doctrine.orm.default_entity_manager 
    factory_method: getRepository 
    arguments: ["@api_key_user_repository"] 

apikey_authenticator: 
    class: AppBundle\Security\ApiKeyAuthenticator 
    public: false 

Gỡ lỗi. Thật thú vị khi lưu ý rằng, trong ApiKeyAuthenticator.php, cuộc gọi tới $user = $token->getUser(); trong authenticateToken() luôn hiển thị người dùng anon., do đó, rõ ràng là không được lưu trữ trong phiên.

Debug 1 Debug 2

Cũng lưu ý như thế nào ở dưới cùng của xác thực chúng tôi thực sự trở lại mới PreAuthenticatedToken với một người dùng tìm thấy từ cơ sở dữ liệu:

Debug 3 Debug 4

Vì vậy, nó được tìm thấy rõ ràng tôi và trả lại những gì nó được cho là ở đây, nhưng cuộc gọi của người dùng trong bộ điều khiển trả về null. Tôi đang làm gì sai? Nó là một thất bại trong việc nối tiếp vào phiên vì người dùng tùy chỉnh của tôi hay gì đó? Tôi đã thử đặt tất cả các thuộc tính người dùng thành công khai ở đâu đó trong tài liệu được đề xuất nhưng điều đó không có sự khác biệt.

+0

Bạn có thể thử để kiểm tra điều này, thêm vào tường lửa của bạn: 'access_control: - {path:^/, vai trò: IS_AUTHENTICATED_ANONYMOUSLY}' – COil

+0

tôi thêm này như một chìa khóa dưới 'security' trong' security.yml '. Không thay đổi. – Jimbo

+0

Bạn có thể thử bằng cách đặt tất cả các thuộc tính của bạn trong lớp ApiKeyUser như được bảo vệ không? – hasumedic

Trả lời

2

Vì vậy, hóa ra là gọi $request->getUser() trong bộ điều khiển không thực sự trả lại người dùng hiện được xác thực như tôi đã mong đợi. Điều này sẽ có ý nghĩa nhất đối với API đối tượng này.

Nếu bạn thực sự nhìn vào mã cho Request::getUser(), nó trông như thế này:

/** 
* Returns the user. 
* 
* @return string|null 
*/ 
public function getUser() 
{ 
    return $this->headers->get('PHP_AUTH_USER'); 
} 

Đó là cho HTTP Basic Auth! Để có được hiện đang đăng nhập sử dụng, bạn cần phải làm điều này mỗi lần duy nhất:

$this->get('security.token_storage')->getToken()->getUser(); 

này, quả thật vậy, cho tôi hiện đang đăng nhập người dùng. Hy vọng rằng câu hỏi trên cho thấy cách xác thực thành công bằng mã thông báo API.

Cách khác, không gọi $this->get() vì đó là định vị dịch vụ. Decouple mình từ bộ điều khiển và tiêm dịch vụ mã thông báo thay vì để có được mã thông báo và người dùng từ nó.

0

Để có được hiện đang đăng nhập tài khoản bên trong điều khiển của bạn chỉ cần gọi:

$this->getUser(); 

này sẽ đề cập đến một phương pháp trong ControllerTrait Symfony, mà về cơ bản kết thúc tốt đẹp mã được cung cấp trong câu trả lời của Jimbo.

protected function getUser() 
{ 
    if (!$this->container->has('security.token_storage')) { 
     throw new \LogicException('The SecurityBundle is not registered in your application. Try running "composer require symfony/security-bundle".'); 
    } 

    if (null === $token = $this->container->get('security.token_storage')->getToken()) { 
     return; 
    } 

    if (!is_object($user = $token->getUser())) { 
     // e.g. anonymous authentication 
     return; 
    } 

    return $user; 
} 
+0

Mặc dù điều này là hữu ích cho một số (và cảm ơn câu trả lời của bạn Marco!), Có một vài điều cần được đề cập. '$ this-> container' là một trình định vị dịch vụ (anti-pattern) và chúng ta nên sử dụng Dependency Injection để thay thế. Ngoài ra, các đặc điểm nói chung và ghép nối với Symfony bằng cách mở rộng lớp điều khiển của Symfony cũng được coi là thực hành kém bởi nhiều (khớp nối). Hy vọng rằng sẽ giúp! – Jimbo

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