【问题标题】:Symfony Authentication Token lost after redirect重定向后 Symfony 身份验证令牌丢失
【发布时间】:2018-03-18 18:56:49
【问题描述】:

首先,不要费心复制这段代码。这将很快在我的 github 上可用。 (我会更新这篇文章以防有人需要它)

嗨。我正在尝试使用 Steam 在我的应用程序中进行连接。因此,我尝试创建自定义用户提供程序和自定义身份验证。单击登录按钮后,我的用户(我自己添加了一个)从数据库中加载,我被重定向到我的自定义页面。在该页面中,我的调试工具栏告诉我我已使用自定义令牌和防火墙进行了身份验证。如果我移动到另一个页面,例如“/search”,我的调试工具栏会告诉我我不再经过身份验证......

我做错了什么?

我正在使用 Symfony 4.0.6。谢谢!

P.S.:这个脚本的灵感来自这个:https://github.com/SirWaddles/SteamAuthBundle

P.P.S:如果我遗漏了任何文件并且您需要它,请回复。

P.P.P.S:我认为这是serialize()unserialize() 的问题,但我不知道具体。

Player.php

<?php

namespace App\Entity;

use App\Service\SteamAuth\User\SteamUserInterface;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\User\AdvancedUserInterface;
use Symfony\Component\Security\Core\User\EquatableInterface;
use Symfony\Component\Security\Core\User\UserInterface;

/**
 * Class Player
 * @package App\Entity
 *
 * @ORM\Entity(repositoryClass="App\Repository\PlayerRepository")
 * @ORM\Table(name="players")
 */
class Player implements UserInterface, SteamUserInterface, AdvancedUserInterface, EquatableInterface, \Serializable
{
    /**
     * @ORM\Id
     * @ORM\GeneratedValue
     * @ORM\Column(type="integer")
     *
     * @var int
     */
    private $id;

    /**
     * @ORM\Column(type="string", length=255, )
     *
     * @var string
     */
    private $username;

    /**
     * @ORM\Column(type="string", length=255)
     *
     * @var string
     */
    private $name;

    /**
     * @ORM\Column(type="string", length=255)
     *
     * @var string
     */
    private $password;

    /**
     * @ORM\Column(type="string", length=255, nullable=true)
     *
     * @var null|string
     */
    private $avatar;

    /**
     * @var array
     *
     * @ORM\Column(type="array")
     */
    private $roles;

    /**
     * @var \DateTime|null
     *
     * @ORM\Column(type="datetime", nullable=true)
     */
    private $lastSync;

    /**
     * @var bool
     *
     * @ORM\Column(type="boolean")
     */
    private $enabled;

    /**
     * @return int
     */
    public function getId(): int
    {
        return $this->id;
    }

    /**
     * @param int $id
     *
     * @return Player
     */
    public function setId(int $id): Player
    {
        $this->id = $id;

        return $this;
    }

    /**
     * @return string
     */
    public function getUsername(): string
    {
        return $this->username;
    }

    /**
     * @param string $username
     *
     * @return Player
     */
    public function setUsername(string $username): Player
    {
        $this->username = $username;

        return $this;
    }

    /**
     * @return string
     */
    public function getName(): string
    {
        return $this->name;
    }

    /**
     * @param string $name
     *
     * @return Player
     */
    public function setName(string $name): Player
    {
        $this->name = $name;

        return $this;
    }

    /**
     * @return string
     */
    public function getPassword()
    {
        return $this->password;
    }

    /**
     * @return array
     */
    public function getRoles()
    {
        return $this->roles;
    }

    /**
     * @param array $roles
     * @return Player
     */
    public function setRoles(array $roles): Player
    {
        $this->roles = $roles;

        return $this;
    }

    /**
     * @return \DateTime|null
     */
    public function getLastSync(): ?\DateTime
    {
        return $this->lastSync;
    }

    /**
     * @param \DateTime|null $lastSync
     * @return Player
     */
    public function setLastSync(?\DateTime $lastSync): Player
    {
        $this->lastSync = $lastSync;

        return $this;
    }

    /**
     * @return null|string
     */
    public function getSalt()
    {
        return null;
    }

    /**
     * @param string $password
     * @return Player
     */
    public function setPassword(string $password): Player
    {
        $this->password = $password;

        return $this;
    }

    /**
     * @return null|string
     */
    public function getAvatar(): ?string
    {
        return $this->avatar;
    }

    /**
     * @param null|string $avatar
     *
     * @return Player
     */
    public function setAvatar(?string $avatar): Player
    {
        $this->avatar = $avatar;

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function eraseCredentials()
    {
    }

    /**
     * {@inheritdoc}
     */
    public function isAccountNonExpired()
    {
        return true;
    }

    /**
     * {@inheritdoc}
     */
    public function isAccountNonLocked()
    {
        return true;
    }

    /**
     * {@inheritdoc}
     */
    public function isCredentialsNonExpired()
    {
        return true;
    }

    /**
     * {@inheritdoc}
     */
    public function isEnabled()
    {
        return $this->enabled;
    }

    /**
     * @param bool|null $enabled
     * @return Player
     */
    public function setEnabled(?bool $enabled): Player
    {
        $this->enabled = $enabled;

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function isEqualTo(UserInterface $user)
    {
        if ($this->username !== $user->getUsername()) {
            return false;
        }

        return true;
    }

    /**
     * {@inheritdoc}
     */
    public function serialize()
    {
        return serialize([
            $this->id,
            $this->username,
            $this->name,
            $this->avatar,
            $this->password,
            $this->enabled
        ]);
    }

    /**
     * {@inheritdoc}
     */
    public function unserialize($data)
    {
        list($this->id, $this->username, $this->name, $this->avatar, $this->password, $this->enabled) = unserialize($data);
    }

    /**
     * @return string
     */
    public function __toString()
    {
        return $this->getUsername() ?? '-';
    }
}

SteamToken.php

<?php

namespace App\Service\SteamAuth\Token;

use Symfony\Component\Security\Core\Authentication\Token\AbstractToken;

/**
 * Class SteamToken
 * @package App\Service\SteamAuth\Token
 */
class SteamToken extends AbstractToken
{
    /**
     * {@inheritdoc}
     */
    public function __construct(array $roles = [])
    {
        parent::__construct($roles);

        $this->setAuthenticated(count($roles) > 0);
    }

    /**
     * {@inheritdoc}
     */
    public function setAttributes(array $attributes)
    {
        foreach ($attributes as $key => $attribute) {
            $key = str_replace("openid_", "openid.", $key);
            $this->setAttribute($key, $attribute);
        }

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function getCredentials()
    {
    }


    /**
     * {@inheritdoc}
     */
    public function serialize()
    {
        return serialize([
            $this->getUser(),
            $this->isAuthenticated(),
            $this->getAttributes()
        ]);
    }

    /**
     * {@inheritdoc}
     */
    public function unserialize($data)
    {
        $data = unserialize($data);

        $this->setUser($data[0]);
        $this->setAuthenticated($data[1]);
        $this->setAttributes($data[2]);
    }
}

SteamListener.php

<?php

namespace App\Service\SteamAuth\Firewall;

use App\Service\SteamAuth\Token\SteamToken;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Http\Firewall\ListenerInterface;

/**
 * Class SteamListener
 * @package App\Service\SteamAuth
 */
class SteamListener implements ListenerInterface
{
    /**
     * @var TokenStorageInterface
     */
    private $tokenStorage;

    /**
     * @var AuthenticationManagerInterface
     */
    private $authentication;

    /**
     * SteamListener constructor.
     *
     * @param TokenStorageInterface $tokenStorage
     * @param AuthenticationManagerInterface $authentication
     */
    public function __construct(TokenStorageInterface $tokenStorage, AuthenticationManagerInterface $authentication)
    {
        $this->tokenStorage = $tokenStorage;
        $this->authentication = $authentication;
    }

    public function handle(GetResponseEvent $event)
    {
        $request = $event->getRequest();

        if($request->get('_route') === 'login_check') {
            $token = new SteamToken();

            $token->setUser(str_replace("http://steamcommunity.com/openid/id/", "", $request->query->get('openid_claimed_id')));
            $token->setAttributes($request->query->all());

            try {
                $authToken = $this->authentication->authenticate($token);
                $this->tokenStorage->setToken($authToken);

                return;
            } catch (AuthenticationException $exception) {

            }
        }

        $response = new Response();
        $response->setStatusCode(Response::HTTP_FORBIDDEN);
        $event->setResponse($response);

        return;
    }
}

SteamProvider.php

<?php

namespace App\Service\SteamAuth\Authentication;

use App\Service\SteamAuth\Token\SteamToken;
use GuzzleHttp\Client;
use Symfony\Component\Security\Core\Authentication\Provider\AuthenticationProviderInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\User\UserProviderInterface;

/**
 * Class SteamProvider
 * @package App\Service\SteamAuth\Provider
 */
class SteamProvider implements AuthenticationProviderInterface
{
    /**
     * @var UserProviderInterface
     */
    private $userProvider;

    /**
     * @var Client
     */
    private $client;

    /**
     * SteamProvider constructor.
     *
     * @param UserProviderInterface $userProvider
     * @param Client                $client
     */
    public function __construct(UserProviderInterface $userProvider, Client $client)
    {
        $this->userProvider = $userProvider;
        $this->client       = $client;
    }

    /**
     * {@inheritdoc}
     */
    public function authenticate(TokenInterface $token)
    {
        if ($token->getAttribute('openid.ns') !== "http://specs.openid.net/auth/2.0") {
            throw new AuthenticationException("Invalid token !");
        }

        $checkAuth                = $token->getAttributes();
        $checkAuth['openid.mode'] = 'check_authentication';

        $response = $this->client->request('GET', 'login', ['query' => $checkAuth]);

        if ((string)$response->getBody() === "ns:http://specs.openid.net/auth/2.0\nis_valid:true\n") {
            $user = $this->userProvider->loadUserByUsername($token->getUsername());

            $authToken = new SteamToken($user->getRoles());
            $authToken->setUser($user);

            return $authToken;
        }

        throw new AuthenticationException("Invalid token !");
    }

    /**
     * {@inheritdoc}
     */
    public function supports(TokenInterface $token)
    {
        return $token instanceof SteamToken;
    }
}

SteamUserProvider.php

<?php

namespace App\Service\SteamAuth\User;

use App\Entity\Player;
use App\Service\SteamAuth\SteamUserService;
use Doctrine\ORM\EntityManager;
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;

/**
 * Class SteamUserProvider
 * @package App\Service\SteamAuth\User
 */
class SteamUserProvider implements UserProviderInterface
{
    /**
     * @var EntityManager
     */
    private $entityManager;

    /**
     * @var string
     */
    private $userClass;

    /**
     * @var SteamUserService
     */
    private $userService;

    /**
     * SteamUserProvider constructor.
     *
     * @param EntityManager    $entityManager
     * @param SteamUserService $userService
     * @param string           $userClass
     */
    public function __construct(EntityManager $entityManager, SteamUserService $userService, string $userClass)
    {
        $this->entityManager = $entityManager;
        $this->userService   = $userService;
        $this->userClass     = $userClass;
    }

    /**
     * {@inheritdoc}
     */
    public function loadUserByUsername($username)
    {
        $repository = $this->entityManager->getRepository($this->userClass);
        $player     = $repository->findOneBy(['username' => $username]);

        if (!$player) {
            /**
             * @var $player Player
             */
            $player = new $this->userClass();
            $player->setUsername($username);
            $player->setPassword(md5(random_bytes(15)));
            $player->setRoles(['ROLE_USER']);
            $player->setEnabled(1);

            $player = $this->userService->updateUserEntry($player);

            $this->entityManager->persist($player);
            $this->entityManager->flush($player);
        }

        /// if last update....

        return $player;
    }

    /**
     * {@inheritdoc}
     */
    public function refreshUser(UserInterface $user)
    {
        if (!$user instanceof SteamUserInterface) {
            throw new UnsupportedUserException("User not supported!");
        }

        return $this->loadUserByUsername($user->getUsername());
    }

    /**
     * {@inheritdoc}
     */
    public function supportsClass($class)
    {
        return $class === $this->userClass;
    }
}

services.yaml

services:
    ...
    # Aliases
    GuzzleHttp\Client: '@eight_points_guzzle.client.login'

    # Log In System
    app.steam_user.service:
        class: App\Service\SteamAuth\SteamUserService
        arguments: ['@eight_points_guzzle.client.steam', '%steam_api_key%']

    app.steam_user.provider:
        class: App\Service\SteamAuth\User\SteamUserProvider
        arguments:
            $entityManager: '@doctrine.orm.default_entity_manager'
            $userService: '@app.steam_user.service'
            $userClass: '%steam_user_class%'

    app.steam.provider:
        class: App\Service\SteamAuth\Authentication\SteamProvider
        arguments:
            $userProvider: '@app.steam_user.provider'
            $client: '@eight_points_guzzle.client.login'

    app.steam.listener:
        class: App\Service\SteamAuth\Firewall\SteamListener

security.yaml

security:
    # https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers
    providers:
        steamauth:
            id: app.steam_user.provider
    firewalls:
        steam_auth:
            pattern: ^/login_check
            stateless: true
            steam: ~
            form_login:
                csrf_token_generator: security.csrf.token_manager
            remember_me:
                secret: '%kernel.secret%'
        dev:
            pattern: ^/(_(profiler|wdt)|css|images|js)/
            security: false
    # Easy way to control access for large sections of your site
    # Note: Only the *first* access control that matches will be used
    access_control:
        - { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
        - { path: ^/login_check, roles: IS_AUTHENTICATED_ANONYMOUSLY }

编辑:如果我在刷新之前dump($this-&gt;get('session')-&gt;all()) 和该页面,我会得到:"_security_steam_auth" =&gt; "C:38:"App\Service\SteamAuth\Token\SteamToken":40:{a:4:{i:0;N;i:1;b:0;i:2;a:0:{}i:3;a:0:{}}}"

【问题讨论】:

  • @MathieuDormeval 我读了几遍...
  • 如果在 security.yml 上设置stateless=false 会怎样?如果是无状态的,则没有cookie,因此如果您返回时存在重定向,则浏览器不知道您是谁。
  • 如果您检查localStorage,是否有您保存的token?因为如果您在登录页面后找不到它,可能是因为您从未保存它。
  • @Pipe 不行,还是不行。

标签: symfony authentication steam


【解决方案1】:

我也遇到过类似的问题。

我的序列化过程中缺少一个属性:isActive 我一直不明白为什么,但是当这个属性是我的序列化/反序列化过程时,它可以正常工作,如果不是,它根本不起作用。

这是我的来源:https://github.com/matthieuleorat/documentManager/blob/master/src/Entity/User.php#L253

文档:http://symfony.com/doc/current/security/entity_provider.html#security-serialize-equatable

希望对您有所帮助。

【讨论】:

  • 嗨。我去看看。如果有效,您将获得积分!谢谢 ! :D
  • 你没有自定义令牌,不是吗?
【解决方案2】:

您是否启用了 module_suhosin7?

我们在启用 suhosin7 的会话中遇到问题。 事实上,它添加了一些关于会话和 cookie 管理的规则。

如果启用,请尝试禁用它并检查它是否有效。

关于使用 suhosin7 进行会话加密存在一个已知问题: https://github.com/sektioneins/suhosin7/issues/21

【讨论】:

  • 嗨。不,我没有使用 Suhosin。
【解决方案3】:

SteamListener.php 中的这一行会阻止任何其他路由工作。

$request = $event->getRequest();

if($request->get('_route') === 'login_check') {
    [...]
}

$response = new Response();
$response->setStatusCode(Response::HTTP_FORBIDDEN);
$event->setResponse($response);

每次导航到一个页面时都会调用安全防火墙handle()函数,所以如果你将其他页面状态设置为禁止,你将无法在登录之外的其他页面上导航。

这应该通过删除它来工作。如果问题解决了请告诉我

【讨论】:

  • 好的,我尝试删除那些行。没有改变。我删除了除 $request = $event-&gt;getRequest()try-catch 块之外的所有内容,因为我有必要从 url 获取一些属性。编辑:关于那些禁止响应的行,这就是 Symfony 解释如何制作自定义身份验证提供程序的方式。 (symfony.com/doc/current/security/…)
  • 在他们的情况下,他们总是尝试验证令牌,而您的代码中并非如此,这就是为什么我认为这些行有问题
【解决方案4】:

经过大量工作,我决定再次研究 Steam 身份验证捆绑包,我找到了一个适用于 Symfony 4 的捆绑包,我确认它可以工作。

链接:https://github.com/knojector/SteamAuthenticationBundle

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-09-22
    • 1970-01-01
    • 2013-03-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-07-18
    • 2018-12-14
    相关资源
    最近更新 更多