I
I
Igor2019-11-13 20:37:37
symfony
Igor, 2019-11-13 20:37:37

Problem in every second request?

Colleagues, hello!
There was a problem with the synfony security system (firewalls)
Look at the gif.
5dcc3df040dc6751050984.gif
As for the animation, when I request it, I expect an error and only that!


An exception occurred while executing 'INSERT INTO services (name, altName, lft, lvl, rgt, root_id, parent_id) VALUES (?, ?, ?, ?, ?, ?, ?)' with params [\"A\", null, 0, 0, 0, null, 26]:\n\nSQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry 'A-26' for key 'services_unique_name_parent_id'

Description of the problem.
When I execute a query, the first time everything works, if I immediately execute a second query, the error There is no user provider for user \"App\\Entity\\User\" immediately occurs.
And so on, it turns out that an error occurs every other time.
Here are some options.
config/admin/packages/security.yaml
# https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers
    providers:
        in_memory: { memory: null }

    firewalls:
        dev:
            pattern: ^/(_(profiler|wdt)|css|images|js)/
            security: false

        get_access_token:
            pattern: ^/security.getAccessToken
            guard:
                entry_point: App\Security\CreateAccessToken

        check_access_token:
            pattern: ^/security.checkAccessToken
            guard:
                entry_point: App\Security\CheckAccessToken

        main:
            anonymous: ~
            guard:
                authenticators :
                    - App\Security\TokenAuthenticator

src/Security/TokenAuthenticator.php
TokenAuthenticator

<?php

namespace App\Security;

use App\Entity\User;
use App\Exception\Api\EAuthorisationError;
use App\Exception\Api\ExceptionInvalidRequest;
use Doctrine\ORM\EntityManagerInterface;
use Jose\Component\Core\AlgorithmManager;
use Jose\Component\Core\AlgorithmManagerFactory;
use Jose\Component\Core\JWK;
use Jose\Component\Signature\JWSVerifier;
use Jose\Component\Signature\Serializer\CompactSerializer;
use Jose\Component\Signature\Serializer\JWSSerializerManager;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Guard\AbstractGuardAuthenticator;

/**
 * Class TokenAuthenticator 
 * @package App\Security
 */
class TokenAuthenticator extends AbstractGuardAuthenticator
{

    /** @var AlgorithmManagerFactory */
    private $algorithmManagerFactory;

    /** @var AlgorithmManager */
    private $algorithmManager;

    /** @var EntityManagerInterface */
    private $entityManager;
    /** @var string */
    private $ip;


    /**
     * AppAuthAuthenticator constructor.
     * @param EntityManagerInterface $entityManager
     * @param AlgorithmManagerFactory $algorithmManagerFactory
     */
    public function __construct(EntityManagerInterface $entityManager, AlgorithmManagerFactory $algorithmManagerFactory)
    {
        $this->entityManager = $entityManager;
        $this->algorithmManagerFactory = $algorithmManagerFactory;
        $this->algorithmManager = $this->algorithmManagerFactory->create(["RS256", "HS512"]);
        $this->ip = $_SERVER['REMOTE_ADDR'];
    }

    /**
     * @param string $token
     * @return bool
     * @throws EAuthorisationError
     */
    public function verification(string $token): bool
    {
        $jwk = new JWK([
            "kty" => "oct",
            "k" => base64_encode($_ENV["JWT_KEY"] . $this->ip),
        ]);

        // The serializer manager. We only use the JWS Compact Serialization Mode.
        $serializerManager = new JWSSerializerManager([
            new CompactSerializer(),
        ]);

        $jwsVerifier = new JWSVerifier($this->algorithmManager);

        try {
            $jws = $serializerManager->unserialize($token);
        }catch (\Exception $exception) {
            throw new EAuthorisationError("Не удалось проверить токен");
        }

        return $jwsVerifier->verifyWithKey($jws, $jwk, 0);
    }

    /**
     * @param Request $request
     * @return bool|void
     */
    public function supports(Request $request)
    {
        return true;
    }


    /**
     * @param Request $request
     * @return array
     * @throws EAuthorisationError
     */
    public function getCredentials(Request $request)
    {
        try {
            preg_match('/Bearer\s+(.*?)$/m', $request->headers->get("authorization"), $match);
            return [
                "token" => $match[1],
            ];
        }catch (\Exception $exception) {
            throw new EAuthorisationError("Не верный токен");
        }
    }



    /**
     * Return a UserInterface object based on the credentials.
     *
     * The *credentials* are the return value from getCredentials()
     *
     * You may throw an AuthenticationException if you wish. If you return
     * null, then a UsernameNotFoundException is thrown for you.
     *
     * @param mixed $credentials
     *
     * @param UserProviderInterface $userProvider
     * @return User|UserProviderInterface
     */
    public function getUser($credentials, UserProviderInterface $userProvider)
    {
        return new User();
    }

    /**
     * Returns true if the credentials are valid.
     *
     * If any value other than true is returned, authentication will
     * fail. You may also throw an AuthenticationException if you wish
     * to cause authentication to fail.
     *
     * The *credentials* are the return value from getCredentials()
     *
     * @param mixed $credentials
     *
     * @return bool
     *
     * @throws AuthenticationException
     * @throws EAuthorisationError
     */
    public function checkCredentials($credentials, UserInterface $user)
    {
        if ($this->verification($credentials["token"])) {
            return true;
        }

        throw new EAuthorisationError("Не верный токен");
    }

    /**
     * Called when authentication executed, but failed (e.g. wrong username password).
     *
     * This should return the Response sent back to the user, like a
     * RedirectResponse to the login page or a 403 response.
     *
     * If you return null, the request will continue, but the user will
     * not be authenticated. This is probably not what you want to do.
     *
     * @return Response|null
     */
    public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
    {
        return null;
    }

    /**
     * Called when authentication executed and was successful!
     *
     * This should return the Response sent back to the user, like a
     * RedirectResponse to the last page they visited.
     *
     * If you return null, the current request will continue, and the user
     * will be authenticated. This makes sense, for example, with an API.
     *
     * @param string $providerKey The provider (i.e. firewall) key
     *
     * @return Response|null
     */
    public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
    {
        return null;
    }

    /**
     * Does this method support remember me cookies?
     *
     * Remember me cookie will be set if *all* of the following are met:
     *  A) This method returns true
     *  B) The remember_me key under your firewall is configured
     *  C) The "remember me" functionality is activated. This is usually
     *      done by having a _remember_me checkbox in your form, but
     *      can be configured by the "always_remember_me" and "remember_me_parameter"
     *      parameters under the "remember_me" firewall key
     *  D) The onAuthenticationSuccess method returns a Response object
     *
     * @return bool
     */
    public function supportsRememberMe()
    {
        // TODO: Implement supportsRememberMe() method.
    }

    /**
     * Returns a response that directs the user to authenticate.
     *
     * This is called when an anonymous request accesses a resource that
     * requires authentication. The job of this method is to return some
     * response that "helps" the user start into the authentication process.
     *
     * Examples:
     *
     * - For a form login, you might redirect to the login page
     *
     *     return new RedirectResponse('/login');
     *
     * - For an API token authentication system, you return a 401 response
     *
     *     return new Response('Auth header required', 401);
     *
     * @param Request $request The request that resulted in an AuthenticationException
     * @param AuthenticationException $authException The exception that started the authentication process
     *
     * @return Response
     * @throws EAuthorisationError
     * @throws ExceptionInvalidRequest
     */
    public function start(Request $request, AuthenticationException $authException = null)
    {
        if (!$this->supports($request)) {
            throw new ExceptionInvalidRequest("Требуется параметр [token]!", $request);
        }

        $credentials = $this->getCredentials($request);

        if ($this->verification($credentials["token"])) {
            return new Response("");
        }

        throw new EAuthorisationError("Token is not valid!");
    }
}

Answer the question

In order to leave comments, you need to log in

1 answer(s)
B
BoShurik, 2019-11-13
@IgorPI

You need to either implement a primitive provider that will return new User()instead of your code

public function getUser($credentials, UserProviderInterface $userProvider)
{
    return new User();
}

either add stateless: true
Issue to the ContextListener which is
It expects the session to have a user token and tries to retrieve it from a provider you don't have.

Didn't find what you were looking for?

Ask your question

Ask a Question

731 491 924 answers to any question