簡體   English   中英

Symfony - 使用API​​令牌進行身份驗證 - 請求令牌用戶為空

[英]Symfony - Authentication with an API Token - Request token user is null

為了記錄,我使用PHP 7.0.0,在Vagrant Box中使用PHPStorm。 哦,和Symfony 3。

我正在關注API密鑰身份驗證文檔。 我的目標是:

  • 允許用戶提供密鑰作為GET apiKey參數來驗證任何路由,顯然除了開發人員分析器等
  • 允許開發人員在控制器中編寫$request->getUser()以獲取當前登錄的用戶

我的問題是,雖然我相信我已經遵循了文檔中的文檔,但我仍然在控制器中獲取$request->getUser()null

注意:我已刪除錯誤檢查以保持代碼簡短

ApiKeyAuthenticator.php

處理從中獲取API密鑰的請求部分的事情。 它可以是標題或任何東西,但我堅持使用GET中的apiKey

與文檔的差異,除了我試圖在這部分文檔之后的會話中保持用戶身份驗證之外,幾乎為0。

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

自定義用戶提供程序從可以加載的任何地方加載用戶對象 - 我堅持使用默認的數據庫實現。

差異:只有這樣一個事實:我必須將存儲庫注入構造函數以調用數據庫,因為文檔提示但不顯示,並且還在refreshUser()返回$user

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

這是我的自定義用戶對象。

我在這里唯一的區別是它包含學說注釋(為了你的理智而刪除)和令牌的自定義字段。 此外,我刪除了\\Serializable因為它似乎沒有做任何事情,顯然Symfony只需要$id值來重新創建它可以自己做的用戶。

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

顯然,我需要能夠將存儲庫注入提供程序。

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

調試。 有趣的是,在ApiKeyAuthenticator.php ,調用$user = $token->getUser(); authenticateToken()始終顯示一個anon. 用戶,所以它顯然沒有存儲在會話中。

調試1 調試2

另請注意,在身份驗證器的底部,我們實際上是如何從數據庫中找到的用戶返回一個新的PreAuthenticatedToken

調試3 調試4

所以它清楚地找到了我並返回它應該在這里的內容,但是控制器中的用戶調用返回null 我究竟做錯了什么? 由於我的自定義用戶或其他東西,是否無法序列進入會話? 我嘗試將所有用戶屬性設置為公共,因為在文檔建議的某處,但沒有區別。

所以事實證明,在控制器調用$request->getUser() 實際上並沒有像我期望的那樣返回當前經過身份驗證的用戶 這對於這個對象API imho來說是最有意義的。

如果您實際查看Request::getUser()的代碼,它看起來像這樣:

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

那是HTTP Basic Auth! 要獲取當前登錄的用戶,您需要每次都執行此操作:

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

這確實給了我當前登錄的用戶。 希望上面的問題顯示如何通過API令牌成功進行身份驗證。

或者,不要調用$this->get()因為它是服務定位器。 將自己與控制器分離並注入令牌服務,以便從中獲取令牌和用戶。

要在Controller中獲取當前登錄的用戶,只需致電:

$this->getUser();

這將引用Symfony的ControllerTrait中的一個方法,該方法基本上包含了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;
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM