php - Symfony 5:登录后用户仍然匿名(有时)
问题描述
我在Symfony 5.0.10中使用自定义身份验证器和自定义用户提供程序
我的一些用户抱怨他们无法再登录:事实上,在某些情况下,登录会成功(调用 onAuthenticationSuccess)但用户仍然是匿名的。这会导致直接重定向到登录页面。
这可以通过清除 cookie (PHPSESSID) 或使用私人导航窗口来解决。我无法解释这是如何进入匿名用户的登录逻辑的。
如果你们能找到真正对我有帮助的问题,我已经花了几个通宵来解决这个问题,但无法弄清楚。
这是我的代码:
安全.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 }
自定义身份验证器 (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");
}
}
还有我的 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
解决方案
这可能是由 symfony 会话固定保护引起的。默认情况下启用,并应在用户身份验证后刷新会话 ID。更多信息在 symfony 文档中
检查 PHPSESSID cookie 是否在每次请求后刷新。如果是这样,那么您的身份验证器会在每个用户请求上触发此方法刷新会话 ID。这导致以下情况:如果用户在收到前一个响应之前发出第二个请求,他们的会话 id 将变为无效,并且他们变得未经身份验证。
您当然可以在安全配置中禁用此保护:
security:
session_fixation_strategy: none
但更好的是解决问题,不要在您的系统中创建漏洞。
推荐阅读
- neo4j - 将本地 Neo4j 图形连接到 Databricks 集群
- python-3.x - 如何修复python中的“TypeError:必须是str,而不是_io.TextIOWrapper”错误
- r - ggplot图形存储相互擦除
- java - 每个 Spring websocket 事件的 Principal 为 null
- jenkins - 在同一硬件上编排一系列几乎相同的构建的最佳方式
- python - 反向字符串省略特殊字符和数字
- reactjs - React Native 升级重复符号问题
- python - 循环遍历函数参数以创建 power point 演示文稿
- gradle - 项目“cocoapods”未与 Gradle 链接
- python - 在这个代码示例中,unittest 计算了哪些测试?