Symfony2博客应用程序教程:第四部分-安全介绍

Since a post on security was requested, I am going to show you how to secure a route prefix in your application. The symfony2 security component is very powerful and complex. This implementation will be simple, but you should be able to easily build on it. For securing a production application I would strongly recommend using the FOSUserBundle which can be found here. This bundle is written by some of the core developers of symfony2 and will most likely become the “sfGuardPlugin” for symfony2. The symfony1 folks will know what I mean. I am going to forego writing tests for this part because I want to get it out there as fast as possible, since people seem to be having trouble with security. I may write some tests later and update this post with them. [Update: I have done this. The post can be found here.]
出于对博文安全的考虑,我打算向您展示如何在您的应用程序中使用路由前缀来确保安全。Symfony2的安全组件是非常强大和复杂的。这个实现很简单,而且您应该能在其上轻松构建。为了确保生产环境中应用程序的安全,我强烈推荐使用FOSUserBundle,该Bundle可以在这里找到。这个Bundle由部分symfony2核心开发成员编写,并且极有可能成为Symfony2的“sfGardPlugin”。Symfony1的用户应该明白我的意思。我打算放弃编写这部分的测试,以便尽可能快地实现安全功能,因为人们在安全方面似乎遇到了麻烦。我也许会在以后写一些测试,然后再更新这篇文章[更新:我已经这样做了,文章可以在这里找到。]

In this part we are going to require anyone who tries to access a route that begins with “/admin/” to login using a form. To do this we will need to do a few things. First we need to register the SecurityBundle that ships with the symfony2 framework. Lets do that now. Open up the AppKernel.php file located in the app directory. We need to register the SecurityBundle so find the registerBundles method and add the following bundle to bundles array:
在这部分里,我们打算要求所有访问以“/admin/”开始的路由的用户使用表单登录。要做到这一点,我们需要做一些准备。首先,我们需要注册symfony2框架自带的SecurityBundle。让我们现在就做。打开app目录中的AppKernel.php文件,因为我们要注册SecurityBundle,所以找到registerBundles方法,然后将以下Bundle添加到Bundle数组中:


  1. new Symfony\Bundle\SecurityBundle\SecurityBundle() 

Now that we have registered the bundle we can start having fun. Before we start to write some code, we need to understand the fundamentals of how the security component in symfony2 operates. The symfony2 security component can at a high level be broken down into three different subcomponents; Users, Authentication and Authorization. The Users subcomponent simply represents the client that is using your application. The Authentication subcomponent tries to ensure that the user is who he claims to be. The Authorization subcomponent decides whether or not the user, once authorized, is allowed to perform certain actions, view certain data, etc.
现在我们注册了这个Bundle,我们可以开始享受乐趣了。在我们开始写代码之前,我们需要了解Symfony2安全组件的基本操作原理。Symfony2安全组件可以分解为三个子组件:用户,认证和授权。用户子组件仅是使用您应用程序的客户端;认证子组件尝试确定用户就是他所宣称的用户。一旦认证通过,授权子组件决定是否允许用户执行某些动作、查看某些数据等。

Now we are ready configure our application security. In our configuration we are going to tell symfony2 that we want to use our Company\BlogBundle\Entity\User entity as our User provider, how to encode user passwords, which part of the application should be secure and what role the user should have to access those parts. Open up the config.yml in the app/config directory and add the following configuration:
现在我们准备配置我们应用程序的安全。在我们的配置里,我们要告诉Symfony2我们打算使用我们的Company\BlogBundle\Entity\User实体作为我们的用户提供器、我们要如何加密用户密码、应用程序的哪些部分需要安全以及拥有什么角色的用户可以访问。打开app/config目录中的config.yml文件,并添加下列配置:


  1. ## Security Configuration 
  2. security: 
  3.     encoders: 
  4.         Company\BlogBundle\Entity\User: 
  5.             algorithm: sha512 
  6.             encode-as-base64: true 
  7.             iterations: 10 
  8.  
  9.     providers: 
  10.         main: 
  11.             entity: { class: BlogBundle:User, property: username } 
  12.  
  13.     firewalls: 
  14.         main: 
  15.             pattern: /.* 
  16.             form_login: 
  17.                 check_path: /login_check 
  18.                 login_path: /login 
  19.             logout: true 
  20.             security: true 
  21.             anonymous: true 
  22.  
  23.     access_control: 
  24.         - { path: /admin/.*, role: ROLE_ADMIN } 
  25.         - { path: /.*, role: IS_AUTHENTICATED_ANONYMOUSLY } 

Lets go over each section in our security configuration. First, take a look at the encoders section. In this section, we define how the user passwords will be encoded for the Company\BlogBundle\Entity\User entity. We will be using the MessageDigestPasswordEncoder that comes with the symfony2 framework, but it is certainly possible to write your own. Here we specify that we want to use sha512 as our encoding algorithm, iterated 10 times and to encode as a base64 string. We will explore encoders in more detail when we modify our fixtures in a just a moment. You can read more about encoders here if you want.
让我们过一遍我们安全配置的每个部分。首先看encoders部分。在这个部分,我们定义了如何为Company\BlogBundle\Entity\User实体的用户密码加密。我们要使用Symfony2框架自带的MessageDigestPasswordEncoder,不过您也可以自己编写一个。在这里我们指定使用sha512作为加密算法,迭代10次编码成base64字符串。稍后当我们修改fixtrue时将详细讨论编码器。如果您想更多地了解编码器,可以参阅这里

Next is the providers section. The providers is where we tell symfony2 how to retrieve users. We use the entity entry to tell symfony2 that we want to use the Doctrine Entity Provider. Other available providers are In-Memory Provider and Chain Provider. You can read about them here. Under the entity entry we have to define the entity class and the username property. The class tells symfony2 what entity to load to represent a user. Here we have specified that we want to use the User class in the BlogBundle. The property entry is the PHP column name for the username and in our entity class it will be username.
接下来是提供器部分。提供器告诉Symfony2如何去检索用户。我们用entity条目去告诉Symfony2,我们要用Doctrine实体提供器。其它可用的提供器有in-Memory提供器和Chain提供器。您可以在这里查阅它们。在entity条目里面我们需要定义实体类和用户名属性。class项告诉Symfony2,我们要装载什么实体来代表用户。在这里我们指定我们想在BlogBundle中使用的User类。property项则表示用户名的PHP列名,在我们的实体类中是username。

Authorization is managed by the Firewall system in symfony2. This system is made up of listeners that listen for the core.security event and then redirect the request based on the credentials of the user if necessary. The firewalls entry defines the routing pattern for which we want the security listeners to listen. As of right now, the recommended way to secure an application is to define a single firewall that listens to all routes and then use the access_control entry to allow or disallow access based on roles. We will see the access_control entry in just a moment. In the firewalls entry we have used the pattern entry to specify that we want the security listeners to listen for every route. The form_login entry specifies that our authentication method is to login via a form. You can read about other methods here. Under the form_login entry we specify the routes for the login_path and the check_path. The form_login entry has many more options which you can read about here.
认证在symfony2中是由防火墙系统管理的。该系统由core.security事件的监听器组成,如有必要可以根据用户证书重定向请求。firewalls条目定义了我们需要安全监听器监听的路由模式。到目前为止,确保应用程序安全的推荐做法是定义一个单一的防火墙去监听所有的路由,然后使用access_control条目根据用户所属角色来允许或不允许访问。稍后我们将学习access_control条目。在firewalls条目里我们使用pattern项指定安全监听器去监听每个路由。form_login项指定采用表单登录的认证方式。您可以在这里了解其它认证方式。在form_login项里我们指定login_path和check_path的路由。form_login条目还有更多的选项,您可以在这里查阅。

Lastly, we have the access_control entry. The entries under access_control specify routing patterns and the roles necessary to access them. We have specified that for any route starting with “/admin/” the ROLE_ADMIN role is required for access otherwise the only role needed is the IS_AUTHENTICATED_ANONYMOUSLY. This is a special role provided by symfony2 that every user has.
最后,我们看一下access_control条目。access_control下面的配置项指定了路由模式和访问它们所需的角色。我们指定访问任何以“/admin/”开始的路由需要ROLE_ADMIN角色,其它的只需要IS_AUTHENTICATED_ANONYMOUSLY角色就可以访问。IS_AUTHENTICATED_ANONYMOUSLY角色是symfony2提供的特定角色,所有用户都拥有该角色。

Now that our security configuration is in place we need to update our entity classes to conform to the interfaces required by the SecurityBundle. We need to modify our User entity to implement the UserInterface interface. We also need to create a Role class that implements the RoleInterface. Then we will need to modify our fixtures to load the new data into our database.
现在我们的安全配置已经到位了,我们需要更新我们的实体类以符合SecurityBundle所要求的接口。我们需要修改我们的User实体类去实现 UserInterface接口。我们还需要创建一个Role类去实现RoleInterface接口。然后我们需要修改我们的fixture,以便将新数据导入数据库。

Create a new file named Role.php in the src/Company/BlogBundle/Entity directory. Here is the full Role class:
在src/Company/BlogBundle/Entity 目录中创建一个名为Role.php的新文件。以下是完整的Role类代码:


  1. namespace Company\BlogBundle\Entity; 
  2.   
  3. use Symfony\Component\Security\Core\Role\RoleInterface; 
  4. use Doctrine\ORM\Mapping as ORM; 
  5.   
  6. /**
  7.   * @ORM\Entity
  8.   * @ORM\Table(name="role")
  9.   */ 
  10. class Role implements RoleInterface 
  11.     /**
  12.       * @ORM\Id
  13.       * @ORM\Column(type="integer")
  14.       * @ORM\GeneratedValue(strategy="AUTO")
  15.       *
  16.       * @var integer $id
  17.       */ 
  18.     protected $id
  19.   
  20.     /**
  21.       * @ORM\Column(type="string", length="255")
  22.       *
  23.       * @var string $name
  24.       */ 
  25.     protected $name
  26.   
  27.     /**
  28.       * @ORM\Column(type="datetime", name="created_at")
  29.       *
  30.       * @var DateTime $createdAt
  31.       */ 
  32.     protected $createdAt
  33.   
  34.     /**
  35.       * Gets the id.
  36.       *
  37.       * @return integer The id.
  38.       */ 
  39.     public function getId() 
  40.     { 
  41.         return $this->id; 
  42.     } 
  43.   
  44.     /**
  45.       * Gets the role name.
  46.       * 
  47.       * @return string The name.
  48.       */ 
  49.     public function getName() 
  50.     { 
  51.         return $this->name; 
  52.     } 
  53.   
  54.     /**
  55.       * Sets the role name.
  56.       *
  57.       * @param string $value The name.
  58.       */ 
  59.     public function setName($value
  60.     { 
  61.         $this->name = $value
  62.     } 
  63.   
  64.     /**
  65.       * Gets the DateTime the role was created.
  66.       *
  67.       * @return DateTime A DateTime object.
  68.       */ 
  69.     public function getCreatedAt() 
  70.     { 
  71.         return $this->createdAt; 
  72.     } 
  73.   
  74.     /**
  75.       * Consturcts a new instance of Role.
  76.       */ 
  77.     public function __construct() 
  78.     { 
  79.         $this->createdAt = new \DateTime(); 
  80.     } 
  81.   
  82.     /**
  83.       * Implementation of getRole for the RoleInterface.
  84.       *
  85.       * @return string The role.
  86.       */ 
  87.     public function getRole() 
  88.     { 
  89.         return $this->getName(); 
  90.     } 
  91. }

The Role entity is quite simple. There is only one property named name that houses the name of the role. The class also implements the RoleInterface interface by supplying a getRole method, which just returns the name of the role. Now that the Role class has been created, we need to update the User entity. Open up the User.php file in the src/Company/BlogBundle/Entity. Here are the relevant changes to the code:
Role实体类非常简单。只有一个名为name的属性,用以存放角色名。该类还通过提供getRole方法来实现RoleInterface接口,该方法只是返回角色名。现在Role类已经被创建,接下来我们还需要更新User实体类。打开src/Company/BlogBundle/Entity 中的User.php文件。以下是改变的代码:


  1. namespace Company\BlogBundle\Entity; 
  2.   
  3. use Doctrine\Common\Collections\ArrayCollection; 
  4. use Doctrine\ORM\Mapping as ORM; 
  5. use Symfony\Component\Security\Core\User\UserInterface; 
  6.   
  7. /** 
  8.  * @ORM\Entity 
  9.  * @ORM\Table(name="user") 
  10.  */ 
  11. class User implements UserInterface 
  12.     // ... 
  13.   
  14.     /** 
  15.      * @ORM\Column(type="string", length="255") 
  16.      * 
  17.      * @var string username 
  18.      */ 
  19.     protected $username
  20.   
  21.     /** 
  22.      * @ORM\Column(type="string", length="255") 
  23.      * 
  24.      * @var string password 
  25.      */ 
  26.     protected $password
  27.   
  28.     /** 
  29.      * @ORM\Column(type="string", length="255") 
  30.      * 
  31.      * @var string salt 
  32.      */ 
  33.     protected $salt
  34.   
  35.     /** 
  36.      * @ORM\ManyToMany(targetEntity="Role") 
  37.      * @ORM\JoinTable(name="user_role", 
  38.      *     joinColumns={@ORM\JoinColumn(name="user_id", referencedColumnName="id")}, 
  39.      *     inverseJoinColumns={@ORM\JoinColumn(name="role_id", referencedColumnName="id")} 
  40.      * ) 
  41.      * 
  42.      * @var ArrayCollection $userRoles 
  43.      */ 
  44.     protected $userRoles
  45.   
  46.     // ...  
  47.   
  48.     /** 
  49.      * Gets the username. 
  50.      * 
  51.      * @return string The username. 
  52.      */ 
  53.     public function getUsername() 
  54.     { 
  55.         return $this->username; 
  56.     } 
  57.   
  58.     /** 
  59.      * Sets the username. 
  60.      * 
  61.      * @param string $value The username. 
  62.      */ 
  63.     public function setUsername($value
  64.     { 
  65.         $this->username = $value
  66.     } 
  67.   
  68.     /** 
  69.      * Gets the user password. 
  70.      * 
  71.      * @return string The password. 
  72.      */ 
  73.     public function getPassword() 
  74.     { 
  75.         return $this->password; 
  76.     } 
  77.   
  78.     /** 
  79.      * Sets the user password. 
  80.      * 
  81.      * @param string $value The password. 
  82.      */ 
  83.     public function setPassword($value
  84.     { 
  85.         $this->password = $value
  86.     } 
  87.   
  88.     /** 
  89.      * Gets the user salt. 
  90.      * 
  91.      * @return string The salt. 
  92.      */ 
  93.     public function getSalt() 
  94.     { 
  95.         return $this->salt; 
  96.     } 
  97.   
  98.     /** 
  99.      * Sets the user salt. 
  100.      * 
  101.      * @param string $value The salt. 
  102.      */ 
  103.     public function setSalt($value
  104.     { 
  105.         $this->salt = $value
  106.     } 
  107.   
  108.     /** 
  109.      * Gets the user roles. 
  110.      * 
  111.      * @return ArrayCollection A Doctrine ArrayCollection 
  112.      */ 
  113.     public function getUserRoles() 
  114.     { 
  115.         return $this->userRoles; 
  116.     } 
  117.   
  118.     /** 
  119.      * Constructs a new instance of User 
  120.      */ 
  121.     public function __construct() 
  122.     { 
  123.         $this->posts = new ArrayCollection(); 
  124.         $this->userRoles = new ArrayCollection(); 
  125.         $this->createdAt = new \DateTime(); 
  126.     } 
  127.   
  128.     /** 
  129.      * Erases the user credentials. 
  130.      */ 
  131.     public function eraseCredentials() 
  132.     { 
  133.         $this->setPassword(''); 
  134.     } 
  135.   
  136.     /** 
  137.      * Gets an array of roles. 
  138.      *  
  139.      * @return array An array of Role objects 
  140.      */ 
  141.     public function getRoles() 
  142.     { 
  143.         return $this->getUserRoles()->toArray(); 
  144.     } 
  145.   
  146.     /** 
  147.      * Compares this user to another to determine if they are the same. 
  148.      *  
  149.      * @param UserInterface $user The user 
  150.      * @return boolean True if equal, false othwerwise. 
  151.      */ 
  152.     public function equals(UserInterface $user
  153.     { 
  154.         return md5($this->getUsername()) == md5($user->getUsername()); 
  155.     } 
  156.   
  157.     // ... 
  158.   
  159. }

If you would rather just download the new User.php than make all of the changes by hand, you can find it here. Nothing special in our new User entity really. We have conformed to the UserInterface interface and also set up a new many-to-many relationship with the Role entity. There is also an AdvancedUserInterface that provides even more functionality, but I am not going to use it in this tutorial. You can read more about it here.
如果您不想自己手工地去更改User.php而只想下载它的话,您可以在这里找到它。我们的新User实体类中真没什么特别之处。我们已经实现了UserInterface接口,并且还新设置了一个与Role实体之间的多对多关系。AdvancedUserInterface接口甚至提供了更多的功能,但在本教程中我并不打算使用它,有关它的更多详情,您可能参阅这里

Now that our entities have been updated, we need to update our fixtures so that we can update the data in our database. Open up the FixtureLoader.php file in the src/Company/BlogBundle/DataFixtures/ORM folder. Here are the relevant code changes:
现在我们的实体类都已经被更新了,接下来我们需要更新我们的fixture以便我们可以更新我们数据库中的数据。打开src/Company/BlogBundle/DataFixtures/ORM的FextureLoader.php文件,下面是文件中的代码:


  1. namespace Company\BlogBundle\DataFixtures\ORM; 
  2.   
  3. use Doctrine\Common\DataFixtures\FixtureInterface; 
  4. use Company\BlogBundle\Entity\Category; 
  5. use Company\BlogBundle\Entity\Post; 
  6. use Company\BlogBundle\Entity\Tag; 
  7. use Company\BlogBundle\Entity\User; 
  8. use Company\BlogBundle\Entity\Role; 
  9. use Symfony\Component\Security\Core\Encoder\MessageDigestPasswordEncoder; 
  10.   
  11. class FixtureLoader implements FixtureInterface 
  12.     public function load($manager
  13.     { 
  14.         // create the ROLE_ADMIN role 
  15.         $role = new Role(); 
  16.         $role->setName('ROLE_ADMIN'); 
  17.   
  18.         $manager->persist($role); 
  19.   
  20.         // create a user 
  21.         $user = new User(); 
  22.         $user->setFirstName('John'); 
  23.         $user->setLastName('Doe'); 
  24.         $user->setEmail('john@example.com'); 
  25.         $user->setUsername('john.doe'); 
  26.         $user->setSalt(md5(time())); 
  27.   
  28.         // encode and set the password for the user, 
  29.         // these settings match our config 
  30.         $encoder = new MessageDigestPasswordEncoder('sha512', true, 10); 
  31.         $password = $encoder->encodePassword('admin'$user->getSalt()); 
  32.         $user->setPassword($password); 
  33.   
  34.         $user->getUserRoles()->add($role); 
  35.   
  36.         $manager->persist($user); 
  37.   
  38.         // ... 
  39.   

A new role with the name ROLE_ADMIN has been created. We have also added a username of john.doe to the user and set a random salt. The only new functionality here is the encoding of the password. Remember back in our security.encoders configuration entry we setup the configuration for a MessageDigestPasswordEncoder. Here we are creating an instance of that class and passing in the parameters as configured in our security.encoders entry. Then we are encoding the password “admin” and setting that encoded password as the password for our user. We have to encode the password using the same settings as we have told the security component of the framework to use or else we wont be able to supply credentials for authentication.
我们创建了一个名为ROLE_ADMIN的新角色,还新增了一个用户名是john.doe的用户并设置了随机salt。这里唯一的新功能是加密密码。回顾一下我们的安全编码器配置条目,我们为MessageDigestPasswordEncoder设置配置。在这里,我们创建了一个该类的实例,并且将我们安全编码器条目中的配置作为参数发送给该实例。然后我们对密码“admin”进行编码,并将经过编码的密码作为用户密码。我们必须按我们告诉框架安全组件相同的设置去对我们的密码进行编码,否则我们将不能支持认证所需的证书。

Before we can update our routing and create new controllers and views, we have to run a few commands from the console. Open up a terminal and change to your base project directory. First run the following command to update our database schema to match that of our entities.
在我们可以更新我们的路由和创建新的控制器和视图之前,我们必须从控制台上运行一些命令。打开终端,进入项目根目录。首先运行下面的命令去更新我们的数据库方案,以便匹配我们的实体。


  1. php app/console doctrine:schema:update --force 

Next, run the following command to clear out the current contents of the database and reload the data using the new updated fixtures.
接下来,运行下面的命令去清除当前数据库内容,使用更新过的新fixtures重新加载数据:


  1. php app/console doctrine:data:load

At this point we have our security configuration, entities and data all set up. Now we are going to update our routing and create some new controllers and views to implement the form login. Open up the routing.yml file in the src/Company/BlogBundle/Resources/config directory. Add the following routes to the top of the file.
这时,我们的安全配置,实体和数据全部都设置好了。现在我们打算更新我们的路由,并创建一些新的控制器和视图去实现表单登录。打开src/Company/BlogBundle/Resources/config目录中的routing.yml文件。在文件顶部添加下面的路由。


  1. _security_login: 
  2.     pattern:  /login 
  3.     defaults: { _controller: BlogBundle:Security:login } 
  4.  
  5. _security_check: 
  6.     pattern:  /login_check 
  7.  
  8. _security_logout: 
  9.     pattern:  /logout 
  10.  
  11. admin_home: 
  12.     pattern:  /admin/ 
  13.     defaults: { _controller: BlogBundle:Admin:index } 

We have added some special security routes to our routing file. These routes will be used by us as well as the security component of the symfony2 framework. You might be wondering why we did not define controllers for two of the routes. Remember how I said that the security component worked by listening to the core.security event? As a result of this, the controllers for these routes will never need to be resolved because the security component will intercept the request and handle it. So we can safely use the route names _security_check and _security_logout in our templates. Also, we have added the admin_home route which will act as the home page for the admin section of our application. Since we configured the security.access_control entry of our configuration to only allow a user with the role ROLE_ADMIN to access any route pattern that started with “/admin/”, the admin_home route is the secured route we will be trying to access in just a bit.
我们已经添加了一些特殊的安全路由到我们的路由文件中。这些路由将会象Symfony2框架的安全组件一样被我们使用。您也许会奇怪为什么我们不为其中的两条路由定义控制器?记得我所说的,安全组件是通过监听core.security事件来工作的吗?正因如此,这些路由的控制器是不需要的,因为安全组件会截获并处理这些请求。因此,我们可以在我们的模板中安全地使用名为_security_check和_security_logout的路由。另外,我们还为应用程序管理部分添加了admin_home路由指向其主页。当我们设置我们配置中的security.access_control条目时,只允许拥有ROLE_ADMIN角色的用户访问所有以“/admin/”开头的路由,admin_home路由就是我们要访问的安全路由。

You may have also noticed that we are going to be creating two new controllers, the SecurityController and AdminController, so lets do that now. Create a new file named AdminController.php in the src/Company/BlogBundle/Controller directory. Here is the code for the AdminController class.
您也许还注意到我们将要创建两个新的控制器,SecurityController和AdminController,那么让我们现在就开始吧。在src/Company/BlogBundle/Controller目录中创建一个名为AdminController.php的文件。以下是AdminController类的代码:


  1. namespace Company\BlogBundle\Controller; 
  2.   
  3. use Symfony\Bundle\FrameworkBundle\Controller\Controller; 
  4.   
  5. class AdminController extends Controller 
  6.     public function indexAction() 
  7.     { 
  8.         return $this->render('BlogBundle:Admin:index.html.twig'); 
  9.     } 

Nothing fancy about the indexAction. It simply renders the index.html.twig template. So lets create that now. Create a new folder in the src/Company/BlogBunde/Resources/views folder named Admin. In this new folder create a file named index.html.twig. Here is the template for the admin home page.
indexAction没有什么特别的,它只是简单地渲染index.html.twig模板。所以让我们现在就创建吧。在src/Company/BlogBunde/Resources/views文件夹中创建名为Admin的新文件夹。在该文件夹中创建一个名为index.html.twig的文件。下面是admin主页的模板:


  1. {% extends "BlogBundle::layout.html.twig" %} 
  2.   
  3. {% block title %} 
  4.     symfony2 Blog Tutorial | Admin | Home 
  5. {% endblock %} 
  6.   
  7. {% block content %} 
  8.     <h2> 
  9.         Welcome to the Admin Homepage {{ app.user.username }}! 
  10.     </h2> 
  11. {% endblock %} 

The only new thing in this template is the app.user template variable. The app.user variable is how you access the currently logged in user. So, in our application the app.user variable translates to an instance of Company\BlogBundle\Entity\User.
在该模板中唯一的新东西是app.user模板变量。app.user变量是您当前使用的登录用户。因此,在我们的应用程序里app.user变量将被转换成Company\BlogBundle\Entity\User的一个实例。

Now that we have our secured page created lets create the SecurityController. Create a new file named SecurityController.php in the src/Company/BlogBundle/Controller folder. Here is the code for the SecurityController class.
现在我们已经创建了我们的安全页面,让我们创建SecurityController。在src/Company/BlogBundle/Controller文件夹中,创建一个名为SecurityController.php的新文件。下面是SecurityController类的代码:


  1. namespace Company\BlogBundle\Controller; 
  2.   
  3. use Symfony\Bundle\FrameworkBundle\Controller\Controller; 
  4. use Symfony\Component\Security\Core\SecurityContext; 
  5.   
  6. class SecurityController extends Controller 
  7.     public function loginAction() 
  8.     { 
  9.         if ($this->get('request')->attributes->has(SecurityContext::AUTHENTICATION_ERROR)) { 
  10.             $error = $this->get('request')->attributes->get(SecurityContext::AUTHENTICATION_ERROR); 
  11.         } else { 
  12.             $error = $this->get('request')->getSession()->get(SecurityContext::AUTHENTICATION_ERROR); 
  13.         } 
  14.   
  15.         return $this->render('BlogBundle:Security:login.html.twig'array
  16.             'last_username' => $this->get('request')->getSession()->get(SecurityContext::LAST_USERNAME), 
  17.             'error' => $error 
  18.         )); 
  19.     } 

We created a loginAction method which is what we mapped the _security_login route to. In this action we are first checking for an error and then rendering the login.html.twig template as a response. The error checking may look weird, but basically we are just checking to see if we have been forwarded or redirected to this action. If we have, then we are getting the exception that was generated.
我们创建的loginAction方法,是由_security_login路由映射的。在这个动作里我们首先进行错误检查,然后渲染login.html.twig模板来响应。这个错误检查看起来怪怪的,但是基本上我们只是检查,看看是否转发或重定向该动作。如果我们查出错误,那么我们将得到产生的异常。

The security component will handle all of the credential validation for us, but we must create the template and supply the necessary parameters. In order for the security component to do the validation for us we must submit a form with _username and _password fields to the _security_check route. Lets create a template that does this. Create a new folder in the src/Company/BlogBunde/Resources/views folder named Security. In this new folder create a file named login.html.twig. Here is the code for the new login template.
安全组件会为我们处理所有证书的有效性,不过我们必须创建模板并提供必要的参数。为了安全组件能为我们提供有效性检测,我们必须提交一个带有_username和_password字段的表单到_sercurity_check路由。在src/Company/BlogBunde/Resources/views文件夹中创建一个名为Security的新文件夹。在该文件夹中创建一个名为login.html.twig的文件。以下是这个新登录模板的代码:


  1. {% extends "BlogBundle::layout.html.twig" %} 
  2.   
  3. {% block title %} 
  4.     symfony2 Blog Tutorial | Login 
  5. {% endblock %} 
  6.   
  7. {% block content %} 
  8.     {% if error %} 
  9.         <div class="error">{{ error.message }}</div> 
  10.     {% endif %} 
  11.   
  12.     <form action="{{ path('_security_check') }}" method="POST"> 
  13.         <table> 
  14.             <tr> 
  15.                 <td> 
  16.                     <label for="username">Username:</label> 
  17.                 </td> 
  18.                 <td> 
  19.                     <input type="text" id="username" name="_username" value="{{ last_username }}" /> 
  20.                 </td> 
  21.             </tr> 
  22.             <tr> 
  23.                 <td> 
  24.                     <label for="password">Password:</label> 
  25.                 </td> 
  26.                 <td> 
  27.                     <input type="password" id="password" name="_password" /> 
  28.                 </td> 
  29.             </tr> 
  30.         </table> 
  31.         <input type="submit" name="login" value="submit" /> 
  32.     </form> 
  33. {% endblock %} 

There is nothing that you should not understand in this template. It is simply submitting our form to the _security_check route. When we submit a form to this route the security component of symfony2 will intercept the request and handle the user authentication for us. If the user is authenticated then he will be redirected to the initial destination otherwise he will be redirected back to the login page.
在这个模板里没有什么您不好理解的东西。它只是简单地将我们的表单提交到_security_check路由。当我们提交表单到这个路由时,Symfony2的安全组件就会截获这个请求,然后帮我们处理用户认证。如果用户认证通过,那么他会被重定向到初始的目标,否则的话,他将被重定向到登录页。

At some point you may want to logout of the application. Lets create a logout link that is only shown when a user has logged in. Open the layout.html.twig file in the src/Company/BlogBundle/Resources/views directory. Here is the relevant code change to that file.
在某些时候您可能想退出程序。让我们创建一个退出(Logout)链接,该链接只有在用户登录后才会显示。打开src/Company/BlogBundle/Resources/views目录下的layout.html.twig文件,以下是该文件改动的相关代码:


  1. // ... 
  2.   
  3. {% block body %} 
  4.     <div id="container"> 
  5.         <header class="clearfix"> 
  6.             <h1> 
  7.                 symfony2 Blog Tutorial 
  8.             </h1> 
  9.             <nav> 
  10.                 <ul> 
  11.                     <li> 
  12.                         <a href="{{ path('show_page', { 'page' : 'about' }) }}"> 
  13.                             About 
  14.                         </a> 
  15.                     </li> 
  16.                     {% if is_granted('IS_AUTHENTICATED_FULLY') %} 
  17.                         <li> 
  18.                             <a href="{{ path('_security_logout') }}"> 
  19.                                 Logout 
  20.                             </a> 
  21.                         </li> 
  22.                     {% endif %} 
  23.                 </ul> 
  24.             </nav> 
  25.         </header> 
  26.   
  27.         // ... 

We have used a new twig method named is_granted to check to see if the current user is a specific role. The role we check is IS_AUTHENTICATED_FULLY. This is a special role that a user will have if they have been authenticated by the symfony2 security component. If the user has this role then we add another item to our navigation which is a link to the _security_logout route that we defined earlier.
我们使用了一个名为is_granted的新twig方法去检查当前用户是否拥有指定角色。我们检查的角色是IS_AUTHENTICATED_FULLY。这是一个特殊角色,一旦用户被symfony2安全组件认证通过后就会拥有。如果用户拥有该角色,那么我们在我们的导航栏上添加一个新的导航项,该导航项指向我们先前定义的_security_logout路由。

We are finally ready to try our new secured page. Before we do I would suggest you clear your cache. There is no clear cache command yet, but you can either manually delete all of the files and folders in the app/cache directory or if you are on linux/mac you can run the command rm -rf app/cache/* from your base project directory. [Update: There is now a clear cache command. It is php app/console cache:clear.] Now that you have cleared your cache navigate to the /admin/ path in your web browser. You should be redirected to the login page which looks similar to the following image:
最后我们准备测试一个我们的新安全页面。在此之前,我建议您清一下您的缓存。虽然没有清缓存的命令,但是您可以手工删除app/cache下的所有文件和文件夹。如果您在linux/mac环境下,您可以在您项目根目录中运行rm –rf app/cache/*命令来清除缓存。【更新:现在已经有了清缓存的命令:php app/console cache:clear】。现在您已经清除了缓存,在您的网页浏览器中访问/admin/。您将被重定向到登录页,如下图所示:

Symfony2博客应用程序教程:第四部分-安全介绍

symfony2 Blog Application Tutorial Login Page (Click for larger)
Symfony2博客应用程序教程登录页面

Enter john.doe for the username and admin for the password. When you submit these credentials you should then be redirected to the admin home page which looks similar to this image:
在用户名处输入john.doe,密码使用admin登录。当你提交这个证书时,你应该会被重定向到admin的主页,如下图所示:

Symfony2博客应用程序教程:第四部分-安全介绍

symfony2 Blog Application Tutorial Admin Homepage (Click for larger)
Symfony2博客应用程序教程管理首页

You should also see the Logout link in the header navigation. If you click the Logout link you should be logged out and then redirected to the home page. Whew! That was a lot of work and a lot of trial and error on my part that you didn’t see! We have successfully secured a route prefix in our application. As I said earlier, I would strongly recommend using the FOSUserBundle in a production application. It has a lot more functionality than I have described in this tutorial. I hope you were able to follow along. If any part needs clarification, just ask. Leave me a comment and let me know what you would like to see next. As of right now, I am planning on exploring the symfony2 container and other internals. Until next time…
您也应该看到了顶部导航栏中的退出(Logout)链接。如果您点击该链接,您将退出并重定向到首页。哎呀!您没有看到,在我这部分内容中有着大量的工作、尝试和错误!我们成功地在我们的应用程序中用路由前缀来保证安全。正如我先前所说,我强烈推荐在生产环境的应用程序中使用FOSUserBundle。它有着比我在本教程中所说多得多的功能。我希望您能够了解。如果任何部分需要澄清,只管问。给我留个评论,让我知道您接下来想看什么。到目前为止,我计划说明Symfony2容器和其它内部机制。直到下一次...
 

Update: I created a test for the admin index action that involves logging in using the security bundle. Unfortunately at this time sessions are not supported in the testing framework, so the test will always fail. I am only posting this so you all can learn more about testing. You can follow the comments to understand what is going on. If you want to you could edit your config_test.yml file to change your security configuration and create an http basic user and supply the credentials with the request. Right now this is really the only way to test secured routes. An example of this can be found in the Testing section of the symfony2 book on the symfony2 website.
更新:我为admin的index动作创建了一个涉及登录的测试,该测试使用SecurityBundle。不幸的是,在测试框架中这次不支持会话,所以测试总是失败。我将它贴在这里,以便您可以学到更多有关测试的内容。您可以跟贴去理解这是怎么回事。如果您打算通过编辑您的config_test.yml来改变您的安全配置,那么创建一个HTTP的基本用户,并提供请求的证书,是测试安全路由的唯一方式。这样的例子可以在Symfony2网站上的Symfony2指导书中的测试章节中找到。


  1. namespace Company\BlogBundle\Tests\Controller; 
  2.   
  3. use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; 
  4.   
  5. class AdminControllerTest extends WebTestCase 
  6.     public function testIndex() 
  7.     { 
  8.         $client = $this->createClient(); 
  9.         $client->followRedirects(true); 
  10.   
  11.         // request the index action 
  12.         $crawler = $client->request('GET''/admin/'); 
  13.   
  14.         $this->assertEquals(200, $client->getResponse()->getStatusCode()); 
  15.   
  16.         // select the login form 
  17.         $form = $crawler->selectButton('submit')->form(); 
  18.   
  19.         // submit the form with bad credentials 
  20.         $crawler = $client->submit( 
  21.             $form
  22.             array
  23.                 '_username' => 'john.doe'
  24.                 '_password' => 'wrong_password' 
  25.             ) 
  26.         ); 
  27.   
  28.         // response should be success 
  29.         $this->assertTrue($client->getResponse()->isSuccessful()); 
  30.   
  31.         // we should have been redirected back to the login page because 
  32.         // invalid credentials were supplied 
  33.         $this->assertTrue($crawler->filter('title:contains("Login")')->count() > 0); 
  34.   
  35.         // select the login form 
  36.         $form = $crawler->selectButton('submit')->form(); 
  37.   
  38.         // submit the form with valid credentials 
  39.         $crawler = $client->submit( 
  40.             $form
  41.             array
  42.                 '_username' => 'john.doe'
  43.                 '_password' => 'admin' 
  44.             ) 
  45.         ); 
  46.   
  47.         // response should be success 
  48.         $this->assertTrue($client->getResponse()->isSuccessful()); 
  49.   
  50.         // check the title of the page matches the admin home page 
  51.         $this->assertTrue($crawler->filter('title:contains("Admin | Home")')->count() > 0); 
  52.   
  53.         // check that the logout link exists 
  54.         $this->assertTrue($crawler->filter('a:contains("Logout")')->count() > 0); 
  55.     } 
  56. }
上一篇:虚拟化的下一站是软件定义的数据中心吗?


下一篇:滴滴派单算法_从算法模型思路到评估方案 - 详解