Symfony2项目中基于学说的认证机制

时间:2011-01-16 17:53:08

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

我是第一次使用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支持的),这使得学习过程有点困难。我对身份验证如何工作的想法是,有一些神奇的Controller捕获validateLogin操作,为我的User实体查找实体存储库,调用findOneBy('username'=&gt; $ username)并比较密码...是这对吗?

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

3 个答案:

答案 0 :(得分:20)

  

我对身份验证如何工作的想法是,有一些神奇的Controller捕获validateLogin操作,为我的User实体查找实体存储库,调用findOneBy('username'=&gt; $ username)并比较密码。 ..这是对的吗?

你错了。身份验证不涉及任何控制器,这就是您不在_security_check路由中指定任何控制器的原因。 Auth基于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. 网址匹配check_path选项。
    2. 请求同时包含_username_password个参数。
  6. 侦听器尝试对用户(attemptAuthentication()方法)进行身份验证。
  7. 身份验证管理器会触发所有已注册的提供商。
  8. 最后,DaoAuthenticationProvider被触发,它尝试使用Doctrine的用户存储库类检索用户。
  9. 如果一切正常UsernamePasswordToken(包含$user方法返回的loadUserByUsername()对象)将被返回并且用户被重定向。
  10. 确实,安全机制非常复杂且难以理解(文档尚未完成)。但是当你终于明白它是如何工作的时候,你会看到它的机制是多么强大。


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

    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. 服务security.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. 安全路线和控制器与您的相同。

答案 1 :(得分:4)

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

答案 2 :(得分:1)

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