When are user roles refreshed and how to force it?

ⅰ亾dé卋堺 提交于 2019-11-28 19:22:40

So after a couple of days trying to find a viable solution and contributing to the Symfony2 user mailing list, I finally found it. The following has been derived from the discussion at https://groups.google.com/d/topic/symfony2/NDBb4JN3mNc/discussion

It turns out that there's an interface Symfony\Component\Security\Core\User\EquatableInterface that is not intended for comparing object identity but precisely to

test if two objects are equal in security and re-authentication context

Implement that interface in your user class (the one already implementing UserInterface). Implement the only required method isEqualTo(UserInterface $user) so that it returns false if the current user's roles differ from those of the passed user.

Note: The User object is serialized in the session. Because of the way serialization works, make sure to store the roles in a field of your user object, and do not retrieve them directly in the getRoles() Method, otherwise all of that won't work!

Here's an example of how the specific methods might look like:

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;
}

Also, note that when the roles actually change and you reload the page, the profiler toolbar might tell you that your user is not authenticated. Plus, looking into the profiler, you might find that the roles didn't actually get refreshed.

I found out that the role refreshing actually does work. It's just that if no authorization constraints are hit (no @Secure annotations, no required roles in the firewall etc.), the refreshing is not actually done and the user is kept in the "unauthenticated" state.

As soon as you hit a page that performs any kind of authorization check, the user roles are being refreshed and the profiler toolbar displays the user with a green dot and "Authenticated: yes" again.

That's an acceptable behavior for me - hope it was helpful :)

In your security.yml (or the alternatives):

security:
    always_authenticate_before_granting: true

Easiest game of my life.

From a Controller, after adding roles to a user, and saving to the database, simply call:

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

Take a look here, set always_authenticate_before_granting to true at security.yml.

I achieve this behaviour by implementing my own EntityUserProvider and overriding loadByUsername($username) method :

   /**
    * 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;
   }

The trick is to call setRoles each time you call loadByUsername ... Hope it helps

Solution is to hang a subscriber on a Doctrine postUpdate event. If updated entity is User, same user as logged, then I do authenticate using AuthenticationManager service. You have to inject service container (or related services) to subscriber, of course. I prefer to inject whole container to prevent a circular references issue.

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);
            }
        }
    }
}

Sorry i cant reply in comment so i replay to question. If someone new in symfony security try to get role refresh work in Custom Password Authentication then inside function authenticateToken :

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

And do not check for passwords from DB/LDAP or anywhere. If user come in system then in $token are just username and had no roles.

Now Symfony 4.1 is here for awhile,
and I resolve the problem by Manually Authenticating the User.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!