如何在服务中获取当前登录的用户

48

在Symfony 2.8/3.0中,使用新的安全组件时,如何在服务中获取当前已登录的User对象(即FOSUser),而不必注入整个容器?

这是否有可能以一种非hacky的方式实现?

注:请不要考虑“将其作为参数传递给服务函数”的方法,因为它过于明显和不规范。

8个回答

70

1
我已经做到了这点,但是当我请求令牌时,我得到了 null 。我错过了什么需要设置的东西吗? - Mathias Bader
始终检查用户是否已登录 首先检查用户是否已通过身份验证非常重要。如果没有,$user 将为 null 或字符串 anon.。等等,什么?是的,这是一个怪癖。如果您没有登录,则用户在技术上是字符串 anon.,但 getUser() 控制器快捷方式将其转换为 null 以方便使用。 https://symfony.com/doc/3.4/security.html#always-check-if-the-user-is-logged-in - Kim

66

1
适用于Symfony 4。 - Darragh Enright
2
我并不完全理解为什么推荐使用 Security 而不是 TokenStorageInterface 和/或 AuthorizationCheckerInterface,因为该类是 final 的,这使得它无法被模拟,进而使单元测试服务依赖于这些服务变得比必要的更加困难。 - Liiva
@Liiva 从这里我所能看到的是因为当服务构造函数运行时,用户可能尚未登录。https://symfony.com/doc/current/security.html#b-fetching-the-user-from-a-service - Brett Thomas
1
@Liiva:我已经检查了Security类的源代码(https://github.com/symfony/security-core/blob/5.2/Security.php),它只在文档中有`@final`,并没有标记为`final`。模拟测试正常,在Symfony 5.2.9中进行了测试。 - k00ni
1
@k00ni 你说得对,但在写作时它是一个最终类,后来随着这个提交的一部分移动到了文档中:https://github.com/symfony/security-core/commit/a44d4cef38ce2c9b8e43d94e4847ebcb5f0e65ef#diff-9883341373efe75f508748760a2f2b8f2ab98e90db5b49dc6b7063c83ff32406 - Liiva
显示剩余2条评论

47

使用构造函数依赖注入,您可以这样做:

use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;

class A
{
    private $user;

    public function __construct(TokenStorageInterface $tokenStorage)
    {
        $this->user = $tokenStorage->getToken()->getUser();
    }

    public function foo()
    {
        dump($this->user);
    }
}

2
为什么您说这只适用于Symfony 3.3+?在较旧的Symfony版本中,您的代码有哪些部分无法正常工作? - Radu Murzea
1
由于某种原因,我认为构造函数依赖注入不被支持,但事实并非如此。感谢您的评论。 - Mateusz
当我调用$tokenStorage->getToken()时,我收到了null。这可能是什么原因?用户已经登录... - Mathias Bader
1
如果您得到 $tokenStorage->getToken() 等于 null,请尝试将整个 $tokenStorage 存储为类属性,即在 __construct(...) 中:$this->tokenStorage = $tokenStorage - iloo
关于@iloo的评论:在使用令牌存储时,时间很重要。正如此处所述,当服务构造函数被调用时,auth过程可能尚未完成。因此,请存储引用$tokenStorage->getToken()或改用Symfony\Component\Security\Core\Security。我无法告诉这两者之间的行为差异。Security似乎在Symfony 4+中很常见。 - k00ni
+1 需要导入 NS,这个经常被忽略,但是有几个NS需要导入,很难弄清到底哪一个是正确答案所需的! - James

22

好答案,对我在Symfony 6上有效。 - undefined

5

3

在Symfony 5.2+和PHP 8.0+中,您还可以使用#[CurrentUser]属性获取已登录的用户。

namespace App\Controller;

use App\Entity\User;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Security\Http\Attribute\CurrentUser;

class FooController extends AbstractController
{
    public function index(#[CurrentUser] ?User $user)
    {
        // ...
    }
}

1
这是现在的最佳实践。请注意,如果您的控制器受到 IsGranted 属性的保护,您可以让用户键入不可为空的 #[CurrentUser] User $user - COil

2
Symfony在Symfony\Bundle\FrameworkBundle\ControllerControllerTrait中实现了这一功能。
protected function getUser()
{
    if (!$this->container->has('security.token_storage')) {
        throw new \LogicException('The SecurityBundle is not registered in your application.');
    }

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

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

    return $user;
}

所以,如果您只是注入并替换security.token_storage,那么您就可以开始了。

security.token_storage 替换为什么? - undefined
我现在会推荐这个答案:https://stackoverflow.com/a/46413924/519333 - undefined

0

2
这个类是一个服务,而不是控制器(从技术上讲,它可以同时兼具两者的角色,但这很罕见且不美观)。此外,我明确要求不要注入控制器。因此,你的回答虽然有点正确,但违反了这两个前提条件,所以从技术上讲是无效的。还是谢谢你。 - xDaizu

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接