简体   繁体   English

Symfony 5:登录后用户仍然匿名(有时)

[英]Symfony 5 : Users still anonymous after login (sometimes)

I'm using a custom authenticator and a custom user provider in Symfony 5.0.10我在Symfony 5.0.10中使用自定义身份验证器和自定义用户提供程序

Some of my users have complained that they can't login anymore: in fact in some cases, the login will be sucessful (onAuthenticationSuccess is called) but the user will still be anonymous.我的一些用户抱怨他们无法再登录了:事实上,在某些情况下,登录会成功(调用 onAuthenticationSuccess)但用户仍然是匿名的。 This causes a direct redirection to login page.这会导致直接重定向到登录页面。

This is solved by clearing the cookies (PHPSESSID) or by using a private navigation window.这可以通过清除 cookies (PHPSESSID) 或使用私人导航 window 来解决。 I can't explain how thats comes into the login logic of an anonymous user.我无法解释这是如何进入匿名用户的登录逻辑的。

If you guys can find the issue that would really help me, i've been spending a few all nighters on this and can't figure it out.如果你们能找到真正对我有帮助的问题,我已经花了几个通宵来解决这个问题,但无法弄清楚。

Here is my code:这是我的代码:

security.yaml安全性.yaml


security:
    encoders:
        App\Security\User:
            algorithm: none

    # https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers
    providers:
        # used to reload user from session & other features (e.g. switch_user)
        app_galette_user_provider:
            id: App\Security\GaletteUserProvider
    firewalls:
        dev:
            pattern: ^/(_(profiler|wdt)|css|images|js)/
            security: false
        main:
            anonymous: lazy
            provider: app_galette_user_provider
            logout:
                path: app_logout
            guard:
                authenticators:
                    - App\Security\AppCustomAuthenticator
                # where to redirect after logout
                # target: app_any_route

            # activate different ways to authenticate
            # https://symfony.com/doc/current/security.html#firewalls-authentication

            # https://symfony.com/doc/current/security/impersonating_user.html
            # switch_user: true

    # 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: ^/, roles: IS_AUTHENTICATED_FULLY }

Custom Authenticator (AppCustomAuthenticator.php)自定义身份验证器 (AppCustomAuthenticator.php)

<?php

namespace App\Security;

use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
use Symfony\Component\Security\Core\Exception\CustomUserMessageAuthenticationException;
use Symfony\Component\Security\Core\Exception\InvalidCsrfTokenException;
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Csrf\CsrfToken;
use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
use Symfony\Component\Security\Guard\Authenticator\AbstractFormLoginAuthenticator;
use Symfony\Component\Security\Guard\PasswordAuthenticatedInterface;
use Symfony\Component\Security\Http\Util\TargetPathTrait;

class AppCustomAuthenticator extends AbstractFormLoginAuthenticator implements PasswordAuthenticatedInterface
{
    private $urlGenerator;
    private $csrfTokenManager;
    private $passwordEncoder;

    public function __construct(UrlGeneratorInterface $urlGenerator, UserPasswordEncoderInterface $passwordEncoder)
    {
        $this->urlGenerator = $urlGenerator;
        $this->passwordEncoder = $passwordEncoder;
    }

    public function supports(Request $request)
    {
        return 'app_login' === $request->attributes->get('_route') && $request->isMethod('POST');
    }

    public function getCredentials(Request $request)
    {
        $credentials = [
            'username' => $request->request->get('email'),
            'password' => $request->request->get('password'),
            //'csrf_token' => $request->request->get('_csrf_token'),
        ];

        $request->getSession()->set(
            Security::LAST_USERNAME,
            $credentials['username']
        );

        return $credentials;
    }

    public function supportsRememberMe()
    {
        return false;
    }

    public function getUser($credentials, UserProviderInterface $userProvider)
    {
        // Load / create our user however you need.
        // You can do this by calling the user provider, or with custom logic here.
        try {
            $user = $userProvider->loadUserByUsername($credentials['username']);
        } catch (UsernameNotFoundException $e) {
            throw new CustomUserMessageAuthenticationException("Erreur lors de la connexion : veuillez vérifier vos identifiants et l'état de votre cotisation.");
        }

        if (!$user) {
            // fail authentication with a custom error
            throw new CustomUserMessageAuthenticationException('Email could not be found.');
        }

        return $user;
    }

    public function checkCredentials($credentials, UserInterface $user)
    {
        if ($credentials['password'] === $user->getPassword()) {
            return true;
        }
        return false;
    }

    /**
     * Used to upgrade (rehash) the user's password automatically over time.
     */
    public function getPassword($credentials): ?string
    {
        return $credentials['password'];
    }

    public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
    {
        return new RedirectResponse($this->urlGenerator->generate('index'));
    }

    protected function getLoginUrl()
    {
        return $this->urlGenerator->generate("app_login");
    }
}

And my User.php entity:我的 User.php 实体:

<?php

namespace App\Security;

use Symfony\Component\Security\Core\User\EquatableInterface;
use Symfony\Component\Security\Core\User\UserInterface;

class User implements UserInterface, EquatableInterface
{

    private $id;
    private $email;
    private $roles;
    private $password;
    private $nom;
    private $prenom;
    private $adresse;
    private $adresse2;
    private $cp;
    private $ville;
    private $pays;
    private $tel;
    private $gsm;
    private $salt;
    private $username;

    public function isEqualTo(UserInterface $user)
    {

        if ($this->getUsername() !== $user->getUsername()) {
            return false;
        }

        return true;
    }

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

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

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

    /**
     * @param string $nom
     */
    public function setNom($nom): void
    {
        $this->nom = $nom;
    }

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

    /**
     * @param string $prenom
     */
    public function setPrenom($prenom): void
    {
        $this->prenom = $prenom;
    }

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

    /**
     * @param string $adresse
     */
    public function setAdresse($adresse): void
    {
        $this->adresse = $adresse;
    }

    /**
     * @return string
     */
    public function getAdresse2(): ?string
    {
        return $this->adresse2;
    }

    /**
     * @param string $adresse2
     */
    public function setAdresse2($adresse2): void
    {
        $this->adresse2 = $adresse2;
    }

    /**
     * @return string
     */
    public function getCp(): ?string
    {
        return $this->cp;
    }

    /**
     * @param string $cp
     */
    public function setCp($cp): void
    {
        $this->cp = $cp;
    }

    /**
     * @return string
     */
    public function getVille(): ?string
    {
        return $this->ville;
    }

    /**
     * @param string $ville
     */
    public function setVille($ville): void
    {
        $this->ville = $ville;
    }

    /**
     * @return string
     */
    public function getPays(): ?string
    {
        return $this->pays;
    }

    /**
     * @param string $pays
     */
    public function setPays($pays): void
    {
        $this->pays = $pays;
    }

    /**
     * @return string
     */
    public function getTel(): ?string
    {
        return $this->tel;
    }

    /**
     * @param string $tel
     */
    public function setTel($tel): void
    {
        $this->tel = $tel;
    }

    /**
     * @return string
     */
    public function getGsm(): ?string
    {
        return $this->gsm;
    }

    /**
     * @param string $gsm
     */
    public function setGsm($gsm): void
    {
        $this->gsm = $gsm;
    }

    public function getEmail(): ?string
    {
        return $this->email;
    }

    public function setEmail(string $email): self
    {
        $this->email = $email;

        return $this;
    }

    /**
     * A visual identifier that represents this user.
     *
     * @see UserInterface
     */
    public function getUsername(): string
    {
        return $this->username;
    }

    public function setUsername(string $username): self
    {
        $this->username = $username;

        return $this;
    }

    /**
     * @see UserInterface
     */
    public function getRoles(): array
    {
        $roles = $this->roles;
        // guarantee every user at least has ROLE_USER
        $roles[] = 'ROLE_USER';

        return array_unique($roles);
    }

    public function setRoles(array $roles): self
    {
        $this->roles = $roles;

        return $this;
    }

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

    public function setPassword(string $password): self
    {
        $this->password = $password;

        return $this;
    }

    /**
     * @see UserInterface
     */
    public function getSalt()
    {
        return;
    }

    /**
     * @see UserInterface
     */
    public function eraseCredentials()
    {
        // If you store any temporary, sensitive data on the user, clear it here
        // $this->plainPassword = null;
    }

}

'''

-> I'm not including my custom user provider as i know it to work (it correctly returns the user)
-> My user passwords are indeed "in clear", this is a very specific scenario which poses no security threat

This may be caused by symfony session fixation protection.这可能是由 symfony session 固定保护引起的。 It is enabled by default and should refresh session id after user authentication.默认情况下启用,并在用户认证后刷新 session id。 More info in symfony docs symfony 文档中的更多信息

Check if the PHPSESSID cookie refreshes after EVERY request.检查 PHPSESSID cookie 是否在每次请求后刷新。 If it does, then your authenticator triggers this method refreshing session id on each user request.如果是这样,那么您的身份验证器会在每个用户请求上触发此方法刷新 session id。 Which leads to the following: if the user makes second request before they receive the response from the previous, their session id becomes invalid, and they become unauthenticated.这导致以下情况:如果用户在收到前一个响应之前发出第二个请求,则他们的 session id 将变为无效,并且他们变得未经身份验证。

You can of course disable this protection in your security config:您当然可以在安全配置中禁用此保护:

security:
    session_fixation_strategy: none

but better is to fix the problem and do not create a vulnerability in your system.但更好的是解决问题,不要在您的系统中创建漏洞。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM