Форма входа в Symfony 4: успешная аутентификация, но аутентификация сразу теряется после перенаправления

Я создал форму входа после этого документ настройки входа в форму.

Это отлично работает на локальном хосте но не на рабочем сервере.

И на localhost, и на prod аутентификация начинается успешно

  1. Проверка подлинности Guard прошла успешно
  2. Защитный аутентификатор установил успешный ответ
  3. Сохранил токен безопасности в сеансе
  4. Соответствующий маршрут "easyadmin

    ### var/log/prod.log output with info level
    [2019-07-05 10:28:46] request.INFO: Matched route "app_login". {"route":"app_login","route_parameters":{"_route":"app_login","_controller":"App\\Controller\\SecurityController::login"},"request_uri":"https://example.com/login","method":"POST"} []
    [2019-07-05 10:28:46] security.DEBUG: Checking for guard authentication credentials. {"firewall_key":"main","authenticators":1} []
    [2019-07-05 10:28:46] security.DEBUG: Checking support on guard authenticator. {"firewall_key":"main","authenticator":"App\\Security\\LoginFormAuthenticator"} []
    [2019-07-05 10:28:46] security.DEBUG: Calling getCredentials() on guard authenticator. {"firewall_key":"main","authenticator":"App\\Security\\LoginFormAuthenticator"} []
    [2019-07-05 10:28:46] security.DEBUG: Passing guard token information to the GuardAuthenticationProvider {"firewall_key":"main","authenticator":"App\\Security\\LoginFormAuthenticator"} []
    [2019-07-05 10:28:46] php.INFO: User Deprecated: The "Symfony\Component\Security\Core\Encoder\BCryptPasswordEncoder" class is deprecated since Symfony 4.3, use "Symfony\Component\Security\Core\Encoder\NativePasswordEncoder" instead. {"exception":"[object] (ErrorException(code: 0): User Deprecated: The \"Symfony\\Component\\Security\\Core\\Encoder\\BCryptPasswordEncoder\" class is deprecated since Symfony 4.3, use \"Symfony\\Component\\Security\\Core\\Encoder\\NativePasswordEncoder\" instead. at /var/www/clients/client0/web4/web/vendor/symfony/security-core/Encoder/BCryptPasswordEncoder.php:14)"} []
    
    [2019-07-05 10:28:46] security.INFO: Guard authentication successful! {"token":"[object] (Symfony\\Component\\Security\\Guard\\Token\\PostAuthenticationGuardToken: PostAuthenticationGuardToken(user=\"[email protected]\", authenticated=true, roles=\"ROLE_EDITOR, ROLE_USER\"))","authenticator":"App\\Security\\LoginFormAuthenticator"} []
    
    [2019-07-05 10:28:46] security.DEBUG: Guard authenticator set success response. {"response":"[object] (Symfony\\Component\\HttpFoundation\\RedirectResponse: HTTP/1.0 302 Found\r\nCache-Control: no-cache, private\r\nDate:          Fri, 05 Jul 2019 10:28:46 GMT\r\nLocation:      /backoffice\r\n\r\n<!DOCTYPE html>\n<html>\n    <head>\n        <meta charset=\"UTF-8\" />\n        <meta http-equiv=\"refresh\" content=\"0;url=/backoffice\" />\n\n        <title>Redirecting to /backoffice</title>\n    </head>\n    <body>\n        Redirecting to <a href=\"/backoffice\">/backoffice</a>.\n    </body>\n</html>)","authenticator":"App\\Security\\LoginFormAuthenticator"} []
    
    [2019-07-05 10:28:46] security.DEBUG: Remember me skipped: it is not configured for the firewall. {"authenticator":"App\\Security\\LoginFormAuthenticator"} []
    [2019-07-05 10:28:46] security.DEBUG: The "App\Security\LoginFormAuthenticator" authenticator set the response. Any later authenticator will not be called {"authenticator":"App\\Security\\LoginFormAuthenticator"} []
    [2019-07-05 10:28:46] security.DEBUG: Stored the security token in the session. {"key":"_security_main"} []
    
    [2019-07-05 10:28:46] request.INFO: Matched route "easyadmin". {"route":"easyadmin","route_parameters":{"_controller":"Symfony\\Bundle\\FrameworkBundle\\Controller\\RedirectController::urlRedirectAction","path":"/backoffice/","permanent":true,"scheme":null,"httpPort":80,"httpsPort":443,"_route":"easyadmin"},"request_uri":"https://example.com/backoffice","method":"GET"} []
    

Но находясь на локальном хосте, меня корректно перенаправляют в бэкофис:

  • Чтение существующего токена безопасности из сеанса
  • Пользователь был перезагружен из поставщика пользователей

    ### var/log/prod.log (following lines, localhost) 
    [2019-07-05 10:19:29] security.DEBUG: Read existing security token from the session. {"key":"_security_main","token_class":"Symfony\\Component\\Security\\Guard\\Token\\PostAuthenticationGuardToken"} []
    [2019-07-05 10:19:29] security.DEBUG: User was reloaded from a user provider. {"provider":"Symfony\\Bridge\\Doctrine\\Security\\User\\EntityUserProvider","username":"[email protected]"} []
    [2019-07-05 10:19:29] security.DEBUG: Checking for guard authentication credentials. {"firewall_key":"main","authenticators":1} []
    [2019-07-05 10:19:29] security.DEBUG: Checking support on guard authenticator. {"firewall_key":"main","authenticator":"App\\Security\\LoginFormAuthenticator"} []
    [2019-07-05 10:19:29] security.DEBUG: Guard authenticator does not support the request. {"firewall_key":"main","authenticator":"App\\Security\\LoginFormAuthenticator"} []
    [2019-07-05 10:19:29] cache.INFO: Lock acquired, now computing item "easyadmin.processed_config" {"key":"easyadmin.processed_config"} []
    

Вместо этого в рабочей среде:

  • он пропускает шаг: чтение существующего токена безопасности
  • не обновить пользователя, как и ожидалось
  • вместо этого он заполняет TokenStorage анонимным токеном
  • Отказано в доступе и вернуться к URL-адресу входа

    ### var/log/prod.log (same following lines, but from production server) 
    [2019-07-05 10:28:46] security.DEBUG: Checking for guard authentication credentials. {"firewall_key":"main","authenticators":1} []
    [2019-07-05 10:28:46] security.DEBUG: Checking support on guard authenticator. {"firewall_key":"main","authenticator":"App\\Security\\LoginFormAuthenticator"} []
    [2019-07-05 10:28:46] security.DEBUG: Guard authenticator does not support the request. {"firewall_key":"main","authenticator":"App\\Security\\LoginFormAuthenticator"} []
    [2019-07-05 10:28:46] security.INFO: Populated the TokenStorage with an anonymous Token. [] []
    [2019-07-05 10:28:46] security.DEBUG: Access denied, the user is not fully authenticated; redirecting to authentication entry point. {"exception":"[object] (Symfony\\Component\\Security\\Core\\Exception\\AccessDeniedException(code: 403): Access Denied. at /var/www/clients/client0/web4/web/vendor/symfony/security-http/Firewall/AccessListener.php:72)"} []
    [2019-07-05 10:28:46] security.DEBUG: Calling Authentication entry point. [] []
    [2019-07-05 10:28:46] request.INFO: Matched route "app_login". {"route":"app_login","route_parameters":{"_route":"app_login","_controller":"App\\Controller\\SecurityController::login"},"request_uri":"https://example.com/login","method":"GET"} []
    

безопасность.yaml

security:
    encoders:
        App\Entity\User:
            algorithm: bcrypt
    providers:
        app_user_provider:
            entity:
                class: App\Entity\User
                property: email
    firewalls:
        dev:
            pattern: ^/(_(profiler|wdt)|css|images|js)/
            security: false
        main:
            anonymous: true
            guard:
                authenticators:
                    - App\Security\LoginFormAuthenticator
            logout:
                path: app_logout
    access_control:
        - { path: ^/backoffice, roles: ROLE_EDITOR} # requires_channel: https

маршруты.yaml

admin:
  path: /backoffice
  controller: EasyCorp\Bundle\EasyAdminBundle\Controller\EasyAdminController

LoginFormAuthenticator

// use...

class LoginFormAuthenticator extends AbstractFormLoginAuthenticator
{
    use TargetPathTrait;

    private $entityManager;
    private $urlGenerator;
    private $csrfTokenManager;
    private $passwordEncoder;

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

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

    public function getCredentials(Request $request)
    {
        $credentials = [
            'email' => $request->request->get('email'),
            'password' => $request->request->get('password'),
            'csrf_token' => $request->request->get('_csrf_token'),
        ];
        $request->getSession()->set(
            Security::LAST_USERNAME,
            $credentials['email']
        );

        return $credentials;
    }

    public function getUser($credentials, UserProviderInterface $userProvider)
    {
        $token = new CsrfToken('authenticate', $credentials['csrf_token']);
        if (!$this->csrfTokenManager->isTokenValid($token)) {
            throw new InvalidCsrfTokenException();
        }

        $user = $this->entityManager->getRepository(User::class)->findOneBy(['email' => $credentials['email']]);

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

        return $user;
    }

    public function checkCredentials($credentials, UserInterface $user)
    {
        return $this->passwordEncoder->isPasswordValid($user, $credentials['password']);
    }

    public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
    {
        if ($targetPath = $this->getTargetPath($request->getSession(), $providerKey)) {
            return new RedirectResponse($targetPath);
        }
        return new RedirectResponse($this->urlGenerator->generate('admin'));
    }

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

Контроллер безопасности

// use...

class SecurityController extends AbstractController
{
    /**
     * @Route("/login", name = "app_login")
     */
    public function login(AuthenticationUtils $authenticationUtils): Response
    {
        // get the login error if there is one
        $error = $authenticationUtils->getLastAuthenticationError();
        // last username entered by the user
        $lastUsername = $authenticationUtils->getLastUsername();

        return $this->render(
          'security/login.html.twig',
          [
            'last_username' => $lastUsername,
            'error' => $error,
          ]
        );
    }

    /**
     * @Route("/logout", name = "app_logout")
     * @return \Symfony\Component\HttpFoundation\RedirectResponse
     */
    public function logout()
    {
        return $this->redirectToRoute('home');
    }
}
//... skipped forgottenPassword and resetPassword methods

Обновлено:

php bin/console debug:config security вывод

Current configuration for extension with alias "security"
=========================================================

security:
encoders:
    App\Entity\User:
        algorithm: bcrypt
        hash_algorithm: sha512
        key_length: 40
        ignore_case: false
        encode_as_base64: true
        iterations: 5000
        cost: null
        memory_cost: null
        time_cost: null
        threads: null
providers:
    app_user_provider:
        entity:
            class: App\Entity\User
            property: email
            manager_name: null
firewalls:
    dev:
        pattern: ^/(_(profiler|wdt)|css|images|js)/
        security: false
        methods: {  }
        user_checker: security.user_checker
        stateless: false
        logout_on_user_change: true
    main:
        anonymous:
            secret: null
        guard:
            authenticators:
                - App\Security\LoginFormAuthenticator
            entry_point: null
        logout:
            path: app_logout
            csrf_parameter: _csrf_token
            csrf_token_id: logout
            target: /
            invalidate_session: true
            delete_cookies: {  }
            handlers: {  }
        methods: {  }
        security: true
        user_checker: security.user_checker
        stateless: false
        logout_on_user_change: true
access_control:
    -
        path: ^/backoffice
        roles:
            - ROLE_EDITOR
        requires_channel: null
        host: null
        port: null
        ips: {  }
        methods: {  }
        allow_if: null
access_decision_manager:
    strategy: affirmative
    allow_if_all_abstain: false
    allow_if_equal_granted_denied: true
access_denied_url: null
session_fixation_strategy: migrate
hide_user_not_found: true
always_authenticate_before_granting: false
erase_credentials: true
role_hierarchy: {  }

РЕДАКТИРОВАТЬ 2

Как прокомментировал @Arno, я отредактировал framework.yaml, чтобы сохранить сеансы в каталоге var/, и я могу проверить, что этот шаг работает без проблем с разрешениями, каждый раз, когда я нажимаю на форму входа, записывается файл sess_.

Стоит сказать, что если я прокомментирую:

access_control:
    - { path: ^/odelices_admin, roles: ROLE_USER}

Я могу получить доступ к бэк-офису.

РЕДАКТИРОВАТЬ 3: поведение сеанса

Итак, теперь сеансы сохраняются в var/sessions/prod.

  1. Я чищу директорию : sudo rm -r var/sessions/prod/sess_*
  2. Я открываю Chrome и URL-адрес, он устанавливает файл cookie PHPSSID с тем же значением, что и первый файл sess_xyz:

    _sf2_attributes|a:2:{s:19:"_csrf/https-contact";s:43:"Oq-QpN21bI_BUDcVbv0ocyrYsTzQo3aJr80QAk2AR7w";s:19:"_csrf/https-booking";s:43:"z_L4TG7Wg0jydwl5VabfJMx0NBhQgeasuAiqxksLvD8";}_sf2_meta|a:3:{s:1:"u";i:1562668584;s:1:"c";i:1562668584;s:1:"l";s:1:"0";}
    
  3. Я захожу на страницу входа. Новое значение PHPSSID, связанное с новым файлом sess_xyz:

    _sf2_attributes|a:1:{s:24:"_csrf/https-authenticate";s:43:"erWMU-irtptcZodr8UOjFtxiuyE23LbAeFHRnXgcNdc";}_sf2_meta|a:3:{s:1:"u";i:1562668662;s:1:"c";i:1562668662;s:1:"l";s:1:"0";}
    
  4. Я вхожу с правильными значениями. Это создает 3 новых файла ssid_xyz.

    # 1st one shows user logged in with correct roles and so on
    _sf2_attributes|a:3:{s:24:"_csrf/https-authenticate";s:43:"erWMU-irtptcZodr8UOjFtxiuyE23LbAeFHRnXgcNdc";s:23:"_security.last_username";s:21:"[email protected]";s:14:"_security_main";s:799:"C:67:"Symfony\Component\Security\Guard\Token\PostAuthenticationGuardToken":718:{a:2:{i:0;s:4:"main";i:1;a:5:{i:0;O:15:"App\Entity\User":6:{s:19:"^@App\Entity\User^@id";i:1;s:22:"^@App\Entity\User^@email";s:21:"[email protected]";s:22:"^@App\Entity\User^@roles";a:1:{i:0;s:11:"ROLE_EDITOR";}s:25:"^@App\Entity\User^@password";s:60:"$2y$13$cXaR7Ss.kTH1U.T/Rzi6m.ALsKwWCLDcO5/OIeRDAq02iylmf4us6";s:21:"^@App\Entity\User^@name";s:7:"Thierry";s:13:"^@*^@resetToken";N;}i:1;b:1;i:2;a:2:{i:0;O:41:"Symfony\Component\Security\Core\Role\Role":1:{s:47:"^@Symfony\Component\Security\Core\Role\Role^@role";s:11:"ROLE_EDITOR";}i:1;O:41:"Symfony\Component\Security\Core\Role\Role":1:{s:47:"^@Symfony\Component\Security\Core\Role\Role^@role";s:9:"ROLE_USER";}}i:3;a:0:{}i:4;a:2:{i:0;s:11:"ROLE_EDITOR";i:1;s:9:"ROLE_USER";}}}}";}_sf2_meta|a:3:{s:1:"u";i:1562668713;s:1:"c";i:1562668713;s:1:"l";s:1:"0";}
    
    # 2nd one ...is empty
    
    # 3rd one refers to backoffice url
    _sf2_attributes|a:1:{s:26:"_security.main.target_path";s:42:"https://mywebsite.com/backoffice";}_sf2_meta|a:3:{s:1:"u";i:1562668713;s:1:"c";i:1562668713;s:1:"l";s:1:"0";}
    
    # last one is similar to point 3, before logging, only ssid value differs, and a corresponding cookie is set on Chrome
    _sf2_attributes|a:1:{s:24:"_csrf/https-authenticate";s:43:"3UC5dCRrahc2qhdZ167Jg4HKTJCexf8PFlefibTVpYk";}_sf2_meta|a:3:{s:1:"u";i:1562668713;s:1:"c";i:1562668713;s:1:"l";s:1:"0";}
    

РЕДАКТИРОВАТЬ 4: Объект пользователя

namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;
// use Symfony\Component\Security\Core\User\EquatableInterface;
use Symfony\Component\Security\Core\User\UserInterface;

/**
 * @ORM\Entity(repositoryClass = "App\Repository\UserRepository")
 */
class User implements UserInterface # , EquatableInterface
{
    /**
     * @ORM\Id()
     * @ORM\GeneratedValue()
     * @ORM\Column(type = "integer")
     */
    private $id;

    /**
     * @ORM\Column(type = "string", length=180, unique=true)
     */
    private $email;

    /**
     * @ORM\Column(type = "json")
     */
    private $roles = [];

    /**
     * @var string The hashed password
     * @ORM\Column(type = "string")
     */
    private $password;

    /**
     * @ORM\Column(type = "string", length=255)
     */
    private $name;

    /**
     * @var string le token qui servira lors de l'oubli de mot de passe
     * @ORM\Column(type = "string", length=255, nullable=true)
     */
    protected $resetToken;

  /*public function __construct($username, $password, array $roles)
  {
    $this->username = $username;
    $this->password = $password;
    $this->roles = $roles;
  }*/


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

    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 (string) $this->email;
    }

    /**
     * @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()
    {
        // not needed when using the "bcrypt" algorithm in security.yaml
    }

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

    public function getName(): ?string
    {
        return $this->name;
    }

    public function setName(string $name): self
    {
        $this->name = $name;

        return $this;
    }

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

    /**
     * @param string $resetToken
     */
    public function setResetToken(?string $resetToken): void
    {
      $this->resetToken = $resetToken;
    }

    public function __toString() {
      return $this->getName() ;
    }

/*    public function isEqualTo(UserInterface $user)
    {

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


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

      return true;
    }*/
}

Куча

Debian Stretch, Nginx + Лак: Nginx обрабатывает 443 запроса, передает их в Varnish в качестве кеш-прокси, который доставляет кэшированные объекты или передает запросы на серверную часть nginx через порт 8083. Это работает как шарм для другого приложения с аналогичной логикой входа (единственное отличие состоит в том, что глючное перенаправляет на easyadmin вместо пользовательского администратора), поэтому я не думаю, что это связано со стеком.

виртуальный хост

server { # this block only redirects www to non www
        listen aaa.bbb.ccc.ddd:443 ssl;
        server_name www.somewebsite.com;

        ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
        ssl_certificate /var/www/clients/client0/web4/ssl/somewebsite.com-le.crt;
        ssl_certificate_key /var/www/clients/client0/web4/ssl/somewebsite.com-le.key;

        return 301 https://somewebsite.com$request_uri;
}

server { # this block redirects ssl requests to Varnish
        listen aaa.bbb.ccc.ddd:443 ssl;
        server_name somewebsite.com;

        ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
        ssl_certificate /var/www/clients/client0/web4/ssl/somewebsite.com-le.crt;
        ssl_certificate_key /var/www/clients/client0/web4/ssl/somewebsite.com-le.key;

        location / {
            # Pass the request on to Varnish.
            proxy_pass  http://127.0.0.1;

            # Pass some headers to the downstream server, so it can identify the host.
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

            # Tell any web apps that the session is HTTPS.
            proxy_set_header X-Forwarded-Proto https;
            proxy_redirect     off;
        }
}

server { # now sent to backend 
        listen aaa.bbb.ccc.ddd:8083;
        server_name somewebsite.com;
        root   /var/www/somewebsite.com/web/public;

        location / {
            try_files $uri /index.php$is_args$args;
       }
       location ~ ^/index\.php(/|$) {
            fastcgi_pass 127.0.0.1:8998;

            fastcgi_split_path_info ^(.+\.php)(/.*)$;
            include fastcgi_params;

            fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
            fastcgi_param DOCUMENT_ROOT $realpath_root;

            internal;
        }
        location ~ \.php$ {
            return 404;
        }

        error_log /var/log/ispconfig/httpd/somewebsite.com/error.log;
        access_log /var/log/ispconfig/httpd/somewebsite.com/access.log combined;

        location ~ /\. {
                        deny all;
        }
        location ^~ /.well-known/acme-challenge/ {
                        access_log off;
                        log_not_found off;
                        root /usr/local/ispconfig/interface/acme/;
                        autoindex off;
                        try_files $uri $uri/ =404;
        }
        location = /favicon.ico {
            log_not_found off;
            access_log off;
            expires max;
        }
        location = /robots.txt {
            allow all;
            log_not_found off;
            access_log off;
        }
}

Может ли это быть связано с разрешениями на какой-то каталог? HTTPS? EasyAdmin? Как я могу убедиться, что токен безопасности был сохранен в сеансе, даже если он зарегистрирован как сохраненный? Я также попытался изменить access_control на роль ROLE_USER, чтобы любой пользователь, прошедший проверку подлинности, имел доступ. Ни за что.

Любая помощь очень ценится.

Можете ли вы показать, как использовать вашу конфигурацию с помощью php bin/console debug:config security

Dylan Kas 05.07.2019 14:11

Вы чистили кеш на сервере после загрузки?

mutantkeyboard 05.07.2019 14:13

Только что опубликовал php bin/console debug:config security вывод. Да, я ищу проблему со вчерашнего вечера и очищал кеш около 100 раз, проверял проблемы с разрешениями...

Kojo 05.07.2019 14:16

Используете ли вы настройки по умолчанию для сохранения сеансов (настроенные в framework.session)? Если это так, сеансы сохраняются в виде файлов, и это определенно может быть проблемой с правами доступа. По умолчанию они должны быть сохранены в папке «sessions» в вашем каталоге кеша, но вы можете изменить это, как описано здесь: symfony.com/doc/3.4/session/sessions_directory.html. Может быть, вы можете попробовать дать каталогу права 777. Он должен создавать один файл на сеанс в формате «sess_<id>» с сериализованным пользователем внутри.

Arno Hilke 09.07.2019 00:04

@ArnoHilke Большое спасибо за эту подсказку. Ну, я переключился на 4.3 документ, затем я изменил свою настройку, чтобы явно сохранять сеансы в var dir, и пока сеанс записан там, так что, к сожалению, это не точка блокировки :((

Kojo 09.07.2019 10:27

Как насчет того факта, что ssid получает новое значение каждый раз, когда я нажимаю URL-адрес (страница входа или нет) ?? Это нормальное поведение???

Kojo 09.07.2019 10:41

@Kojo Идентификатор сеанса должен меняться каждый раз, когда изменяется ваш уровень аутентификации, то есть при входе и выходе из системы, но не при обычных запросах. Одной из причин может быть то, что ваш пользователь изменился или был обнаружен как измененный, например. из-за ошибочной (де)сериализации. Вы также можете проверить 1) создается ли правильный сеанс при входе в систему (проверьте содержимое файла для пользователя с правильным адресом электронной почты и ролью) и 2) правильно ли установлен файл cookie и отправлен ли он с этим идентификатором сеанса на вашем клиенте. .

Arno Hilke 09.07.2019 12:02

@ArnoHilke еще раз спасибо! Я отредактировал пост со всем поведением. Пожалуйста, посмотрите, да, файлы cookie установлены в отношении файлов ssid, но всякий раз, когда выполняется перенаправление, вернуться к началу ... Мне нужно посетить несколько собраний команды сегодня днем, я вернусь после ... Я действительно не знаю что думать :-((

Kojo 09.07.2019 13:04

мы можем увидеть вашу сущность пользователя, пожалуйста?

Yoann MIR 09.07.2019 15:59

Также убедитесь, что в вашем брандмауэре нет опции stateless: true. Это заблокирует запуск сеанса для пользователя, и все останутся анонимными после успешной аутентификации.

Alexander 03.07.2020 15:40
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Symfony Station Communiqué - 7 июля 2023 г
Symfony Station Communiqué - 7 июля 2023 г
Это коммюнике первоначально появилось на Symfony Station .
Symfony Station Communiqué - 17 февраля 2023 г
Symfony Station Communiqué - 17 февраля 2023 г
Это коммюнике первоначально появилось на Symfony Station , вашем источнике передовых новостей Symfony, PHP и кибербезопасности.
Управление ответами api для исключений на Symfony с помощью KernelEvents
Управление ответами api для исключений на Symfony с помощью KernelEvents
Много раз при создании api нам нужно возвращать клиентам разные ответы в зависимости от возникшего исключения.
12
10
13 450
5
Перейти к ответу Данный вопрос помечен как решенный

Ответы 5

Случилось со мной год назад, аутентификация прошла успешно, перенаправление и вход в систему как анонимный. Заставь меня хотеть биться головой о стены. Проблема, с которой я столкнулся тогда, заключалась в том, что я создал пользователя по старому курсу из KnpUniversity, и он не реализовывал EquatableInterface и метод isEqualTo. Надеюсь, это сработает для вас.

Убедитесь, что ваша пользовательская сущность реализует EquatableInterface

namespace App\Entity;

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

    class User implements UserInterface, EquatableInterface
{
    private $username;
    private $password;
    private $salt;
    private $roles;

    public function __construct($username, $password, $salt, array $roles)
    {
        $this->username = $username;
        $this->password = $password;
        $this->salt = $salt;
        $this->roles = $roles;
    }

    // your setters, getters and whatever ...

    public function isEqualTo(UserInterface $user)
    {
        if (!$user instanceof WebserviceUser) {
            return false;
        }

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

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

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

        return true;
    }
}

Спасибо, Йоанн. Это не меняет поведения. :-(( Я собираюсь опубликовать User Entity

Kojo 09.07.2019 20:13

Вам может быть интересно: я сделал эту проверку, пытаясь реализовать EquatableInterface if ($this->email !== $user->getUsername()), потому что getUsername возвращает адрес электронной почты, установленный в провайдере security.yaml... это работает в localhost, а не в prod

Kojo 09.07.2019 20:19

это странно и может быть связано с вашей проблемой

Yoann MIR 09.07.2019 22:09

Насколько я понимаю, по умолчанию пользователь обновляется, сравнивая последний сериализованный с текущим, используя getSalt(), getPassword() и getUsername() из объекта пользователя. getUsername — это нетgetName(), он должен возвращать уникальное значение, и вы определяете, какое именно, в свойстве объекта security.yaml -> provider. ты не согласен? Ваша реализация действительно отлично работает с моей конфигурацией, но не было причин добавлять эту проверку в мою столь базовую конфигурацию. В любом случае, благодаря вам я узнал отличный новый способ настройки этого шага!

Kojo 10.07.2019 15:21

Это заканчивается тем, что установка Varnish была ошибочной.

Я совсем забыл, что я должен указать любой паттерн backoffice типа /admin/ , /backoffice/ и еще не кэшируется прокси, а вместо этого напрямую передается на серверную часть.

sub vcl_recv {
    # ...
    if (req.url ~ "^/status\.php$" ||
        req.url ~ "^/update\.php$" ||
        req.url ~ "^/install\.php" ||
        req.url ~ "^/admin$" ||
        req.url ~ "^/admin/.*$" ||
        req.url ~ "^/flag/.*$" ||
        req.url ~ "^.*/ajax/.*$" ||
        req.url ~ "^.*/ahah/.*$" ||
        req.url ~ "^/info/.*$" ||
        req.url ~ "^/system/files/.*$" ||
        req.url ~ "^/user" ||
        req.url ~ "^/users/.*$" ||
        req.url ~ "^/user/.*$" ) {

       return (pass);
    }
    # ...

Это уже было настроено для другого приложения Symfony, о котором я упоминал в вопросе, и нескольких веб-сайтов Drupal. По крайней мере, это заставило меня глубоко покопаться в Процесс аутентификации пользователя Symfony 4, и как его отладить! Может быть, этот отладочный пост шаг за шагом поможет дальнейшим читателям?!

Ответ принят как подходящий

Итак, вот мои комментарии в более структурированном виде, чтобы они могли помочь кому-то еще, у кого проблемы с аутентификацией в Symfony.

Убедитесь, что сеансы сохранены

По умолчанию каждый сеанс сохраняется в виде файла с именем sess_<id> в <project_dir>/var/cache/<env>/sessions или в соответствии с определением save_path в вашем php.ini, если для framework.session.handler установлено значение null. Явно настройте каталог сеанса и убедитесь, что файл сеанса создается при входе в систему. Если нет, проверьте разрешения для этой папки.

# app/config/config.yml (Symfony 3)
# config/packages/framework.yaml (Symfony 4)
framework:
    session:
        handler_id: 'session.handler.native_file'
        save_path: '%kernel.project_dir%/var/sessions/%kernel.environment%'

См. https://symfony.com/doc/current/session.html#configuration

Убедитесь, что сеансы правильные и используются

При входе в систему должна быть создана сессия с новым идентификатором. Проверьте содержимое файла. Он должен содержать вашего сериализованного пользователя под именем брандмауэра (например, main), включая ваш идентификатор (например, адрес электронной почты) и ваши роли пользователя (например, ROLE_USER). Проблема здесь может быть вызвана ошибочной аутентификацией, конфигурацией безопасности или сериализацией.

_sf2_attributes|a:3:{s:18:"_csrf/authenticate";s:43:"n2oap401u4P4O7m_IhPODZ6Bz7EHl-DDsHxBEl-fhxc";s:23:"_security.last_username";s:10:"[email protected]";s:14:"_security_main";s:545:"C:67:"Symfony\Component\Security\Guard\Token\PostAuthenticationGuardToken":464:{a:2:{i:0;s:4:"main";i:1;a:5:{i:0;O:15:"App\Entity\User":4:{s:19:"App\Entity\Userid";i:1;s:22:"App\Entity\Useremail";s:10:"[email protected]";s:22:"App\Entity\Userroles";a:0:{}s:25:"App\Entity\Userpassword";s:60:"$2y$13$qwbtasafa58lPonX6B5a9eV4lziF7EZWP8NFLAe3blpCJVhQgPVOS";}i:1;b:1;i:2;a:1:{i:0;O:41:"Symfony\Component\Security\Core\Role\Role":1:{s:47:"Symfony\Component\Security\Core\Role\Rolerole";s:9:"ROLE_USER";}}i:3;a:0:{}i:4;a:1:{i:0;s:9:"ROLE_USER";}}}}";}_sf2_meta|a:3:{s:1:"u";i:1563015142;s:1:"c";i:1563015142;s:1:"l";s:1:"0";}

Убедитесь, что файл cookie с таким же идентификатором установлен в вашем браузере при входе в систему. Имя файла cookie определяется session.name в вашем php.ini, по умолчанию это PHPSESSID. Его следует отправлять с каждым вашим запросом (например, Cookie: PHPSESSID=lpcf79ff8jdv2iigsgvepnr9bb). Если правильный сеанс существует, но в вашем браузере есть другой файл cookie, возможно, вы сразу же вышли из системы после успешного перенаправления.

Убедитесь, что пользователь обновлен правильно

Идентификатор сеанса должен меняться только при изменении вашего пользователя (например, при входе в систему и выходе из нее). Если он меняется после обычных запросов (например, вы немедленно выходите из системы) или ваша сессия кажется проигнорированной, проблема может заключаться в том, что Symfony считает, что ваш пользователь изменился. Это может быть вызвано ошибочной (де)сериализацией или сравнением.

По умолчанию Symfony использует сериализованные результаты getPassword(), getUsername() и getSalt() из сеанса для сравнения с пользователем, предоставленным провайдером пользователя (например, базой данных). Если какое-либо из этих значений изменится, вы выйдете из системы (см. https://symfony.com/doc/current/security/user_provider.html#understanding-how-users-refreshed-from-the-session).

Таким образом, вы должны убедиться, что пользователь, предоставленный, например. ваша база данных верна и соответствует десериализованному пользователю из сеанса. Убедитесь, что соответствующие поля правильно сериализованы. Если вы реализуете интерфейс Serializable, убедитесь, что ваш метод serialize() соответствует вашему unserialize(). Если вы реализуете EquatableInterface, убедитесь, что ваш метод isEqualTo() работает правильно. Оба эти интерфейса являются необязательными, поэтому вы можете удалить их в целях отладки.

У меня сработали "isEqualTo()" и методы сериализации, спасибо!

Laurent 02.10.2019 16:36

Меня удивило, что hasUserChanged in Symfony\Component\Security\Core\Authentication\Token\Abstrac‌​tToken вызывается во время обновления. Это сработало для меня, внедрив EquatableInterface в мою сущность User и определив isEqualTo.

Julian 29.12.2020 13:34

@Julian Как упоминалось в документы, на который я также ссылался выше, они говорят, что это из соображений безопасности, чтобы убедиться, что десериализованный пользователь по-прежнему соответствует только что запрошенному пользователю. В некоторых более старых документы они также заявляют, что теоретически было бы достаточно просто сохранить идентификатор в сеансе и использовать его для запроса пользователя, поэтому я не совсем уверен, почему это было сделано именно так.

Arno Hilke 30.12.2020 16:15

У меня был тот же симптом, который был описан OP, но в приложении Symfony 5.1 и с использованием моего собственного существующего объекта User. Оказывается, проблема в том, что я не реализовал \Serializable в своей сущности пользователя и двух необходимых методах:

public function serialize()
{
    return serialize(array(
        $this->id,
        $this->username,
        $this->password,
        // see section on salt below
        // $this->salt,
    ));
}

/** @see \Serializable::unserialize() */
public function unserialize($serialized)
{
    list (
        $this->id,
        $this->username,
        $this->password,
        // see section on salt below
        // $this->salt
    ) = unserialize($serialized, array('allowed_classes' => false));
}

Как только это было исправлено, приложение могло оставаться в системе как в среде разработки, так и в рабочей среде, а также как с использованием параметра «Запомнить меня», так и без него. Логика этого объясняется в официальный сайт.

Моя проблема была очень похожа. Я настроил безопасность в Symfony 6.0 в соответствии с Безопасность (Документация Symfony). После отправки формы входа и успешной аутентификации я был перенаправлен без сохранения сеанса как анонимный пользователь.

На следующий день я, наконец, попытался зайти на свой сайт с помощью https и вуаля, я вошел в систему. Поэтому попробуйте использовать https при входе в систему.

Другие вопросы по теме