何时刷新用户角色以及如何强制刷新



首先,我没有使用FOSUserBundle,我不能,因为我正在移植一个遗留系统,该系统有自己的模型层(这里没有Doctrine/Mongo/任何东西)和其他非常自定义的行为。

我正在尝试将我的遗留角色系统与Symfony的连接起来,这样我就可以在控制器和视图中使用本机Symfony安全性。

我的第一次尝试是从SymfonyComponentSecurityCoreUserUserInterface加载并返回getRoles()方法中的所有用户角色。一开始,这似乎奏效了。但深入了解后,我发现这些角色只有在用户登录时才会刷新。这意味着,如果我授予或撤销用户的角色,他必须注销并重新登录才能使更改生效。然而,如果我撤销了用户的安全角色,我希望立即应用它,这样我就不能接受这种行为

我想让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在每次请求时刷新用户角色?

因此,经过几天的努力,我终于找到了一个可行的解决方案,并为Symfony2用户邮件列表做出了贡献https://groups.google.com/d/topic/symfony2/NDBb4JN3mNc/discussion

事实证明,有一个接口SymfonyComponentSecurityCoreUserEquatableInterface不是用来比较对象身份的,而是用来精确地与进行比较

测试两个对象在安全和重新身份验证上下文中是否相等

在用户类(已经实现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注释,在防火墙中没有所需的角色等),则刷新实际上不会完成,用户将处于"未经身份验证"状态。

一旦您访问了执行任何类型的授权检查的页面,就会刷新用户角色,探查器工具栏会再次显示带有绿点和"Authenticated:yes"的用户。

这对我来说是一种可以接受的行为——希望它能有所帮助:)

在您的安全中。yml(或替代方案):

security:
always_authenticate_before_granting: true

我一生中最轻松的游戏。

从控制器向用户添加角色并保存到数据库后,只需调用:

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

看这里,在security.yml处将always_authenticate_before_granting设置为true

我通过实现自己的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;
}

诀窍是每次呼叫loadByUsername时都要呼叫setRoles。。。希望它能帮助

解决方案是在DoctrinepostUpdate事件上挂起订阅者。若更新的实体是User,和记录的用户相同,那个么我会使用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);
}
}
}
}

很抱歉我不能在评论中回复,所以我重播这个问题。如果新加入symfony安全的人试图在自定义密码验证中进行角色刷新,那么在函数内部验证令牌:

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

并且不要从DB/LDAP或任何地方检查密码。若用户进入系统,那个么$token中只有用户名,并没有任何角色。

我一直在为Symfony4而斗争,我想我终于找到了解决方案。

问题是,在我的情况下,角色取决于";公司";用户正在使用。它可能是一家公司的首席执行官,但另一家公司是运营商,菜单、权限等取决于公司。切换公司时,用户不得重新登录。

最后我完成了以下操作:

  • 将防火墙设置为无状态
  • 在FormAuthentication类中,我使用username在会话中明确地设置了一个属性
  • 我设置了另一个Guard,它本质上接受这个属性,并从数据库中为每个请求加载用户
class FormAuthenticator extends AbstractFormLoginAuthenticator
{
/** Constructor omitted */
public function supports(Request $request)
{
return 'app_login' === $request->attributes->get('_route')
&& $request->isMethod('POST');
}
public function getCredentials(Request $request)
{
$credentials = [
'nomusuari' => $request->request->get('nomusuari'),
'password' => $request->request->get('password'),
'csrf_token' => $request->request->get('_csrf_token'),
];
$request->getSession()->set(
Security::LAST_USERNAME,
$credentials['nomusuari']
);
return $credentials;
}
public function getUser($credentials, UserProviderInterface $userProvider)
{
$token = new CsrfToken('authenticate', $credentials['csrf_token']);
if (!$this->csrfTokenManager->isTokenValid($token)) {
throw new InvalidCsrfTokenException();
}
$user = $userProvider->loadUserByUsername($credentials['nomusuari']);
if (!$user) {
// fail authentication with a custom error
throw new CustomUserMessageAuthenticationException('Invalid user/password');
}
return $user;
}
public function checkCredentials($credentials, UserInterface $user)
{
$valid = $this->passwordEncoder->isPasswordValid($user, $credentials['password']);
return $valid;
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
{
$request->getSession()->set("user_username",$token->getUsername());
return new RedirectResponse(
$this->urlGenerator->generate("main")
);
}
protected function getLoginUrl()
{
return $this->urlGenerator->generate('app_login');
}
}

SessionAuthenticator(返回JSON,您可能需要对其进行调整):

class SessionAuthenticator extends AbstractGuardAuthenticator
{
/**
* Called on every request to decide if this authenticator should be
* used for the request. Returning `false` will cause this authenticator
* to be skipped.
*/
public function supports(Request $request)
{
return $request->getSession()->has("user_username");
}
/**
* Called on every request. Return whatever credentials you want to
* be passed to getUser() as $credentials.
*/
public function getCredentials(Request $request)
{
return $request->getSession()->get("user_username","");
}
public function getUser($credentials, UserProviderInterface $userProvider)
{
if (null === $credentials) {
// The token header was empty, authentication fails with HTTP Status
// Code 401 "Unauthorized"
return null;
}
// if a User is returned, checkCredentials() is called
/*return $this->em->getRepository(User::class)
->findOneBy(['apiToken' => $credentials])
;*/
return $userProvider->loadUserByUsername($credentials);
}
public function checkCredentials($credentials, UserInterface $user)
{
// Check credentials - e.g. make sure the password is valid.
// In case of an API token, no credential check is needed.
// Return `true` to cause authentication success
return true;
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
{
// on success, let the request continue
return null;
}
public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
{
$data = [
// you may want to customize or obfuscate the message first
'message' => strtr($exception->getMessageKey(), $exception->getMessageData())
// or to translate this message
// $this->translator->trans($exception->getMessageKey(), $exception->getMessageData())
];
return new JsonResponse($data, Response::HTTP_UNAUTHORIZED);
}
/**
* Called when authentication is needed, but it's not sent
*/
public function start(Request $request, AuthenticationException $authException = null)
{
$data = [
// you might translate this message
'message' => 'Authentication Required'
];
return new JsonResponse($data, Response::HTTP_UNAUTHORIZED);
}
public function supportsRememberMe()
{
return false;
}
}

最后,我的安全。yaml:

main:
anonymous:
stateless: true
guard:
entry_point: AppSecurityFormAuthenticator
authenticators:
- AppSecuritySessionAuthenticator
- AppSecurityFormAuthenticator

工作良好。我可以在工具栏中看到更改,并且角色会被刷新。

HTH,

Esteve

最新更新