2016-02-18 24 views
7

Dla przypomnienia, używam PHP 7.0.0, w Vagrant Box z PHPStorm. Och, i Symfony 3.Symfony - Autoryzacja z API Reklamowe - tokenu żądania użytkownik jest null

Podążam za dokumentacją API Key Authentication. Moim celem jest:

  • Aby umożliwić użytkownikowi dostarczenie klucza jako parametr GET apiKey uwierzytelnienia dla dowolny trasie, z wyjątkiem deweloper profiler itp oczywiście
  • Aby umożliwić deweloper napisać $request->getUser() w kontrolerze aby uzyskać aktualnie zalogowanego użytkownika

Moim problemem jest to, że chociaż wierzę, że już po dokumentacji pismo, ja wciąż dostaję null dla $request->getUser() w kontrolerze.

Uwaga: Usunąłem sprawdzanie błędów, aby zachować kod krótki

ApiKeyAuthenticator.php

Rzecz, która przetwarza część wniosku o chwycić klucz API z niego . Może to być nagłówek lub cokolwiek innego, ale trzymam się apiKey z GET.

Różnice w dokumentacji, prawie 0 oprócz tego staram się utrzymać użytkownika uwierzytelnionego w sesji po this part dokumentów.

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

Zwyczaj dostawcą użytkownik załadować obiekt użytkownika z dowolnego miejsca mogą być ładowane z - mam trzymać z domyślnym realizacji DB.

Różnice: tylko fakt, że muszę wstrzykiwać repozytorium do konstruktora do wykonywania połączeń do DB, jak docs wspomina się, ale nie pokazują, a także powrót $user w 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

To jest mój obiekt niestandardowy użytkownika.

Jedyną różnicą, którą mam tutaj jest to, że zawiera adnotacje doktryny (usunięte dla twojego zdrowia psychicznego) i niestandardowe pole dla tokena. Usunęłam też \Serializable, ponieważ nic nie robiłam i najwyraźniej Symfony potrzebuje tylko wartości $id, aby odtworzyć użytkownika, który sam może zrobić.

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

Oczywiście muszę być w stanie wstrzyknąć repozytorium do dostawcy.

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 

debugowanie. Warto zauważyć, że w ApiKeyAuthenticator.php wywołanie $user = $token->getUser(); w authenticateToken() zawsze pokazuje użytkownika anon., więc wyraźnie nie jest on przechowywany w sesji.

Debug 1 Debug 2

również pamiętać, jak na dnie uwierzytelnienia mamy rzeczywiście powrócić nową PreAuthenticatedToken z użytkownikiem znaleziony w bazie danych:

Debug 3 Debug 4

Więc to wyraźnie stwierdzono mnie i zwraca to, co ma tutaj, ale wywołanie użytkownika w kontrolerze zwraca null. Co ja robię źle? Czy to nie serializowanie w sesji z powodu mojego niestandardowego użytkownika lub czegoś podobnego? Próbowałem ustawić wszystkie właściwości użytkownika jako publiczne w jakimś miejscu w sugerowanej dokumentacji, ale to nie miało znaczenia.

+0

można spróbować to sprawdzić, należy dodać do zapory: 'access_control: - {ścieżkę:^/, role: IS_AUTHENTICATED_ANONYMOUSLY}' – COil

+0

Dodałem to jako klucz pod 'security' w' security.yml '. Bez zmiany. – Jimbo

+0

Czy możesz spróbować, ustawiając wszystkie swoje właściwości w klasie ApiKeyUser jako chronione? – hasumedic

Odpowiedz

2

Okazuje się, że wywołanie $request->getUser() w kontrolerze nie zwraca obecnie uwierzytelnionego użytkownika, tak jak oczekiwałbym. To byłoby najbardziej sensowne dla tego obiektu API imho.

Jeśli rzeczywiście spojrzeć na kod dla Request::getUser(), wygląda to tak:

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

To dla HTTP podstawowe Auth! W celu uzyskania aktualnie zalogowanego użytkownika, trzeba to robić za każdym razem:

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

ta ma w istocie dać mi aktualnie zalogowanego użytkownika. Mam nadzieję, że powyższe pytanie pokazuje, jak pomyślnie uwierzytelnić token API.

Ewentualnie nie wywołuj numeru $this->get(), ponieważ jest to lokalizator usług. Odłącz się od kontrolera i zamiast tego wstrzyknij usługę tokena, aby pobrać od niego token i użytkownika.

0

Aby uzyskać aktualnie zalogowanego użytkownika wewnątrz sterownika wystarczy zadzwonić:

$this->getUser(); 

To będzie odnosić się do sposobu w Symfony za ControllerTrait, które zasadniczo otacza kod podany w odpowiedzi Jimbo jest.

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

Chociaż jest to pomocne dla niektórych (i dzięki za odpowiedź Marco!), Jest kilka rzeczy, o których trzeba wspomnieć. '$ this-> container' jest lokalizatorem usług (anti-pattern) i zamiast tego powinniśmy używać Dependency Injection. Również cechy ogólne i sprzężenie z Symfony przez rozszerzenie klasy kontrolerów Symfony są również uważane przez wielu za słabe praktyki (sprzężenie). Mam nadzieję, że pomaga! – Jimbo