Я пытаюсь использовать два метода аутентификации на моем сайте:
Использование адреса электронной почты / пароля (работает)
ИспользованиеSteam (где у меня проблема)
Вот мой services.yaml
:
security:
encoders:
App\Entity\User\User:
algorithm: bcrypt
providers:
user_provider:
entity:
class: App\Entity\User\User
steam_provider:
id: App\Security\SteamAuth\User\SteamUserProvider
chain_provider:
chain:
providers: [user_provider, steam_provider]
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
steam:
anonymous: ~
pattern: /steam/login
user_checker: App\Security\User\UserChecker
provider: steam_provider
steam: true
logout:
path: /logout
target: /
classic:
anonymous: true
user_checker: App\Security\User\UserChecker
provider: user_provider
form_login:
provider: user_provider
default_target_path: /servers
use_referer: true
login_path: /login
check_path: /login
username_parameter: login_form[emailAddress]
password_parameter: login_form[password]
remember_me:
remember_me_parameter: login_form[remember_me]
secret: '%kernel.secret%'
lifetime: 1209600 # 2 week in seconds
path: /
logout:
target: /
access_control:
# Users
- { path: '^/steam/login', roles: IS_AUTHENTICATED_ANONYMOUSLY }
По сравнению с другими сервисами Steam предлагает аутентификацию только с OpenID. Нет проблем, я обновил свой код для работы OpenID.
На мой взгляд, все работает нормально, даже токен работает правильно. Основная проблема после перезагрузки страницы. Аутентификационный токен потерян, и я авторизован как Анонимный пользователь. Как видите, я перенаправляю пользователей на /login
, несмотря ни на что (у меня есть логика для перенаправления с этой страницы, если пользователь аутентифицирован).
Вот мой код:
SteamListener.php
<?php declare(strict_types = 1);
namespace App\Security\SteamAuth\Firewall;
use App\Security\SteamAuth\Authentication\Token\SteamUserToken;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
/**
* Class SteamListener
* @package App\Security\SteamAuth\Firewall
*/
class SteamListener
{
/**
* @var AuthenticationManagerInterface
*/
private $authenticationManager;
/**
* @var RouterInterface
*/
private $router;
/**
* @var TokenStorageInterface
*/
private $tokenStorage;
/**
* SteamListener constructor.
*
* @param AuthenticationManagerInterface $authenticationManager
* @param TokenStorageInterface $tokenStorage
* @param RouterInterface $router
*/
public function __construct(AuthenticationManagerInterface $authenticationManager, TokenStorageInterface $tokenStorage, RouterInterface $router)
{
$this->authenticationManager = $authenticationManager;
$this->tokenStorage = $tokenStorage;
$this->router = $router;
}
/**
* Try to authenticate user based on SteamID.
*
* @param RequestEvent $event
*/
public function __invoke(RequestEvent $event)
{
$request = $event->getRequest();
$claimedId = str_replace('https://steamcommunity.com/openid/id/', '', $request->query->get('openid_claimed_id'));
try {
$token = new SteamUserToken($claimedId);
$token->setAttributes($request->query->all());
$authToken = $this->authenticationManager->authenticate($token);
$this->tokenStorage->setToken($authToken);
} catch (AuthenticationException $exception) {
$token = $this->tokenStorage->getToken();
if ($token instanceof SteamUserToken) {
$this->tokenStorage->setToken(null);
}
}
$event->setResponse(new RedirectResponse($this->router->generate('login')));
}
}
SteamProvider.php
<?php declare(strict_types = 1);
namespace App\Security\SteamAuth\Authentication\Provider;
use App\Security\SteamAuth\Authentication\Token\SteamUserToken;
use Symfony\Component\HttpClient\HttpClient;
use Symfony\Component\HttpFoundation\Request;
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;
use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
/**
* Class SteamProvider
* @package App\Security\SteamAuth\Provider
*/
class SteamProvider implements AuthenticationProviderInterface
{
const ACCEPTED_RESPONSE = "ns:http://specs.openid.net/auth/2.0\nis_valid:true\n";
/**
* This actually points to UserRepository.
*
* @var UserProviderInterface
*/
private $userProvider;
/**
* SteamProvider constructor.
*
* @param UserProviderInterface $userProvider
*/
public function __construct(UserProviderInterface $userProvider)
{
$this->userProvider = $userProvider;
}
/**
* {@inheritDoc}
*
* Note: Token is an instance of SteamUserToken.
*/
public function authenticate(TokenInterface $token)
{
if (!$user = $this->userProvider->loadUserByUsername($token->getUsername())) {
throw new AuthenticationException('Steam auth is invalid!');
}
if ($token->getAttribute('openid_ns') !== 'http://specs.openid.net/auth/2.0') {
throw new AuthenticationException('Steam token is invalid!');
}
// Validate SteamID before authenticating user.
$checkAuth = $token->getAttributes();
$checkAuth['openid_mode'] = 'check_authentication';
try {
$request = HttpClient::create();
$response = $request->request(Request::METHOD_GET, $checkAuth['openid_op_endpoint'], ['query' => $checkAuth]);
if ($response->getContent() !== self::ACCEPTED_RESPONSE) {
throw new AuthenticationException('Steam token is invalid!');
}
$authToken = new SteamUserToken($token->getUsername(), $user->getRoles());
$authToken->setUser($user);
return $authToken;
} catch (ClientExceptionInterface|RedirectionExceptionInterface|ServerExceptionInterface|TransportExceptionInterface $e) {
throw new AuthenticationException('Steam token is invalid!');
}
}
/**
* {@inheritDoc}
*/
public function supports(TokenInterface $token)
{
return $token instanceof SteamUserToken;
}
}
SteamUserToken.php
<?php declare(strict_types = 1);
namespace App\Security\SteamAuth\Authentication\Token;
use App\Entity\User\User;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\User\UserInterface;
/**
* Class SteamUserToken
* @package App\Security\SteamAuth\Authentication
*/
class SteamUserToken implements TokenInterface
{
/**
* @var array
*/
private $attributes = [];
/**
* @var bool
*/
private $authenticated = false;
/**
* @var User|null
*/
private $user = null;
/**
* @var array
*/
private $roles = [];
/**
* @var string|null
*/
private $steamId = null;
/**
* SteamUserToken constructor.
*
* @param string $steamId
* @param array $roles
*/
public function __construct(string $steamId, array $roles = [])
{
$this->steamId = $steamId;
$this->roles = $roles;
$this->authenticated = \count($roles) > 0;
}
/**
* {@inheritDoc}
*/
public function getUser()
{
return $this->user;
}
/**
* {@inheritDoc}
* @var $user UserInterface
*/
public function setUser($user)
{
$this->user = $user;
}
/**
* {@inheritDoc}
*/
public function getUsername()
{
return $this->steamId;
}
/**
* {@inheritDoc}
*/
public function getRoles()
{
if (!$this->user) {
return [];
}
return $this->roles;
}
/**
* @param array $roles
*/
public function setRoles(array $roles = [])
{
$this->roles = $roles;
}
/**
* {@inheritDoc}
*/
public function getRoleNames()
{
return array_map(function ($role) {return $role;}, $this->user ? $this->user->getRoles() : []);
}
/**
* {@inheritDoc}
*/
public function getCredentials()
{
return '';
}
/**
* {@inheritDoc}
*/
public function isAuthenticated()
{
return $this->authenticated;
}
/**
* {@inheritDoc}
*/
public function setAuthenticated($authenticated)
{
$this->authenticated = $authenticated;
}
/**
* {@inheritDoc}
*/
public function eraseCredentials()
{
if ($this->getUser() instanceof UserInterface) {
$this->getUser()->eraseCredentials();
}
}
/**
* {@inheritDoc}
*/
public function serialize()
{
return serialize($this->__serialize());
}
/**
* {@inheritDoc}
*/
public function __serialize(): array
{
return [$this->attributes, $this->authenticated, $this->steamId, $this->roles, $this->user];
}
/**
* {@inheritDoc}
*/
public function unserialize($serialized)
{
$this->__unserialize(unserialize($serialized));
}
/**
* {@inheritDoc}
*/
public function __unserialize(array $data): void
{
[$this->attributes, $this->authenticated, $this->steamId, $this->roles, $this->user] = $data;
}
/**
* {@inheritDoc}
*/
public function getAttributes()
{
return $this->attributes;
}
/**
* {@inheritDoc}
*/
public function setAttributes(array $attributes)
{
$this->attributes = $attributes;
}
/**
* {@inheritDoc}
*/
public function hasAttribute($name)
{
return \array_key_exists($name, $this->attributes);
}
/**
* {@inheritDoc}
*/
public function getAttribute($name)
{
if (!\array_key_exists($name, $this->attributes)) {
throw new \InvalidArgumentException(sprintf('This token has no "%s" attribute.', $name));
}
return $this->attributes[$name];
}
/**
* {@inheritDoc}
*/
public function setAttribute($name, $value)
{
$this->attributes[$name] = $value;
}
/**
* {@inheritDoc}
*/
public function __toString()
{
if (!$this->user) {
return '-';
}
return $this->user->getUsername();
}
}
Если я пишу return;
после установки $this->tokenStorage->setToken($authToken)
внутри SteamListener
, яЯ получаю сообщение об ошибке, но это любопытно: я правильно авторизован на этой странице (см. прикрепленное фото). В противном случае, я перенаправлен на страницу входа. (Вместо того, чтобы выдавать ошибку в этом методе, я вызвал $this->getUser()
и вернул мне аутентифицированного пользователя. Также я попытался перенаправить пользователя на другую страницу, и проблема все еще сохраняется).
РЕДАКТИРОВАТЬ: Вот мой журнал консоли:
2019-10-30T14:32:47+01:00 [debug] Stored the security token in the session.
2019-10-30T14:32:47+01:00 [debug] Notified event "kernel.response" to listener "Sonata\BlockBundle\Cache\HttpCacheHandler::onKernelResponse".
2019-10-30T14:32:47+01:00 [debug] Notified event "kernel.response" to listener "Symfony\Component\HttpKernel\EventListener\ResponseListener::onKernelResponse".
2019-10-30T14:32:47+01:00 [debug] Notified event "kernel.response" to listener "Symfony\Component\HttpKernel\DataCollector\RequestDataCollector::onKernelResponse".
2019-10-30T14:32:47+01:00 [debug] Notified event "kernel.response" to listener "Symfony\Component\Security\Http\RememberMe\ResponseListener::onKernelResponse".
2019-10-30T14:32:47+01:00 [debug] Notified event "kernel.response" to listener "Sensio\Bundle\FrameworkExtraBundle\EventListener\HttpCacheListener::onKernelResponse".
2019-10-30T14:32:47+01:00 [debug] Notified event "kernel.response" to listener "Symfony\Component\HttpKernel\EventListener\ProfilerListener::onKernelResponse".
2019-10-30T14:32:47+01:00 [debug] Notified event "kernel.response" to listener "Symfony\Bundle\WebProfilerBundle\EventListener\WebDebugToolbarListener::onKernelResponse".
2019-10-30T14:32:47+01:00 [debug] Notified event "kernel.response" to listener "Symfony\Component\HttpKernel\EventListener\DisallowRobotsIndexingListener::onResponse".
2019-10-30T14:32:47+01:00 [debug] Notified event "kernel.response" to listener "Symfony\Component\HttpKernel\EventListener\SessionListener::onKernelResponse".
2019-10-30T14:32:47+01:00 [debug] Notified event "kernel.response" to listener "Symfony\Component\HttpKernel\EventListener\StreamedResponseListener::onKernelResponse".
2019-10-30T14:32:47+01:00 [debug] Notified event "kernel.finish_request" to listener "Symfony\Component\HttpKernel\EventListener\LocaleListener::onKernelFinishRequest".
2019-10-30T14:32:47+01:00 [debug] Notified event "kernel.finish_request" to listener "Symfony\Component\HttpKernel\EventListener\SessionListener::onFinishRequest".
2019-10-30T14:32:47+01:00 [debug] Notified event "kernel.finish_request" to listener "Symfony\Component\HttpKernel\EventListener\RouterListener::onKernelFinishRequest".
2019-10-30T14:32:47+01:00 [debug] Notified event "kernel.finish_request" to listener "Symfony\Bundle\SecurityBundle\Debug\TraceableFirewallListener::onKernelFinishRequest".
2019-10-30T14:32:47+01:00 [debug] Notified event "kernel.finish_request" to listener "Symfony\Component\HttpKernel\EventListener\LocaleAwareListener::onKernelFinishRequest".
2019-10-30T14:32:47+01:00 [debug] Notified event "kernel.terminate" to listener "Symfony\Bundle\SwiftmailerBundle\EventListener\EmailSenderListener::onTerminate".
2019-10-30T14:32:47+01:00 [debug] Notified event "kernel.terminate" to listener "Symfony\Component\HttpKernel\EventListener\ProfilerListener::onKernelTerminate".
[Wed Oct 30 15:32:47 2019] 127.0.0.1:52654 [302]: /steam/login?openid.ns=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0&openid.mode=id_res&openid.op_endpoint=https%3A%2F%2Fsteamcommunity.com%2Fopenid%2Flogin&openid.claimed_id=https%3A%2F%2Fsteamcommunity.com%2Fopenid%2Fid%2F
76561198062372939&openid.identity=https%3A%2F%2Fsteamcommunity.com%2Fopenid%2Fid%2F76561198062372939&openid.return_to=http%3A%2F%2Flocalhost%3A8000%2Fsteam%2Flogin&openid.response_nonce=2019-10-30T13%3A31%3A49ZSGUwWiQNPgQRCry3p3AjonwB7rg%3D&openid.assoc_handle=1234567890
&openid.signed=signed%2Cop_endpoint%2Cclaimed_id%2Cidentity%2Creturn_to%2Cresponse_nonce%2Cassoc_handle&openid.sig=3ysTtN3Cc3FbC8b0oWi8ZTCrc0U%3D
2019-10-30T14:32:49+01:00 [info] User Deprecated: The Symfony\Bundle\TwigBundle\Loader\FilesystemLoader class is deprecated since version 4.3 and will be removed in 5.0; use Twig notation for templates instead.
2019-10-30T14:32:49+01:00 [info] Matched route "login".
2019-10-30T14:32:49+01:00 [info] Populated the TokenStorage with an anonymous Token.