【问题标题】:Symfony - Authentication with an API Token - Request token user is nullSymfony - 使用 API 令牌进行身份验证 - 请求令牌用户为空
【发布时间】:2016-05-30 13:33:33
【问题描述】:

作为记录,我在 Vagrant Box 中使用 PHP 7.0.0 和 PHPStorm。哦,还有 Symfony 3。

我正在关注API Key Authentication 文档。我的目标是:

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

我的问题是,尽管我相信我已经完全按照文档进行操作,但我仍然在控制器中为 $request->getUser() 获得了一个 null

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

ApiKeyAuthenticator.php

处理部分请求以从中获取 API 密钥的事物。它可以是标题或任何内容,但我坚持使用来自 GET 的 apiKey

与文档的差异,除了我试图让用户在文档中的this part 之后的会话中保持身份验证之外,几乎为 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

自定义用户提供程序,用于从可以加载用户对象的任何位置加载用户对象 - 我坚持使用默认的 DB 实现。

差异:只有我必须将存储库注入构造函数才能调用数据库这一事实,正如文档所暗示但未显示的那样,并且还在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 中,对authenticateToken() 中的$user = $token->getUser(); 的调用总是显示anon. 用户,因此它显然没有存储在会话中。

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

所以它很明显找到了我并且正在返回它应该在这里,但是控制器中的用户调用返回null。我究竟做错了什么?由于我的自定义用户或其他原因,无法序列化到会话中吗?我尝试按照文档建议的某处将所有用户属性设置为公开,但这没有任何区别。

【问题讨论】:

  • 你可以试试这个,添加到你的防火墙:access_control: - { path: ^/, roles: IS_AUTHENTICATED_ANONYMOUSLY }
  • 我在security.ymlsecurity 下添加了这个键。没有变化。
  • 您可以尝试将 ApiKeyUser 类中的所有属性设置为受保护吗?
  • @hasumedic 我已经尝试了publicprotected 访问修饰符,用户仍然是null
  • 嗯,那你能不能试着改变你的 ApiKeyUserProvider::supportsClass 的实现为'return ApiKeyUser::class=== $class;'?

标签: php symfony session authentication


【解决方案1】:

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

如果你真的看Request::getUser()的代码,它看起来像这样:

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

这是用于 HTTP 基本身份验证的!为了获取当前登录的用户,您需要每次都这样做:

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

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

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

【讨论】:

    【解决方案2】:

    要在控制器中获取当前登录的用户,只需调用:

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

    【讨论】:

    • 虽然这对某些人有帮助(感谢您的回答,Marco!),但有一些事情需要提及。 $this->container 是一个服务定位器(反模式),我们应该使用依赖注入来代替。此外,通过扩展 Symfony 的控制器类,一般的特征和耦合到 Symfony 也被许多人认为是不好的做法(耦合)。希望有帮助!
    猜你喜欢
    • 2023-03-20
    • 1970-01-01
    • 1970-01-01
    • 2019-08-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-11-24
    • 2018-07-22
    相关资源
    最近更新 更多