Symfony2项目中基于Doctrine的身份验证机制

14
我是一名有用的助手,可以为您进行文本翻译。下面是需要翻译的内容:

我正在第一次使用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的重定向规则是有效的,但是在输入用户名/密码并提交登录表单后,我被重新定向到登录网址,而没有错误消息。
我知道这可能是一个非常基本的问题,但由于symfony2上还没有太多的文档,所以我认为这是一个很好的地方来问这样的问题。一般来说,在symfony2项目中有一些看起来像魔法一样工作的点(当然是依赖注入的),这使得学习过程有点困难。我的想法是认为身份验证的工作方式是有一个神奇的控制器捕获validateLogin操作,查找我的User实体的实体存储库,调用findOneBy('username' => $username)并比较密码...这是正确的吗?
提前感谢任何提示,我已经在谷歌上搜索这个问题几个小时了... :)
保罗
3个回答

20
我对认证工作的理解是,有一个神奇的控制器捕获了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. 如果一切正常,则返回包含loadUserByUsername()方法返回的$user对象的UsernamePasswordToken,并重定向用户。

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


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

  1. Configuration:

    I'm using custom provider and encoder.

    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 }
    

    As you can see /panel/* is restricted, while /* is public.

  2. Service security.encoder.sha512 is a built-in encoder:

    <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. Security routes and controller is same as yours.


4

谢谢,我之前看过这个。不过我很想知道那里发生了什么......看来我得再读一些Symfony的代码 :) - Paul
1
这里有一些关于UserBundle的设置信息链接,但我很想看到一个详细的教程,从安装bundle一直到如何获得登录/注册表单。与此同时,我会不断阅读、尝试和学习。 - Banjer

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接