【问题标题】:Doctrine based authentication mechanism in Symfony2 projectSymfony2 项目中基于 Doctrine 的认证机制
【发布时间】:2011-01-16 17:53:07
【问题描述】:

我正在第一次使用 Symfony2 进行一个由 Doctrine2 支持的小型项目。目前我正在为 symfony2 的安全组件而苦苦挣扎,确切地说是documentation 中描述的身份验证机制。

我想使用基于表单的身份验证并完成文档中所述的所有操作:

我有一个 security.yml 配置文件,如下所示:

security.config:
    firewalls:
        admin:
            pattern:                             /admin/.*
            form-login:                          true
            logout:                              true
            login_path:                          /login
            check_path:                          /validateLogin
            always_use_default_target_path:      false
            target_path_parameter:               target
        check_page:
            pattern:                             /validateLogin
            form-login:                          true
            login_path:                          /login
            check_path:                          /validateLogin
            always_use_default_target_path:      false
            target_path_parameter:               target
        public:
            pattern:                             /.*
            security:                            false
    providers:
        admin:
            password_encoder:                    md5
            entity:
                class:                           AdminBundle:User
                property:                        username
    access_control:
        - { path: /admin/.*, role: ROLE_ADMIN }
        - { path: /validateLogin, role: IS_AUTHENTICATED_ANONYMOUSLY }
    role_hierarchy:
        ROLE_ADMIN:       ROLE_USER

在阅读devcomments 上的类似帖子后,check_page 被排除在“无安全”区域之外。

在我的路由配置中,我包含两个身份验证规则:

_security_login:
    pattern:                      /login
    defaults:    
        _controller:              PublicBundle:Auth:index

_security_check:
    pattern:                      /validateLogin

我用来表示用户的实体类是一个 Doctrine2 实体并实现了 AccountInterface:

<?php

namespace Application\AdminBundle\Entity;

use Symfony\Component\Security\User\AccountInterface;

/**
 * @orm:Entity
 */
class User implements AccountInterface
{
/**
 * @orm:Id
 * @orm:Column(type="integer")
 * @orm:GeneratedValue(strategy="IDENTITY")
 */
protected $id;
/**
 * @orm:Column(type="string", length="255")
 */
protected $username;
/**
 * @orm:Column(type="string", length="40")
 */
protected $password;

public function getId()
{
    return $this->id;
}

public function setId($id)
{
    $this->id = $id;
}

public function getUsername()
{
    return $this->username;
}

public function setUsername($username)
{
    $this->username = $username;
}

public function getPassword()
{
    return $this->password;
}

public function setPassword($password)
{
    $this->password = $password;
}

/**
 * Implementing the AccountInterface interface
 */
public function __toString()
{
    return $this->getUsername();
}

public function getRoles()
{
    return array('ROLE_ADMIN');
}

public function eraseCredentials()
{

}

public function getSalt()
{
    return $this->getId();
}
}

在 AuthController 类中,我使用 symfony2 文档中的示例代码:

public function indexAction()
{
    if ($this->get('request')->attributes->has(SecurityContext::AUTHENTICATION_ERROR)) {
        $error = $this->get('request')->attributes->get(SecurityContext::AUTHENTICATION_ERROR);
    } else {
        $error = $this->get('request')->getSession()->get(SecurityContext::AUTHENTICATION_ERROR);
    }

    return
        $this->render(
            'PublicBundle:Auth:index.twig',
            array(
                'last_username' => $this->get('request')->getSession()->get(SecurityContext::LAST_USERNAME),
                'error' => $error));
}

现在问题来了:从http://symfony2.localhost/app_dev.php/admin/testhttp://symfony2.localhost/app_dev.php/login 的重定向规则有效,但是在输入用户名/密码并提交登录表单后,我再次被重定向到登录 url 而没有错误消息。

我知道这可能是一个非常基本的问题,但是由于关于 symfony2 的文档还不是很多,我认为这是一个提出类似问题的好地方。 一般来说,symfony2 项目中的一些要点似乎在神奇地工作(当然是 DI 支持的),这使得学习过程有点困难。我对身份验证如何工作的想法是,有一些神奇的控制器可以捕获 validateLogin 操作,为我的 User 实体查找实体存储库,调用 findOneBy('username' => $username) 并比较密码......是这样吗?对吧?

提前感谢您的任何提示,我已经在谷歌上搜索这个问题几个小时了...... :)

保罗

【问题讨论】:

    标签: php authentication dependency-injection doctrine-orm symfony


    【解决方案1】:

    我对身份验证如何工作的想法是,有一些神奇的控制器可以捕获 validateLogin 操作,为我的用户实体查找实体存储库,调用 findOneBy('username' => $username) 并比较密码.. . 对吗?

    你错了。身份验证不涉及任何控制器,这就是您不在_security_check 路由中指定任何控制器的原因。身份验证基于EventDispatcher。每当您在防火墙中指定某个侦听器(例如form_loginanonymouslogout 等)时,您实际上为core.security 事件注册了一个新侦听器。 Symfony\Component\HttpKernel\Security\Firewall::handle() 是这些监听器实际注册的地方。

    一般的、简化的流程:

    1. 用户填写登录表单(_username_password 字段)。
    2. 请求由 Symfony2 处理。
    3. core.security 事件被触发。
    4. EventDispatcher 通知所有侦听器。
    5. UsernamePasswordFormAuthenticationListener 被触发(handle() 方法)并检查是否:
      1. URL 匹配 check_path 选项。
      2. 请求同时具有_username_password 参数。
    6. 侦听器尝试对用户进行身份验证(attemptAuthentication() 方法)。
    7. 身份验证管理器触发所有已注册的提供程序。
    8. 最后,DaoAuthenticationProvider 被触发并尝试使用 Doctrine 的用户存储库类检索用户。
    9. 如果一切正常,UsernamePasswordToken(包含由loadUserByUsername() 方法返回的$user 对象)将被返回并重定向用户。

    确实,安全机制相当复杂且难以理解(文档尚未完成)。但是当您最终了解它的工作原理时,您就会看到它的强大机制。


    我编写了自己的身份验证机制,它运行良好。

    1. 配置:

      我正在使用自定义提供程序和编码器。

      security.config:
          providers:
              main:
                  id:         project.user_repository # DI id. Doctrine's UserRepositry
                  check_path: /login-check
          encoders:
              main:
                  class: Project\SiteBundle\Entity\User
                  id:    security.encoder.sha512     # DI id. Service %security.encoder.digest.class% (with "sha512" as first parameter)
          firewalls:
              restricted:
                  pattern:    /panel/.*
                  form_login: 
                      check_path: /login-check
              public:
                  pattern:    /.*
                  anonymous:  true
                  form_login: 
                      check_path: /login-check
                  logout:     true
          access_control:
              - { path: /panel/.*, role: ROLE_USER }
              - { path: /.*, role: IS_AUTHENTICATED_ANONYMOUSLY }
      

      如您所见,/panel/* 是受限的,而 /* 是公开的。

    2. Servicesecurity.encoder.sha512 是一个内置的编码器:

      <service id="security.encoder.sha512" class="%security.encoder.digest.class%">
          <argument>sha512</argument>
      </service>
      
    3. Project\SiteBundle\Entity\User:

      /**
       * @orm:Entity(repositoryClass="Project\SiteBundle\Repository\UserRepository")
       */
      class User implements AdvancedAccountInterface {
          /** 
           * @orm:Id @orm:Column(type="integer")
           * @orm:GeneratedValue(strategy="AUTO")
           */
          protected $id;
      
          /**
           * @orm:Column(unique=true, nullable=true)
           */
          protected $email;
      
          /**
           * @orm:Column(unique=true, nullable=true)
           */
          protected $xmpp;
      
          /**
           * @orm:Column(length=128)
           */
          protected $password;
      
          /**
           * @orm:Column(length=16)
           */
          protected $salt;
      
          // User can be logged in using email address or xmpp adress.
      
          // Dozens of getters/setters here.
      }
      
    4. Project\SiteBundle\Repository\UserRepository

      class UserRepository extends EntityRepository implements UserProviderInterface {
          public function loadUserByUsername($username) {
              $dql = sprintf('
                  SELECT u
                  FROM %s u
                  WHERE u.email = :id OR u.xmpp = :id
              ', $this->_entityName);
      
              $user = null;
      
              try {
                  $user = $this->_em->createQuery($dql)->setParameter('id', $username)->getSingleResult();
              } catch (ORMException $e) {
                  throw new UsernameNotFoundException("User $username not found.", $e->getCode(), $e);
              }
      
              return $user;
          }
      
          public function loadUserByAccount(AccountInterface $user) {
              return $this->loadUserByUsername($user->getUsername());
          }
      }
      
    5. 安全路由和控制器和你的一样。

    【讨论】:

      【解决方案2】:

      您应该使用https://github.com/FriendsOfSymfony/FOSUserBundle FOS UserBundle,它通过 Doctrine 2 实现了所有这些,并具有大量功能。

      【讨论】:

      • 谢谢,我前段时间看过这个。不过我很高兴知道那里发生了什么......似乎我必须阅读更多的 symfony 代码 :)
      • 有一些关于 UserBundle here 的操作方法设置信息,但我很想看一个教程,详细介绍如何安装捆绑包,一直到如何获取登录/注册形式。与此同时,我会阅读、破坏和阅读更多内容。
      【解决方案3】:

      本质上,登录页面再次加载而没有错误消息的原因是具有讽刺意味的是,您的安全设置未设置为允许匿名访问登录页面。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2013-10-16
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-10-24
        相关资源
        最近更新 更多