symfony 4:成功验证后,它重定向到管理区域并使用匿名令牌填充 TokenStorage
symfony 4: after successful authentication it redirects to admin area and populates the TokenStorage with an anonymous Token
我有一个旧的 Symfony 3.1 站点,我升级到 Symfony 3。4.x 然后升级到 Symfony 4.4.11,但我没有将它升级到 symfony flex。我修复了很多问题,public 网站似乎正常工作。
我不得不重建身份验证,因为旧的与 sf4 不兼容。
我关注了这个
https://symfony.com/doc/4.4/security/form_login_setup.html
还有这个:
https://symfonycasts.com/screencast/symfony-security/make-user
我遇到的情况是,在身份验证成功后,当它重定向到管理区域时,它总是再次检查 LoginFormAuthenticator,这显然不支持管理区域,并且它重定向回匿名用户的登录页面.
关于这个问题有很多讨论,我尝试了所有发现的方法,但我没有找到解决方案。即使调试它也没有。
会话保存在定义的路径中。它的 id 与浏览器中的 PHPSESSID 相同。
站点运行 HTTP 协议。
security.yml
security:
encoders:
AppBundle\Entity\User:
algorithm: bcrypt
cost: 12
providers:
user_provider:
entity:
class: AppBundle:User
property: email
firewalls:
dev:
pattern: ^/(_(profiler|wdt|error)|css|images|js)/
security: false
main:
stateless: true
pattern: ^/
anonymous: true
logout_on_user_change: true
guard:
authenticators:
- AppBundle\Security\LoginFormAuthenticator
form_login:
provider: user_provider
username_parameter: email
csrf_token_generator: security.csrf.token_manager
login_path: app_login
logout:
path: app_logout
access_control:
- { path: ^/admin, roles: ROLE_ADMIN }
- { path: ^/, roles: IS_AUTHENTICATED_ANONYMOUSLY }
路由:
app_login:
path: /login
defaults: { _controller: AppBundle\Controller\BackendController:loginAction }
app_logout:
path: /logout
defaults: { _controller: AppBundle\Controller\BackendController:logoutAction }
app_admin:
path: /admin/{page}/{entry}
defaults: { _controller: AppBundle\Controller\BackendController:showAction, entry: null }
User.php
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\User\EquatableInterface;
use Symfony\Component\Security\Core\User\UserInterface;
/**
* User
*
* @ORM\Table(name="user")
* @ORM\Entity(repositoryClass="AppBundle\Repository\UserRepository")
*/
class User implements UserInterface, \Serializable, EquatableInterface
{
private $id;
// and so on
public function serialize()
{
return serialize(array(
$this->id,
$this->email,
$this->password
));
}
public function unserialize($serialized)
{
list (
$this->id,
$this->email,
$this->password,
) = unserialize($serialized);
}
public function getRoles()
{
return array('ROLE_ADMIN');
}
public function getUsername()
{
return $this->getEmail();
}
public function isEqualTo(UserInterface $user)
{
if (!$user instanceof User) {
return false;
}
if ($this->password !== $user->getPassword()) {
return false;
}
if ($this->salt !== $user->getSalt()) {
return false;
}
if ($this->email !== $user->getUsername()) {
return false;
}
return true;
}
}
后端控制器:
class BackendController extends AbstractController
{
public function loginAction(AuthenticationUtils $authenticationUtils)
{
return $this->render('AppBundle:Backend:page.html.twig', array(
'email' => $authenticationUtils->getLastUsername(),
'error' => $authenticationUtils->getLastAuthenticationError()
));
}
public function logoutAction()
{
$this->container->get('security.token_storage')->setToken(null);
$this->container->get('request')->getSession()->invalidate();
}
public function showAction(Request $request, $page, $entry)
{
$this->denyAccessUnlessGranted('ROLE_ADMIN', null, 'Unable to access this page!');
// some logic
}
}
LoginFormAuthentication.php
在示例中看起来一样,并且有效。它成功到达 onAuthenticationSuccess() 并重定向到管理区域。
dev.log
request.INFO: Matched route "app_login". {"route":"app_login"..}
security.DEBUG: Checking for guard authentication credentials. {"firewall_key":"main","authenticators":1} []
security.DEBUG: Checking support on guard authenticator. {"firewall_key":"main","authenticator":"AppBundle\Security\LoginFormAuthenticator"} []
security.DEBUG: Calling getCredentials() on guard authenticator. {"firewall_key":"main","authenticator":"AppBundle\Security\LoginFormAuthenticator"} []
security.DEBUG: Passing guard token information to the GuardAuthenticationProvider {"firewall_key":"main","authenticator":"AppBundle\Security\LoginFormAuthenticator"} []
doctrine.DEBUG: SELECT t0.* FROM user t0 WHERE t0.email = ? LIMIT 1 ["email@me.com"] []
security.INFO: Guard authentication successful! {"token":"[object] (Symfony\Component\Security\Guard\Token\PostAuthenticationGuardToken: PostAuthenticationGuardToken(user=\"email@me.com\", authenticated=true, roles=\"ROLE_ADMIN\"))","authenticator":"AppBundle\Security\LoginFormAuthenticator"} []
security.DEBUG: Guard authenticator set success response. Redirect response
security.DEBUG: Remember me skipped: it is not configured for the firewall.
security.DEBUG: The "AppBundle\Security\LoginFormAuthenticator" authenticator set the response. Any later authenticator will not be called {"authenticator":"AppBundle\Security\LoginFormAuthenticator"} []
重定向后:
request.INFO: Matched route "app_admin". {"route":"app_admin" ..}
security.DEBUG: Checking for guard authentication credentials. {"firewall_key":"main","authenticators":1} []
security.DEBUG: Checking support on guard authenticator. {"firewall_key":"main","authenticator":"AppBundle\Security\LoginFormAuthenticator"} []
security.DEBUG: Guard authenticator does not support the request. {"firewall_key":"main","authenticator":"AppBundle\Security\LoginFormAuthenticator"} []
security.INFO: Populated the TokenStorage with an anonymous Token. [] []
security.DEBUG: Access denied, the user is not fully authenticated; redirecting to authentication entry point.
security.DEBUG: Calling Authentication entry point. [] []
您必须更改您的守卫验证器
AppBundle\Security\LoginFormAuthenticator
这向守卫说明您只需要在登录页面上检查凭据
public function supports(Request $request)
{
return 'login_route' === $request->attributes->get('_route') && $request->isMethod('POST');
}
我的同事发现了问题所在。其实上面的代码有很多问题。
- 使用 GuardAuthenticator 接口已从 sf4 中删除:
https://github.com/symfony/symfony/blob/4.4/UPGRADE-4.0.md#security
- logout_on_user_change 没有必要
- 不需要 LoginFormAuthenticator。
- stateless: true 是防火墙中的错误设置,但当我删除它时,它会抛出一个先前的错误:“无法刷新令牌,因为用户已更改。令牌在尝试刷新后被取消身份验证。”它的发生是因为
- 在 isEqualTo 我检查了
$this->salt !== $user->getSalt()
但它没有序列化
所以工作解决方案看起来像这样
- 路由相同
- 后端控制器相同
- LoginFormAuthentication.php 已删除
security.yml
security:
encoders:
AppBundle\Entity\User:
algorithm: bcrypt
cost: 12
providers:
user_provider:
entity:
class: AppBundle:User
property: email
firewalls:
dev:
pattern: ^/(_(profiler|wdt|error)|css|images|js)/
security: false
main:
anonymous: ~
provider: user_provider
form_login:
login_path: app_login
check_path: app_login
default_target_path: app_admin
logout:
path: app_logout
access_control:
- { path: ^/admin, roles: ROLE_ADMIN }
- { path: ^/, roles: IS_AUTHENTICATED_ANONYMOUSLY }
User.php
class User implements UserInterface, \Serializable, EquatableInterface
{
// ..
public function serialize()
{
return serialize(array(
$this->id,
$this->email,
$this->password,
$this->salt,
));
}
public function unserialize($serialized)
{
list (
$this->id,
$this->email,
$this->password,
$this->salt
) = unserialize($serialized, array('allowed_classes' => false));
}
public function isEqualTo(UserInterface $user)
{
if (!$user instanceof User) {
return false;
}
if ($user->getId() == $this->getId()) {
return true;
}
if ($this->password !== $user->getPassword()) {
return false;
}
if ($this->salt !== $user->getSalt()) {
return false;
}
if ($this->email !== $user->getUsername()) {
return false;
}
return true;
}
}
我有一个旧的 Symfony 3.1 站点,我升级到 Symfony 3。4.x 然后升级到 Symfony 4.4.11,但我没有将它升级到 symfony flex。我修复了很多问题,public 网站似乎正常工作。
我不得不重建身份验证,因为旧的与 sf4 不兼容。
我关注了这个 https://symfony.com/doc/4.4/security/form_login_setup.html
还有这个: https://symfonycasts.com/screencast/symfony-security/make-user
我遇到的情况是,在身份验证成功后,当它重定向到管理区域时,它总是再次检查 LoginFormAuthenticator,这显然不支持管理区域,并且它重定向回匿名用户的登录页面.
关于这个问题有很多讨论,我尝试了所有发现的方法,但我没有找到解决方案。即使调试它也没有。
会话保存在定义的路径中。它的 id 与浏览器中的 PHPSESSID 相同。 站点运行 HTTP 协议。
security.yml
security:
encoders:
AppBundle\Entity\User:
algorithm: bcrypt
cost: 12
providers:
user_provider:
entity:
class: AppBundle:User
property: email
firewalls:
dev:
pattern: ^/(_(profiler|wdt|error)|css|images|js)/
security: false
main:
stateless: true
pattern: ^/
anonymous: true
logout_on_user_change: true
guard:
authenticators:
- AppBundle\Security\LoginFormAuthenticator
form_login:
provider: user_provider
username_parameter: email
csrf_token_generator: security.csrf.token_manager
login_path: app_login
logout:
path: app_logout
access_control:
- { path: ^/admin, roles: ROLE_ADMIN }
- { path: ^/, roles: IS_AUTHENTICATED_ANONYMOUSLY }
路由:
app_login:
path: /login
defaults: { _controller: AppBundle\Controller\BackendController:loginAction }
app_logout:
path: /logout
defaults: { _controller: AppBundle\Controller\BackendController:logoutAction }
app_admin:
path: /admin/{page}/{entry}
defaults: { _controller: AppBundle\Controller\BackendController:showAction, entry: null }
User.php
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\User\EquatableInterface;
use Symfony\Component\Security\Core\User\UserInterface;
/**
* User
*
* @ORM\Table(name="user")
* @ORM\Entity(repositoryClass="AppBundle\Repository\UserRepository")
*/
class User implements UserInterface, \Serializable, EquatableInterface
{
private $id;
// and so on
public function serialize()
{
return serialize(array(
$this->id,
$this->email,
$this->password
));
}
public function unserialize($serialized)
{
list (
$this->id,
$this->email,
$this->password,
) = unserialize($serialized);
}
public function getRoles()
{
return array('ROLE_ADMIN');
}
public function getUsername()
{
return $this->getEmail();
}
public function isEqualTo(UserInterface $user)
{
if (!$user instanceof User) {
return false;
}
if ($this->password !== $user->getPassword()) {
return false;
}
if ($this->salt !== $user->getSalt()) {
return false;
}
if ($this->email !== $user->getUsername()) {
return false;
}
return true;
}
}
后端控制器:
class BackendController extends AbstractController
{
public function loginAction(AuthenticationUtils $authenticationUtils)
{
return $this->render('AppBundle:Backend:page.html.twig', array(
'email' => $authenticationUtils->getLastUsername(),
'error' => $authenticationUtils->getLastAuthenticationError()
));
}
public function logoutAction()
{
$this->container->get('security.token_storage')->setToken(null);
$this->container->get('request')->getSession()->invalidate();
}
public function showAction(Request $request, $page, $entry)
{
$this->denyAccessUnlessGranted('ROLE_ADMIN', null, 'Unable to access this page!');
// some logic
}
}
LoginFormAuthentication.php
在示例中看起来一样,并且有效。它成功到达 onAuthenticationSuccess() 并重定向到管理区域。
dev.log
request.INFO: Matched route "app_login". {"route":"app_login"..}
security.DEBUG: Checking for guard authentication credentials. {"firewall_key":"main","authenticators":1} []
security.DEBUG: Checking support on guard authenticator. {"firewall_key":"main","authenticator":"AppBundle\Security\LoginFormAuthenticator"} []
security.DEBUG: Calling getCredentials() on guard authenticator. {"firewall_key":"main","authenticator":"AppBundle\Security\LoginFormAuthenticator"} []
security.DEBUG: Passing guard token information to the GuardAuthenticationProvider {"firewall_key":"main","authenticator":"AppBundle\Security\LoginFormAuthenticator"} []
doctrine.DEBUG: SELECT t0.* FROM user t0 WHERE t0.email = ? LIMIT 1 ["email@me.com"] []
security.INFO: Guard authentication successful! {"token":"[object] (Symfony\Component\Security\Guard\Token\PostAuthenticationGuardToken: PostAuthenticationGuardToken(user=\"email@me.com\", authenticated=true, roles=\"ROLE_ADMIN\"))","authenticator":"AppBundle\Security\LoginFormAuthenticator"} []
security.DEBUG: Guard authenticator set success response. Redirect response
security.DEBUG: Remember me skipped: it is not configured for the firewall.
security.DEBUG: The "AppBundle\Security\LoginFormAuthenticator" authenticator set the response. Any later authenticator will not be called {"authenticator":"AppBundle\Security\LoginFormAuthenticator"} []
重定向后:
request.INFO: Matched route "app_admin". {"route":"app_admin" ..}
security.DEBUG: Checking for guard authentication credentials. {"firewall_key":"main","authenticators":1} []
security.DEBUG: Checking support on guard authenticator. {"firewall_key":"main","authenticator":"AppBundle\Security\LoginFormAuthenticator"} []
security.DEBUG: Guard authenticator does not support the request. {"firewall_key":"main","authenticator":"AppBundle\Security\LoginFormAuthenticator"} []
security.INFO: Populated the TokenStorage with an anonymous Token. [] []
security.DEBUG: Access denied, the user is not fully authenticated; redirecting to authentication entry point.
security.DEBUG: Calling Authentication entry point. [] []
您必须更改您的守卫验证器 AppBundle\Security\LoginFormAuthenticator
这向守卫说明您只需要在登录页面上检查凭据
public function supports(Request $request)
{
return 'login_route' === $request->attributes->get('_route') && $request->isMethod('POST');
}
我的同事发现了问题所在。其实上面的代码有很多问题。
- 使用 GuardAuthenticator 接口已从 sf4 中删除: https://github.com/symfony/symfony/blob/4.4/UPGRADE-4.0.md#security
- logout_on_user_change 没有必要
- 不需要 LoginFormAuthenticator。
- stateless: true 是防火墙中的错误设置,但当我删除它时,它会抛出一个先前的错误:“无法刷新令牌,因为用户已更改。令牌在尝试刷新后被取消身份验证。”它的发生是因为
- 在 isEqualTo 我检查了
$this->salt !== $user->getSalt()
但它没有序列化
所以工作解决方案看起来像这样
- 路由相同
- 后端控制器相同
- LoginFormAuthentication.php 已删除
security.yml
security:
encoders:
AppBundle\Entity\User:
algorithm: bcrypt
cost: 12
providers:
user_provider:
entity:
class: AppBundle:User
property: email
firewalls:
dev:
pattern: ^/(_(profiler|wdt|error)|css|images|js)/
security: false
main:
anonymous: ~
provider: user_provider
form_login:
login_path: app_login
check_path: app_login
default_target_path: app_admin
logout:
path: app_logout
access_control:
- { path: ^/admin, roles: ROLE_ADMIN }
- { path: ^/, roles: IS_AUTHENTICATED_ANONYMOUSLY }
User.php
class User implements UserInterface, \Serializable, EquatableInterface
{
// ..
public function serialize()
{
return serialize(array(
$this->id,
$this->email,
$this->password,
$this->salt,
));
}
public function unserialize($serialized)
{
list (
$this->id,
$this->email,
$this->password,
$this->salt
) = unserialize($serialized, array('allowed_classes' => false));
}
public function isEqualTo(UserInterface $user)
{
if (!$user instanceof User) {
return false;
}
if ($user->getId() == $this->getId()) {
return true;
}
if ($this->password !== $user->getPassword()) {
return false;
}
if ($this->salt !== $user->getSalt()) {
return false;
}
if ($this->email !== $user->getUsername()) {
return false;
}
return true;
}
}