Symfony - 使用API​​令牌进行身份验证 - 请求令牌用户为空

Jim*_*mbo 7 php authentication session symfony

为了记录,我使用PHP 7.0.0,在Vagrant Box中使用PHPStorm.哦,和Symfony 3.

我正在关注API密钥身份验证文档.我的目标是:

  • 允许用户提供密钥作为GET apiKey参数来验证任何路由,显然除了开发人员分析器等
  • 允许开发人员$request->getUser()在控制器中写入以获取当前登录的用户

我的问题是,虽然我相信我已经按照文档的信,我仍然得到一个null用于$request->getUser()在控制器中.

注意:我已删除错误检查以保持代码简短

ApiKeyAuthenticator.php

处理从中获取API密钥的请求部分的事情.它可以是标题或任何东西,但我坚持apiKey从GET.

与文档的差异,除了我试图在这部分文档之后的会话中保持用户身份验证之外,几乎为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;
    }
}
Run Code Online (Sandbox Code Playgroud)

ApiKeyUserProvider.php

自定义用户提供程序从可以加载的任何地方加载用户对象 - 我坚持使用默认的数据库实现.

不同点:只有事实,我必须到存储库注入到构造函数建立数据库调用,该文档暗示,但没有显示,也返回$userrefreshUser().

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

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 --//
}
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

调试.这是有趣的是,在ApiKeyAuthenticator.php中,调用$user = $token->getUser();authenticateToken()总是显示的anon.用户,所以它显然不被存储在会话.

调试1 调试2

还要注意在身份验证器的底部我们实际上是如何PreAuthenticatedToken从数据库中找到的用户返回一个新的:

调试3 调试4

所以它清楚地找到了我并且正在返回它应该在这里的内容,但是控制器中的用户调用返回null.我究竟做错了什么?由于我的自定义用户或其他东西,是否无法序列进入会话?我尝试将所有用户属性设置为公共,因为在文档建议的某处,但没有区别.

Jim*_*mbo 3

所以事实证明,调用$request->getUser()控制器实际上并没有像我预期的那样返回当前经过身份验证的用户。恕我直言,这对于这个对象 API 来说是最有意义的。

如果您实际查看 的代码Request::getUser(),它看起来像这样:

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

这是 HTTP 基本身份验证!为了获取当前登录的用户,您需要每次都执行以下操作:

$this->get('security.token_storage')->getToken()->getUser();
Run Code Online (Sandbox Code Playgroud)

这确实给了我当前登录的用户。希望上面的问题展示了如何通过 API 令牌成功进行身份验证。

或者,不要致电,$this->get()因为它是服务定位器。将自己与控制器分离并注入令牌服务以从中获取令牌和用户。