Подтвердить что ты не робот

Когда обновляется пользовательский ролик и как его заставить?

Во-первых, я не использую FOSUserBundle, и я не могу, потому что я портирую унаследованную систему, у которой есть свой собственный уровень модели (без Doctrine/Mongo/whatsoever здесь) и другое очень обычное поведение.

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

Моя первая попытка состояла в том, чтобы загрузить и вернуть все пользовательские роли в методе getRoles() из Symfony\Component\Security\Core\User\UserInterface. Сначала все выглядело так, как будто это сработало. Но после более глубокого взгляда я заметил, что эти роли обновляются только тогда, когда пользователь входит в систему. Это означает, что если я предоставляю или отменю роли от пользователя, ему придется выйти из системы и вернуться, чтобы изменения вступили в силу. Однако, если я отменяю роли безопасности у пользователя, я хочу, чтобы это было немедленно применено, чтобы поведение было неприемлемым для меня.

Я хочу, чтобы Symfony выполнял перезагрузку пользовательских ролей по каждому запросу, чтобы убедиться, что они актуальны. Я внедрил пользовательский поставщик, и его метод refreshUser(UserInterface $user) вызывается по каждому запросу, но роли каким-то образом не обновляются.

Код для загрузки/обновления пользователя в UserProvider выглядит примерно так:

public function loadUserByUsername($username) {
    $user = UserModel::loadByUsername($username); // Loads a fresh user object including roles!
    if (!$user) {
        throw new UsernameNotFoundException("User not found");
    }
    return $user;
}

(refreshUser выглядит похожим)

Есть ли способ заставить Symfony обновлять роли пользователей по каждому запросу?

4b9b3361

Ответ 1

Итак, через пару дней, пытаясь найти жизнеспособное решение и внес свой вклад в список рассылки пользователей Symfony2, я наконец нашел его. Из обсуждения было получено следующее: https://groups.google.com/d/topic/symfony2/NDBb4JN3mNc/discussion

Оказывается, существует интерфейс Symfony\Component\Security\Core\User\EquatableInterface, который не предназначен для сравнения идентификатора объекта, а именно с

проверить, совпадают ли два объекта в контексте безопасности и повторной аутентификации

Внедрите этот интерфейс в свой пользовательский класс (тот, который уже реализует UserInterface). Внедрите единственный требуемый метод isEqualTo(UserInterface $user), чтобы он возвращал значение false, если текущие роли пользователя отличаются от существующих пользователей.

Примечание. Объект User сериализуется в сеансе. Из-за того, как работает сериализация, обязательно сохраните роли в поле вашего пользовательского объекта и не извлекайте их непосредственно в методе getRoles(), иначе все это не сработает!

Вот пример того, как могут выглядеть конкретные методы:

protected $roles = null;

public function getRoles() {

    if ($this->roles == null) {
        $this->roles = ...; // Retrieve the fresh list of roles
                            // from wherever they are stored here
    }

    return $this->roles;
}

public function isEqualTo(UserInterface $user) {

    if ($user instanceof YourUserClass) {
        // Check that the roles are the same, in any order
        $isEqual = count($this->getRoles()) == count($user->getRoles());
        if ($isEqual) {
            foreach($this->getRoles() as $role) {
                $isEqual = $isEqual && in_array($role, $user->getRoles());
            }
        }
        return $isEqual;
    }

    return false;
}

Также обратите внимание, что когда роли фактически меняются и вы перезагружаете страницу, панель инструментов профилировщика может сказать вам, что ваш пользователь не аутентифицирован. Кроме того, глядя в профилировщик, вы можете обнаружить, что роли на самом деле не обновились.

Я узнал, что обновление роли действительно работает. Просто, если никаких ограничений авторизации не удастся (нет @Secure аннотаций, нет необходимых ролей в брандмауэре и т.д.), Обновление на самом деле не выполняется, и пользователь находится в состоянии "не прошедшего проверку".

Как только вы нажмете на страницу, которая выполняет какую-либо проверку авторизации, обновляются роли пользователей, а панель инструментов профилировщика отображает пользователя с зеленой точкой и снова "Аутентифицировано: да".

Это приемлемое для меня поведение - надеюсь, что это было полезно:)

Ответ 2

В вашем security.yml(или альтернативах):

security:
    always_authenticate_before_granting: true

Самая простая игра в моей жизни.

Ответ 3

Из контроллера после добавления роли пользователю и сохранения в базе данных просто вызовите:

// Force refresh of user roles
$token = $this->get('security.context')->getToken()->setAuthenticated(false);

Ответ 4

Я достигаю этого поведения, реализуя собственный EntityUserProvider и переопределяя метод loadByUsername ($ username):

   /**
    * Load an user from its username
    * @param string $username
    * @return UserInterface
    */
   public function loadUserByUsername($username)
   {
      $user = $this->repository->findOneByEmailJoinedToCustomerAccount($username);

      if (null === $user)
      {
         throw new UsernameNotFoundException(sprintf('User "%s" not found.', $username));
      }

      //Custom function to definassigned roles to an user
      $roles = $this->loadRolesForUser($user);

      //Set roles to the user entity
      $user->setRoles($roles);

      return $user;
   }

Фокус в том, чтобы называть setRoles каждый раз, когда вы вызываете loadByUsername... Надеюсь, он поможет

Ответ 5

Посмотрите здесь, установите always_authenticate_before_granting в true на security.yml.

Ответ 6

Решение состоит в том, чтобы повесить абонента на событие postUpdate Doctrine. Если обновленный объект - это Пользователь, тот же пользователь, который зарегистрирован, то я выполняю аутентификацию с помощью службы AuthenticationManager. Разумеется, вы должны инъецировать контейнер обслуживания (или связанные с ним услуги) абоненту. Я предпочитаю вводить весь контейнер, чтобы предотвратить проблему с циркулярными ссылками.

public function postUpdate(LifecycleEventArgs $ev) {
    $entity = $ev->getEntity();

    if ($entity instanceof User) {
        $sc = $this->container->get('security.context');
        $user = $sc->getToken()->getUser();

        if ($user === $entity) {
            $token = $this->container->get('security.authentication.manager')->authenticate($sc->getToken());

            if ($token instanceof TokenInterface) {
                $sc->setToken($token);
            }
        }
    }
}

Ответ 7

Извините, я не могу ответить в комментарии, поэтому переиграю на вопрос. Если кто-то новый в безопасности Symfony попытается получить функцию обновления роли в пользовательской аутентификации пароля, тогда внутри функции authenticateToken:

if(count($token->getRoles()) > 0 ){
        if ($token->getUser() == $user ){
            $passwordValid=true;
        }
    }

И не проверяйте пароли из DB/LDAP или где угодно. Если пользователь входит в систему, то в $токене просто имя пользователя и не имеет ролей.