Symfony2: невозможно успешно войти в систему с двумя брандмауэрами с использованием двух пользовательских провайдеров

Я настраиваю веб-сайт, на котором хочу использовать отдельные брандмауэры и системы аутентификации для интерфейса и сервера. Итак, мой security.yml настроен, как показано ниже. Я использую поставщика пользователей in_memory на ранней стадии разработки.

security:
    encoders:
        Symfony\Component\Security\Core\User\User: plaintext

    role_hierarchy:
        ROLE_ADMIN:       ROLE_USER
        ROLE_SUPER_ADMIN: [ROLE_USER, ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH]

    providers:
        backend_in_memory:
            memory:
                users:
                    admin: { password: admin, roles: [ 'ROLE_ADMIN' ] }
        frontend_in_memory:
            memory:
                users:
                    user:  { password: 12345, roles: [ 'ROLE_USER' ] }

    firewalls:

        # (Configuration for backend omitted)

        frontend_login_page:
            pattern:  ^/login$
            security: false

        frontend:
            pattern:   ^/
            provider: frontend_in_memory
            anonymous: ~
            form_login:
                check_path: login_check_route  # http://example.com/login_check
                login_path: login_route        # http://example.com/login


    access_control:
        # (Configuration for backend omitted)
        - { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
        - { path: ^/, roles: ROLE_USER }

Я опустил внутреннюю часть, потому что это не имеет значения. Проблема все еще существует, когда пропущенная часть закомментирована.

Проблема в том, что внешняя аутентификация не будет работать с указанной выше конфигурацией. Вот что я сделал:

  1. Посетите http://example.com/login.
  2. Введите учетные данные (пользователь: 12345), нажмите «Войти».
  3. http://example.com/login_check аутентифицирует пользователя
  4. Служба аутентификации перенаправляет пользователя обратно на http://example.com/. Ошибка не выдается. На самом деле, когда я включил опцию debug_redirects, она ясно показывает, что «пользователь» аутентифицирован на странице перенаправления.

Ожидаемое поведение: токен безопасности должен показывать, что я вошел в систему как «пользователь» после перенаправления и вернулся на страницу индекса.

Фактическое поведение: токен безопасности по-прежнему показывает «анонимный» вход в систему после перенаправления и возврата на страницу индекса.

Но при почти одинаковых настройках (пути и названия маршрутов не совпадают) бэкенд-часть работает корректно.

После некоторого расследования я обнаружил, что причина в том, как в настоящее время написаны пользовательские поставщики. Обратите внимание, что раздел frontend_in_memory расположен под разделом backend_in_memory, который используется для внутренней аутентификации. Поэтому я явно указываю поставщика frontend_in_memory для внешнего брандмауэра. И это работает - я должен войти в систему с «пользователем: 12345» на странице входа в систему. Войти под "админом" не получится. Поэтому он должен использовать правильного поставщика пользователей. Но я подозреваю, что платформа не может правильно обновить токен безопасности, потому что она все еще ищет учетную запись «пользователь» из первого поставщика пользователей, который является backend_in_memory. На самом деле я могу заставить приведенную выше конфигурацию работать с одним из следующих изменений:

  1. добавить логин "user" в список пользователей поставщика backend_in_memory (пароль может не совпадать) или
  2. поменяйте местами frontend_in_memory с backend_in_memory, чтобы frontend_in_memory стал первым поставщиком пользователей.

Конечно, они не являются правильным способом решения этой проблемы. Добавление учетной записи «пользователя» в бэкэнд вообще не имеет смысла; замена порядка двух пользовательских провайдеров исправляет интерфейс, но ломает серверную часть.

Я хотел бы знать, что не так и как это исправить. Спасибо!


person Edmund Tam    schedule 26.11.2014    source источник


Ответы (1)


Я застрял, когда разместил вопрос, но после сна ответ найден;)

Оказывается, я столкнулся с проблемой, о которой сообщалось давно: https://github.com/symfony/symfony/issues/4498

Вкратце,

  • Проблема не в конфигурации.
  • И дело даже не в аутентификации.
  • На самом деле это относится к тому, как аутентифицированный пользователь обновляется после перенаправления. Вот почему приложение правильно аутентифицируется как «пользователь» на странице перенаправления, но не после этого.

Вот код, когда фреймворк обновляет пользователя (можно найти в \Symfony\Component\Security\Http\Firewall\ContextListener):

    foreach ($this->userProviders as $provider) {
        try {
            $refreshedUser = $provider->refreshUser($user);
            $token->setUser($refreshedUser);

            if (null !== $this->logger) {
                $this->logger->debug(sprintf('Username "%s" was reloaded from user provider.', $refreshedUser->getUsername()));
            }

            return $token;
        } catch (UnsupportedUserException $unsupported) {
            // let's try the next user provider // *1
        } catch (UsernameNotFoundException $notFound) {
            if (null !== $this->logger) {
                $this->logger->warning(sprintf('Username "%s" could not be found.', $notFound->getUsername()));
            }

            return; // *2
        }
    }

Приведенный выше код показывает, как фреймворк перебирает провайдеров пользователей, чтобы найти конкретного пользователя (refreshUser()). *1 и *2 добавлены мной. Если поставщик пользователя выдает UnsupportedUserException, это означает, что поставщик не несет ответственности за предоставленный UserInterface. Затем прослушиватель перейдет к следующему поставщику пользователей (*1).

Однако, если провайдер пользователя выдал UsernameNotFoundException, это означает, что провайдер несет ответственность за предоставленный UserInterface, но не удалось найти соответствующую учетную запись. После этого цикл немедленно остановится. (*2)

В моем вопросе один и тот же пользовательский провайдер \Symfony\Component\Security\Core\User\InMemoryUserProvider используется как во внешней, так и в внутренней среде. А InMemoryUserProvider отвечает за UserInterface, реализованный Symfony\Component\Security\Core\User\User.

Во внешнем интерфейсе «пользователь» фактически успешно аутентифицирован. Однако при попытке обновления пользователя

  • Порядок пользовательских провайдеров будет таким: внутренний провайдер в памяти, внешний провайдер в памяти.
  • Таким образом, внутренний провайдер в памяти будет запущен первым.
  • Серверный поставщик в памяти считает, что он несет ответственность за предоставленный UserInterface, поскольку он также является экземпляром Symfony\Component\Security\Core\User\User.
  • Но ему не удается найти учетную запись «пользователь» (у нее есть только учетная запись «администратор»).
  • Затем он выдает UsernameNotFoundException.
  • Подпрограмма refreshUser() не будет пытаться использовать следующий провайдер, потому что UsernameNotFoundException означает, что ответственный пользовательский провайдер уже найден. Вместо этого он прекращает попытки и удаляет токен аутентификации.

Это объясняет, почему конфигурация не будет работать. Несмотря на использование другого провайдера пользователей, единственный способ обойти это — скопировать классы InMemoryUserProvider и User фреймворка и изменить метод refreshUser() для проверки на соответствие скопированному классу User, чтобы внешний и бэкенд-провайдеры пользователей использовали разные пользовательские классы и выиграли. не конфликтуют.

person Edmund Tam    schedule 27.11.2014
comment
Вы можете пометить свой ответ как принятый ответ, чтобы другие знали, что на этот пост есть ответ, - person Sam; 08.07.2015