如何使用 FOSUserBundle 强制更改密码?

Posted

技术标签:

【中文标题】如何使用 FOSUserBundle 强制更改密码?【英文标题】:How to force password change using FOSUserBundle? 【发布时间】:2013-02-21 19:11:19 【问题描述】:

我正在尝试在 Symfony 2.1 项目中实现安全功能,管理员可以使用初始密码创建用户,然后当用户第一次登录时自动触发更改密码处理程序。

我在覆盖 FOSUserBundle 类时遇到了问题,我认为这肯定已经以某种方式内置,至少部分内置,尽管我在任何地方的文档中都看不到它。

我想在实体中使用 credentials_expired 标志。当管理员创建用户时,这将设置为 1。当用户首次登录时,会检查 credentials_expired,而不是抛出异常,而是触发 change-password。我已经做到了这一点。

ChangePasswordController 然后将确保密码实际已更改(这似乎不是 FOS 中的默认行为)并且 credentials_expired 设置为 0。这就是我卡住的地方。有太多的服务层,我似乎无法正确地定制东西。

【问题讨论】:

您能说得更具体些吗?有很多方法可以解决这个问题。发布您的代码将帮助我们确定如何进行。无论如何,使用角色比使用标志更可能是一个好主意,因为您可以使用 symfony 防火墙来管理它。因此,具有 CREDENTIAL_EXPIRED 角色的人无法访问整个网络,并且他们被困在一个强制他们更改密码的表单中。 代码是 FOSUserBundle。这个角色是个好主意,因为它不需要扩展类。我会试一试。谢谢。 【参考方案1】:

这是详细的答案。感谢 Manu 的跳板!

首先,确保在 composer.json 文件中获取正确的 FOSUserBundle(“dev-master”,而不是“*”):

"friendsofsymfony/user-bundle":"dev-master"

以下全部包含在我自己的用户包中,它按照安装文档中的说明扩展了 FOSUserBundle。

PortalFlare/Bundle/UserBundle/Resources/config/services.xml:

<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services  http://symfony.com/schema/dic/services/services-1.0.xsd">

<parameters>
    <parameter key="portal_flare_user.forcepasswordchange.class">PortalFlare\Bundle\UserBundle\EventListener\ForcePasswordChange</parameter>
    <parameter key="portal_flare_user.passwordchangelistener.class">PortalFlare\Bundle\UserBundle\EventListener\PasswordChangeListener</parameter>
</parameters>

<services>
    <service id="portal_flare_user.forcepasswordchange" class="%portal_flare_user.forcepasswordchange.class%">
        <argument type="service" id="router" />
        <argument type="service" id="security.context" />
        <argument type="service" id="session" />
        <tag name="kernel.event_listener" event="kernel.request" method="onCheckStatus" priority="1" />
    </service>
    <service id="portal_flare_user.passwordchange" class="%portal_flare_user.passwordchangelistener.class%">
        <argument type="service" id="router" />
        <argument type="service" id="security.context" />
        <argument type="service" id="fos_user.user_manager" />
        <tag name="kernel.event_subscriber" />
    </service>
</services>

</container>

PortalFlare/Bundle/UserBundle/EventListener/ForcePasswordChange.php

    <?php

namespace PortalFlare\Bundle\UserBundle\EventListener;

use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;

use Symfony\Component\Security\Core\SecurityContext;
use Symfony\Bundle\FrameworkBundle\Routing\Router;
use Symfony\Component\HttpFoundation\Session\Session;

/**
 * @Service("request.set_messages_count_listener")
 *
 */
class ForcePasswordChange 

  private $security_context;
  private $router;
  private $session;

  public function __construct(Router $router, SecurityContext $security_context, Session $session) 
    $this->security_context = $security_context;
    $this->router           = $router;
    $this->session          = $session;

  

  public function onCheckStatus(GetResponseEvent $event) 

    if (($this->security_context->getToken()) && ($this->security_context->isGranted('IS_AUTHENTICATED_FULLY'))) 

      $route_name = $event->getRequest()->get('_route');

      if ($route_name != 'fos_user_change_password') 

        if ($this->security_context->getToken()->getUser()->hasRole('ROLE_FORCEPASSWORDCHANGE')) 

          $response = new RedirectResponse($this->router->generate('fos_user_change_password'));
          $this->session->setFlash('notice', "Your password has expired. Please change it.");
          $event->setResponse($response);

        

      

    

  

 

PortalFlare/Bundle/UserBundle/EventListener/PasswordChangeListener.php:

<?php
namespace PortalFlare\Bundle\UserBundle\EventListener;

use FOS\UserBundle\FOSUserEvents;
use FOS\UserBundle\Event\FormEvent;
use FOS\UserBundle\Doctrine\UserManager;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Security\Core\SecurityContext;

/**
 * Listener responsible to change the redirection at the end of the password change
 */
class PasswordChangeListener implements EventSubscriberInterface 
  private $security_context;
  private $router;
  private $usermanager;

  public function __construct(UrlGeneratorInterface $router, SecurityContext $security_context, UserManager $usermanager) 
    $this->security_context = $security_context;
    $this->router           = $router;
    $this->usermanager      = $usermanager;
  

  /**
   * @inheritDoc
   */
  public static function getSubscribedEvents() 
    return array(
      FOSUserEvents::CHANGE_PASSWORD_SUCCESS => 'onChangePasswordSuccess',
    );
  

  public function onChangePasswordSuccess(FormEvent $event) 

    $user = $this->security_context->getToken()->getUser();
    $user->removeRole('ROLE_FORCEPASSWORDCHANGE');
    $this->usermanager->updateUser($user);

    $url = $this->router->generate('_welcome');
    $event->setResponse(new RedirectResponse($url));
  

FOSUserBundle 的问题实际上并没有确保用户更改密码是另一天的问题。

我希望这对某人有所帮助。

【讨论】:

【参考方案2】:

一个好的方法是开始定义一个 ROLE_USER,它是可以访问整个应用程序的角色。当用户注册时,自动添加他 ROLE_CREDENTIAL_EXPIRED 或类似的东西。使用JMSSecurityExtraBundle,您可以在控制器中使用注释,决定具有给定角色的用户是否可以访问。还可以查看Symfony handle the HTTP Authentication 的文档。

【讨论】:

你真好!谢谢@DarraghEnright :)

以上是关于如何使用 FOSUserBundle 强制更改密码?的主要内容,如果未能解决你的问题,请参考以下文章

linux如何设置一般用户密码必须达到一定强度?还有3月强制更改一次密码?

FOSUserBundle:密码重置后的成功目标

如何强制 Access 更改无 DSN 的 ODBC 连接的用户名和密码?

linux如何设置一般用户密码必须达到一定强度?还有3月强制更改一次密码?

新用户注册/密码更改后强制注销

新用户注册/密码更改后强制注销