Когда обновляется пользовательский ролик и как его заставить?
Во-первых, я не использую 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 обновлять роли пользователей по каждому запросу?
Ответы
Ответ 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 или где угодно. Если пользователь входит в систему, то в $токене просто имя пользователя и не имеет ролей.