<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Symfony中文教程 &#187; security</title>
	<atom:link href="http://www.newlifeclan.com/symfony/archives/tag/security/feed" rel="self" type="application/rss+xml" />
	<link>http://www.newlifeclan.com/symfony</link>
	<description>站在巨人肩膀上的phpweb框架</description>
	<lastBuildDate>Fri, 12 Dec 2025 00:58:27 +0000</lastBuildDate>
	<language>zh-CN</language>
		<sy:updatePeriod>hourly</sy:updatePeriod>
		<sy:updateFrequency>1</sy:updateFrequency>
	<generator>https://wordpress.org/?v=4.0.38</generator>
	<item>
		<title>KU案例2之10给数据库中的用户添加角色</title>
		<link>http://www.newlifeclan.com/symfony/archives/294</link>
		<comments>http://www.newlifeclan.com/symfony/archives/294#comments</comments>
		<pubDate>Thu, 09 Apr 2015 14:37:48 +0000</pubDate>
		<dc:creator><![CDATA[napoleon]]></dc:creator>
				<category><![CDATA[实战教程]]></category>
		<category><![CDATA[role]]></category>
		<category><![CDATA[security]]></category>
		<category><![CDATA[symfony2.6]]></category>

		<guid isPermaLink="false">http://www.newlifeclan.com/symfony/?p=294</guid>
		<description><![CDATA[<p>很多时候，所有的用户只能得到一个角色：ROLE_USER，这是因为我们的User entity中的getRol [&#8230;]</p>
<p><a rel="nofollow" href="http://www.newlifeclan.com/symfony/archives/294">KU案例2之10给数据库中的用户添加角色</a>，首发于<a rel="nofollow" href="http://www.newlifeclan.com/symfony">Symfony中文教程</a>。</p>
]]></description>
				<content:encoded><![CDATA[<p>很多时候，所有的用户只能得到一个角色：ROLE_USER，这是因为我们的User entity中的getRoles()的默认返回值所导致。</p>
<p><span id="more-294"></span></p>
<p>所以我们要在User entity中添加一个字段roles，并且把它设置成json_array类型：</p><pre class="crayon-plain-tag">// src/Acme/UserBundle/Entity/User.php
// ...

/**
 * @ORM\Column(type="json_array")
 */
private $roles = array();</pre><p>json_array()允许我们存储一个字符串数组到一个字段里。在数据库中，这些数组被存储为JSON字符串。Doctrine来负责array和JSON之间的转换工作。</p>
<p>现在我们要更新getRoles()方法，并添加setRoles方法：</p><pre class="crayon-plain-tag">public function getRoles()
{
    return $this-&gt;roles;
}

public function setRoles(array $roles)
{
    $this-&gt;roles = $roles;

    // allows for chaining
    return $this;
}</pre><p>很酷，但是现在的方式，用户的用户角色很可能为空。他们变成了僵尸用户，他们能够登录，但不能访问页面，我们不能让这种事情发生。</p>
<p>应该在getRoles()添加一些逻辑，来保证每个用户都有ROLE_USER。</p><pre class="crayon-plain-tag">public function getRoles()
{
    $roles = $this-&gt;roles;
    $roles[] = 'ROLE_USER';

    return array_unique($roles);
}</pre><p>更新一下数据库</p><pre class="crayon-plain-tag">php app/console doctrine:schema:update --force</pre><p>让我们添加一个ROLE_ADMIN角色的用户</p><pre class="crayon-plain-tag">// src/Acme/UserBundle/DataFixtures/ORM/LoadUsers.php
// ...

public function load(ObjectManager $manager)
{
    // ...
    $manager-&gt;persist($user);

    $admin = new User();
    $admin-&gt;setUsername('wayne');
    $admin-&gt;setPassword($this-&gt;encodePassword($admin, 'waynepass'));
    $admin-&gt;setRoles(array('ROLE_ADMIN'));
    $manager-&gt;persist($admin);

    $manager-&gt;flush();
}</pre><p>运行下面命令添加数据</p><pre class="crayon-plain-tag">php app/console doctrine:fixtures:load</pre><p>现在我们用管理员登陆，debug条会显示我们有ROLE_USER和ROLE_ADMIN角色。</p>
<h2>使用AdvancedUserBundle禁用不活跃用户</h2>
<p>我们可以对一些不怎么来网站的用户进行禁用操作。</p>
<p>添加一个isActive的boolean字段到User entity。如果这个字段为假，那么程序会阻止用户进行身份验证。不要忘了在命令行运行 doctrine:generate:entities 来生成get和set方法：</p><pre class="crayon-plain-tag">/ src/Acme/UserBundle/Entity/User.php
// ...

/**
 * @var bool
 *
 * @ORM\Column(type="boolean")
 */
private $isActive = true;

// ...
// write or generate your getIsActive and setIsActive methods...</pre><p>在那之后，更新我们的数据库字段</p><pre class="crayon-plain-tag">php app/console doctrine:schema:update --force</pre><p>现在isActive字段已经存在了，但是它还不能的在登陆中使用。为了使他能工作，要将User类的UserInterface替换成implements AdvancedUserInterface；</p><pre class="crayon-plain-tag">// src/Acme/UserBundle/Entity/User.php
// ...

use Symfony\Component\Security\Core\User\AdvancedUserInterface;

class User implements AdvancedUserInterface
{
    // ...
}</pre><p>这里 AdvancedUserInterface继承UserInterface。</p>
<p>新的接口是一个更强大的UserInterface他需要四个额外的方法。如果这些方法返回false，Symfony将阻止用户登录。为了证明这一点，除了isAccountNonLocked都返回true：</p><pre class="crayon-plain-tag">// src/Acme/UserBundle/Entity/User.php
// ...

public function isAccountNonExpired()
{
    return true;
}

public function isAccountNonLocked()
{
    return false;
}

public function isCredentialsNonExpired()
{
    return true;
}

public function isEnabled()
{
    return true;
}</pre><p>现在登录不太好玩了：我们堵住了有用的信息。</p>
<p>这里所有的方法都在做同样的事情：就是阻止登陆。如果你想你可以翻译每个用户带来的不同信息。设置每一个都为true，除了isEnabled。让他返回isActive属性的值：</p><pre class="crayon-plain-tag">/ src/Acme/UserBundle/Entity/User.php
// ...

public function isAccountNonLocked()
{
    return true;
}

public function isEnabled()
{
    return $this-&gt;getIsActive();
}</pre><p>如果isActive为false，这应该阻止用户登录。</p>
<p>我们更改不活跃的用户：</p><pre class="crayon-plain-tag">// src/Acme/UserBundle/DataFixtures/ORM/LoadUsers.php
// ...

public function load(ObjectManager $manager)
{
    // ...
    $admin-&gt;setIsActive(false);
    // ...
}</pre><p>运行命令：</p><pre class="crayon-plain-tag">php app/console doctrine:fixtures:load</pre><p>当我们尝试登录，程序自动阻止。爽！</p>
<p>哈哈，学会了吧。我们可以还原程序了，让用户能够登录进来。</p>
<p>&nbsp;</p>
<p><a rel="nofollow" href="http://www.newlifeclan.com/symfony/archives/294">KU案例2之10给数据库中的用户添加角色</a>，首发于<a rel="nofollow" href="http://www.newlifeclan.com/symfony">Symfony中文教程</a>。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.newlifeclan.com/symfony/archives/294/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>KU案例2之01基础的Security</title>
		<link>http://www.newlifeclan.com/symfony/archives/400</link>
		<comments>http://www.newlifeclan.com/symfony/archives/400#comments</comments>
		<pubDate>Mon, 30 Mar 2015 06:54:51 +0000</pubDate>
		<dc:creator><![CDATA[napoleon]]></dc:creator>
				<category><![CDATA[实战教程]]></category>
		<category><![CDATA[security]]></category>

		<guid isPermaLink="false">http://www.newlifeclan.com/symfony/?p=400</guid>
		<description><![CDATA[<p>接下来一个小时，我们将提升一个新的水平，针对symfony一些困难的领域如Security，表单和一些重要的D [&#8230;]</p>
<p><a rel="nofollow" href="http://www.newlifeclan.com/symfony/archives/400">KU案例2之01基础的Security</a>，首发于<a rel="nofollow" href="http://www.newlifeclan.com/symfony">Symfony中文教程</a>。</p>
]]></description>
				<content:encoded><![CDATA[<p>接下来一个小时，我们将提升一个新的水平，针对symfony一些困难的领域如Security，表单和一些重要的Doctrine，来进行分析和案例实战。</p>
<p>好了，我们可以先喝杯咖啡，伸伸懒腰，准备go！</p>
<p><span id="more-400"></span></p>
<h2>基础的Security</h2>
<p>Symfony的Security组件，实在是很强大。老实说，也非常的复杂。但是它能够链接一些其他的认证系统，比如像Facebook或者LDAP，他还可以从很多地方加载用户信息，比如一个数据库甚至是一个api。</p>
<p>令人失望的是，你想把这一切弄好其实并不容易。但是如果你知道每一块内容都是如何工作的，你会做出令人惊叹的事情来。再告诉你一些秘密武器，自定义你的认证系统会容易的多。</p>
<h2>认证（Authentication）和授权（Authorization）</h2>
<p>Security是两个部分：认证（Authentication）和授权（Authorization）。认证（Authentication）检查用户的凭证。他的工作不是去限制访问，它只想知道你是谁。</p>
<p>好了，打个比方你是一个高级别的人，想进入一个要塞，你要通过一个安全检查站，每个人都要检查，检查站里的系统（Security）会给你一张卡片（token）来代表你，这就是认证。进入这个地方后，你的卡片可能比标准的访问者能去的地方更多。</p>
<p>授权（Authorization）是Security的第二步，就好像一个一个锁着的门。你会问“这是我的token，我能进入这个门吗？”，Authorization会告诉你“能”或者“不能”。到了这时根本不管你是如何进来的或者说你是谁，只有你的token是管用的。</p>
<p>&nbsp;</p>
<h2>Security配置：security.yml</h2>
<p>让我们先来谈谈认证，这可能比授权更加复杂。这个Security配置文件在app/config/security.yml目录下，被config.yml所包含。</p><pre class="crayon-plain-tag"># app/config.config.yml
imports:
    # ...
    - { resource: security.yml }</pre><p>Security配置作为一个独立的配置的原因是，他很大也很难看。它和config.yml一起工作会变得很麻烦。</p>
<h2>Firewalls 配置（Security.yml）</h2>
<blockquote><p>注意：如果你的Security.yml文件大部分是空的，不用担心！你只需要下载本教程的Security.yml替换它既可。</p></blockquote>
<p>我们找到firewalls键：他是这个文件最重要的部分。防火墙代表了你的应用程序的认证层或者是安全检查站。删除login和dev防火墙部分，我们只留一个防火墙：</p><pre class="crayon-plain-tag"># app/config/security.yml
# ...

firewalls:
    secured_area:
        pattern:    ^/demo/secured/
        form_login:
            check_path: _security_check
            login_path: _demo_login
        logout:
            path:   _demo_logout
            target: _demo
        #anonymous: ~
        #http_basic:
        #    realm: "Secured Demo Area"</pre><p>他就像一个机器，让他看到每一个通过安全系统的人才是有意义的。所以我们需要改变pattern键为^/:</p><pre class="crayon-plain-tag"># app/config/security.yml
# ...

firewalls:
    secured_area:
        pattern:    ^/
        # ...</pre><p>现在每个来到我们的应用，所有的请求都使用这个防火墙来认证身份。让我们更改login_path键位/my-login-url网址：</p><pre class="crayon-plain-tag"># app/config/security.yml
# ...

firewalls:
    secured_area:
        pattern:    ^/
        form_login:
            check_path: _security_check
            login_path: /my-login-url
        # ...</pre><p>不用担心我修改的这个键或者其他的别的键值，因为他们会干扰你。我会在下面解释他们。</p>
<p>匿名访问（Security.yml）</p>
<p>现在开启anonymous键：</p><pre class="crayon-plain-tag"># app/config/security.yml
# ...

security:
    # ...
    firewalls:
        secured_area:
            pattern:    ^/
            # ...
            anonymous: ~</pre><p>让匿名用户进入网站，类似于让小喽喽也进入要塞。我不打算，让每一个页面都需要用户来登录才能访问。请记住，防火墙是找出你是谁，而不是拒绝访问的。</p>
<p>回到浏览器，不要刷新（要确保你没启用anonymous之前已经启用了浏览器并访问了页面）！首页，注意你的debug工具栏有个红色的小图标。他会提示你”You are not authenticated“；</p>
<p>现在刷新。他变成绿色的了，并告诉您”anon“。点击它显示我们新的”authenticated“。是的，是有点怪，但是匿名用户通过防火墙的认证。</p>
<p><span id="tran_0" class="copied">放心,很容易在代码中检查用户是否已经登录。</span><span id="tran_1" class="copied highLight">稍后我将向您展示。当然，我们还没有真正的完成登录工作，但是我们在一秒钟获得了一些细节。</span></p>
<p><a rel="nofollow" href="http://www.newlifeclan.com/symfony/archives/400">KU案例2之01基础的Security</a>，首发于<a rel="nofollow" href="http://www.newlifeclan.com/symfony">Symfony中文教程</a>。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.newlifeclan.com/symfony/archives/400/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>jobeet第十三天:安全</title>
		<link>http://www.newlifeclan.com/symfony/archives/377</link>
		<comments>http://www.newlifeclan.com/symfony/archives/377#comments</comments>
		<pubDate>Fri, 27 Mar 2015 08:19:28 +0000</pubDate>
		<dc:creator><![CDATA[napoleon]]></dc:creator>
				<category><![CDATA[实战教程]]></category>
		<category><![CDATA[jobeet]]></category>
		<category><![CDATA[security]]></category>
		<category><![CDATA[安全]]></category>

		<guid isPermaLink="false">http://www.newlifeclan.com/symfony/?p=377</guid>
		<description><![CDATA[<p>*这一系列文章来源于Fabien Potencier，基于Symfony1.4编写的Jobeet Tutiru [&#8230;]</p>
<p><a rel="nofollow" href="http://www.newlifeclan.com/symfony/archives/377">jobeet第十三天:安全</a>，首发于<a rel="nofollow" href="http://www.newlifeclan.com/symfony">Symfony中文教程</a>。</p>
]]></description>
				<content:encoded><![CDATA[<p>*这一系列文章来源于Fabien Potencier，基于Symfony1.4编写的<a href="http://symfony.com/legacy/doc/jobeet?orm=Doctrine">Jobeet Tutirual</a>。</p>
<h2><a id="user-content-实现程序的安全性" class="anchor" href="https://github.com/happen-zhang/symfony2-jobeet-tutorial/blob/master/chapter-13/chapter-13.md#%E5%AE%9E%E7%8E%B0%E7%A8%8B%E5%BA%8F%E7%9A%84%E5%AE%89%E5%85%A8%E6%80%A7"></a>实现程序的安全性</h2>
<p>应用程序的安全性是一个两步验证的过程，它的目标是阻止用户访问他/她不应该访问到的资源。实现安全性的第一步是<strong>认证（authentication）</strong>，安全认证系统会取得用户提交的一些标识符，然后通过这个标识符分辨出用户的身份。一旦系统分辨出用户的身份之后，下一步就是系统对用户进行<strong>授权（authorization）</strong>操作，这一步将决定用户能够访问哪些给定的资源（系统会检查用户是否有权限进行这个操作）。我们可以通过配置<em>app/config</em>目录下的<em>security.yml</em>文件来对应用程序的安全组件进行配置。为了实现我们应用的安全性，我们修改<em>scurity.yml</em>文件：</p>
<p><span id="more-377"></span></p><pre class="crayon-plain-tag"># app/config/security.yml
security:
    role_hierarchy:
        ROLE_ADMIN:       ROLE_USER
        ROLE_SUPER_ADMIN: [ROLE_USER, ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH]

    firewalls:
        dev:
            pattern:  ^/(_(profiler|wdt)|css|images|js)/
            security: false

        secured_area:
            pattern:    ^/
            anonymous: ~
            form_login:
                login_path:  /login
                check_path:  /login_check
                default_target_path: ibw_jobeet_homepage

    access_control:
        - { path: ^/admin, roles: ROLE_ADMIN }

    providers:
        in_memory:
            memory:
                users:
                    admin: { password: adminpass, roles: 'ROLE_ADMIN' }

    encoders:
        Symfony\Component\Security\Core\User\User: plaintext</pre><p>上面的配置会对网站的<em>/admin</em>部分（所有以<em>/admin</em>作为开头的url）进行安全性保护，并且只有<em>ROLE_ADMIN</em>角色的用户才能访问到<em>/admin</em>（可以查看<em>security.yml</em>中的<em>access_controller</em>部分）。在这个例子中，<em>admin</em>用户被定义在<em>security.yml</em>中（<em>providers</em>部分），而且<em>admin</em>用户的密码没有被编码（即没有经过加密算法处理过）（<em>encoders</em>部分）。对于用户认证，我们需要使用一个传统的登录表单，我们需要去实现它。首先我们需要创建两个路由：一个用来显示登录表单（例如：<em>/login</em>），另一个则用来处理提的交登录表单（例如：<em>/login_check</em>）：</p><pre class="crayon-plain-tag"># src/Ibw/JobeetBundle/Resources/config/routing.yml
login:
    pattern:   /login
    defaults:  { _controller: IbwJobeetBundle:Default:login }
login_check:
    pattern:   /login_check

# ...</pre><p>我们不需要为<em>/login_check</em> URL实现一个控制器，因为防火墙（firewall）会自动捕捉并处理所有提交到<em>/login_check</em> URL的表单。我们需要做的就是创建一个路由（<em>/login_check</em>），我们会在登录表单的模板中使用它，这样才能把登录表单提交到<em>/login_check</em>。</p>
<p>下一步我们来创建显示登录表单的<em>action</em>：</p><pre class="crayon-plain-tag">// src/Ibw/JobeetBundle/Controller/DefaultController.php
namespace Ibw\JobeetBundle\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\Security\Core\SecurityContext;

class DefaultController extends Controller
{
    // ...

    public function loginAction()
    {
        $request = $this-&gt;getRequest();
        $session = $request-&gt;getSession();

        // get the login error if there is one
        if ($request-&gt;attributes-&gt;has(SecurityContext::AUTHENTICATION_ERROR)) {
            $error = $request-&gt;attributes-&gt;get(SecurityContext::AUTHENTICATION_ERROR);
        } else {
            $error = $session-&gt;get(SecurityContext::AUTHENTICATION_ERROR);
            $session-&gt;remove(SecurityContext::AUTHENTICATION_ERROR);
        }

        return $this-&gt;render('IbwJobeetBundle:Default:login.html.twig', array(
            // last username entered by the user
            'last_username' =&gt; $session-&gt;get(SecurityContext::LAST_USERNAME),
            'error'         =&gt; $error,
        ));
    }
}</pre><p>当用户提交了表单，安全系统会自动处理提交的表单。如果用户提交了一个无效的用户名或者密码，那么这个操作就会从系统中读取出提交表单的错误信息，并把这个错误信息反馈给用户。我们的工作仅仅只有显示出一个登录表单和可能出现的登录错误信息，而安全系统则会对用户提交上来的用户名和密码进行验证。</p>
<p>最后，我们来创建模板：</p><pre class="crayon-plain-tag">&lt;!-- src/Ibw/JobeetBundle/Resources/views/Default/login.html.twig --&gt;
{% if error %}
    &lt;div&gt;{{ error.message }}&lt;/div&gt;
{% endif %}

&lt;form action="{{ path('login_check') }}" method="post"&gt;
    &lt;label for="username"&gt;Username:&lt;/label&gt;
    &lt;input type="text" id="username" name="_username" value="{{ last_username }}" /&gt;

    &lt;label for="password"&gt;Password:&lt;/label&gt;
    &lt;input type="password" id="password" name="_password" /&gt;

    &lt;button type="submit"&gt;login&lt;/button&gt;
&lt;/form&gt;</pre><p>现在，如果我们去访问http://jobeet.local/app_dev.php/admin/dashboard URL，我们会看到一个登录表单，我们需要输入定义在<em>security.yml</em>中的用户名和密码（<em>admin/adminpass</em>）才能进入<em>admin</em>部分。</p>
<p>&nbsp;</p>
<h2>User Providers</h2>
<p>在认证过程中，用户提交了一组登录凭证（通常是用户名和密码）。认证系统的工作就是在用户数据列表中逐个匹配登录凭证。那么用户数据列表从何而来呢？</p>
<p>在<em>Symfony2</em>中，用户数据列表可以存放在任何地方，一个配置文件，一个数据库表，一个web service接口，或者是其它你能想象得到的地方。为认证系统提供一个或者多个用户的类或接口就叫做“<em>user provider</em>”。<em>Symfony2</em> 自带了两种最常见的<em>user provider</em>：一种是使用<strong>配置文件</strong>，另一种是使用<strong>数据库表</strong>。</p>
<p>在上面的例子中，我们把用户列表存放在配置文件里：</p><pre class="crayon-plain-tag"># app/config/security.yml
# ...

providers:
    in_memory:
        memory:
            users:
                admin: { password: adminpass, roles: 'ROLE_ADMIN' }

# ...</pre><p>但我们更多时候是需要把用户信息存放在数据表中。为了做到这点，我们需要在数据库中创建一个<em>user</em>表。首先我们来为<em>user</em>表创建orm文件：</p><pre class="crayon-plain-tag"># src/Ibw/JobeetBundle/Resources/config/doctrine/User.orm.yml
Ibw\JobeetBundle\Entity\User:
    type: entity
    table: user
    id:
        id:
            type: integer
            generator: { strategy: AUTO }
    fields:
        username:
            type: string
            length: 255
        password:
            type: string
            length: 255</pre><p>现在运行命令生成<em>User</em>实体类：</p><pre class="crayon-plain-tag">php app/console doctrine:generate:entities IbwJobeetBundle</pre><p>然后更新数据库：</p><pre class="crayon-plain-tag">php app/console doctrine:schema:update --force</pre><p>这里需要为用户类（User）实现<em>UserInterface</em>接口，这就意味着“用户”可是任何的东西，只要它实现了<em>UserInterface</em>接口。打开并修改<em>User.php</em>：</p><pre class="crayon-plain-tag">// src/Ibw/JobeetBundle/Entity/User.php
namespace Ibw\JobeetBundle\Entity;

use Symfony\Component\Security\Core\User\UserInterface;
use Doctrine\ORM\Mapping as ORM;

/**
 * User
 */
class User implements UserInterface
{
    /**
     * @var integer
     */
    private $id;

    /**
     * @var string
     */
    private $username;

    /**
     * @var string
     */
    private $password;

    /**
     * Get id
     *
     * @return integer 
     */
    public function getId()
    {
        return $this-&gt;id;
    }

    /**
     * Set username
     *
     * @param string $username
     * @return User
     */
    public function setUsername($username)
    {
        $this-&gt;username = $username;

    }

    /**
     * Get username
     *
     * @return string 
     */
    public function getUsername()
    {
        return $this-&gt;username;
    }

    /**
     * Set password
     *
     * @param string $password
     * @return User
     */
    public function setPassword($password)
    {
        $this-&gt;password = $password;

    }

    /**
     * Get password
     *
     * @return string 
     */
    public function getPassword()
    {
        return $this-&gt;password;
    }

    public function getRoles()
    {
        return array('ROLE_ADMIN');
    }

    public function getSalt()
    {
        return null;
    }

    public function eraseCredentials()
    {

    }

    public function equals(User $user)
    {
        return $user-&gt;getUsername() == $this-&gt;getUsername();
    }        
}</pre><p>我们需要为<em>User</em>类实现<em>UserInterface</em>接口的抽象方法：<em>getRoles</em>，<em>getSalt</em>，<em>eraseCredentials</em>和<em>equals</em>。</p>
<p>接下来配置一个<em>user provider</em>实体，它指向<em>User</em>类：</p><pre class="crayon-plain-tag"># app/config/security.yml
...

    providers:
        main:
            entity: { class: Ibw\JobeetBundle\Entity\User, property: username }

    encoders:
        Ibw\JobeetBundle\Entity\User: sha512</pre><p>我们同样改变了<em>encoder</em>部分，这样就能够使用<em>sha512</em>算法对<em>User</em>类的<em>password</em>属性进行加密了。</p>
<p>所有准备都已经做好了，现在我们需要先创建一个用户。为了做到这点，我们会创建一个新的<em>Symfony</em>命令：</p><pre class="crayon-plain-tag">// src/Ibw/JobeetBundle/Command/JobeetUsersCommand.php
namespace Ibw\JobeetBundle\Command;

use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Ibw\JobeetBundle\Entity\User;

class JobeetUsersCommand extends ContainerAwareCommand
{
    protected function configure()
    {
        $this
            -&gt;setName('ibw:jobeet:users')
            -&gt;setDescription('Add Jobeet users')
            -&gt;addArgument('username', InputArgument::REQUIRED, 'The username')
            -&gt;addArgument('password', InputArgument::REQUIRED, 'The password')
        ;
    }

    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $username = $input-&gt;getArgument('username');
        $password = $input-&gt;getArgument('password');

        $em = $this-&gt;getContainer()-&gt;get('doctrine')-&gt;getManager();

        $user = new User();
        $user-&gt;setUsername($username);
        // encode the password
        $factory = $this-&gt;getContainer()-&gt;get('security.encoder_factory');
        $encoder = $factory-&gt;getEncoder($user);
        $encodedPassword = $encoder-&gt;encodePassword($password, $user-&gt;getSalt());
        $user-&gt;setPassword($encodedPassword);
        $em-&gt;persist($user);
        $em-&gt;flush();

        $output-&gt;writeln(sprintf('Added %s user with password %s', $username, $password));
    }
}</pre><p>使用命令创建第一个用户：</p><pre class="crayon-plain-tag">php app/console ibw:jobeet:users admin admin</pre><p>上面的命令会创建一个用户名和密码都是“admin”的用户。你可以使用它登录并访问到<em>admin</em>部分。</p>
<h2></h2>
<h2>登出（Logout）</h2>
<p>防火墙（firewall）能够自动处理登出。我们需要做的就是激活<em>logout</em>配置参数：</p><pre class="crayon-plain-tag"># app/config/security.yml
security:
    firewalls:
        # ...
        secured_area:
            # ...
            logout:
                path:   /logout
                target: /
    # ...</pre><p>我们不需要为<em>/logout</em> URL实现控制器，防火墙（firewall）会自行处理。我们来创建<em>/logout</em>路由：</p><pre class="crayon-plain-tag"># src/Ibw/JobeetBundle/Resources/config/routing.yml
# ...

logout:
    pattern:   /logout

# ...</pre><p>一旦配置完成了，访问<em>/logout</em>后，当前用户就会被注销认证（un-authenticate）了。然后用户会被重定向到首页（这个值定义在<em>target</em>参数中）。</p>
<p>剩下没有做的事情就是为<em>admin</em>部分添加<em>logout</em>链接。我们会重载<em>SonataAdminBundle</em>的<em>user_block.html.twig</em>。在<em>app/Resources/SonataAdminBundle/views/Core</em>目录下创建<em>user_block.html.twig</em>文件：</p><pre class="crayon-plain-tag">&lt;!-- app/Resources/SonataAdminBundle/views/Core/user_block.html.twig --&gt;
{% block user_block %}&lt;a href="{{ path('logout') }}"&gt;Logout&lt;/a&gt;{% endblock%}</pre><p>现在，当我们访问<em>admin</em>部分（请先清除缓存）时，我们会被要求填写用户名和密码，进入之后我们可以在右上角看到一个<em>logout</em>链接。</p>
<p>&nbsp;</p>
<h2>用户<em>Session</em></h2>
<p><em>Symfony2</em>提供了一个非常好用的<em>session</em>对象，用户的每个不同请求都可以访问到它里面保存的信息。<em>Symfony2</em>默认是使用原生的<em>PHP session</em>把信息保存到一个<em>cookie</em>中。</p>
<p>我们可以在控制器中方便地取出<em>session</em>里保存的信息：</p><pre class="crayon-plain-tag">$session = $this-&gt;getRequest()-&gt;getSession();

// store an attribute for reuse during a later user request
$session-&gt;set('foo', 'bar');

// in another controller for another request
$foo = $session-&gt;get('foo');</pre><p>可惜的是，在我们<em>Jobeet</em>的用户<em>stories</em>中（第二天的内容），并没有需要在用户<em>session</em>中保存信息的需求。所以我们来改改需求：为了方便<em>Job</em>信息的浏览，我们会在菜单栏上显示用户刚刚浏览过的三条<em>Job</em>信息的链接。</p>
<p>当用户访问一条<em>Job</em>页面时，那么这条<em>Job</em>信息对应的<em>Job</em>对象就会被添加到用户的浏览历史中，并且把它保存到<em>session</em>中：</p><pre class="crayon-plain-tag">// src/Ibw/JobeetBundle/Controller/JobController.php
// ...

public function showAction($id)
{
    $em = $this-&gt;getDoctrine()-&gt;getManager();

    $entity = $em-&gt;getRepository('IbwJobeetBundle:Job')-&gt;getActiveJob($id);

    if (!$entity) {
        throw $this-&gt;createNotFoundException('Unable to find Job entity.');
    }

    $session = $this-&gt;getRequest()-&gt;getSession();

    // fetch jobs already stored in the job history
    $jobs = $session-&gt;get('job_history', array());

    // store the job as an array so we can put it in the session and avoid entity serialize errors
    $job = array('id' =&gt; $entity-&gt;getId(), 'position' =&gt;$entity-&gt;getPosition(), 'company' =&gt; $entity-&gt;getCompany(), 'companyslug' =&gt; $entity-&gt;getCompanySlug(), 'locationslug' =&gt; $entity-&gt;getLocationSlug(), 'positionslug' =&gt; $entity-&gt;getPositionSlug());

    if (!in_array($job, $jobs)) {
        // add the current job at the beginning of the array
        array_unshift($jobs, $job);

        // store the new job history back into the session
        $session-&gt;set('job_history', array_slice($jobs, 0, 3));
    }

    $deleteForm = $this-&gt;createDeleteForm($id);

    return $this-&gt;render('IbwJobeetBundle:Job:show.html.twig', array(
        'entity'      =&gt; $entity,
        'delete_form' =&gt; $deleteForm-&gt;createView(),
    ));
}</pre><p>在<em>layout</em>文件的<em>#content div</em>标签之前添加下面的代码：</p><pre class="crayon-plain-tag">&lt;!-- src/Ibw/JobeetBundle/Resources/views/layout.html.twig --&gt;
&lt;!-- ... --&gt;

&lt;div id="job_history"&gt;
    Recent viewed jobs:
    &lt;ul&gt;
        {% for job in app.session.get('job_history') %}
            &lt;li&gt;
                &lt;a href="{{ path('ibw_job_show', { 'id': job.id, 'company': job.companyslug, 'location': job.locationslug, 'position': job.positionslug }) }}"&gt;{{ job.position }} - {{ job.company }}&lt;/a&gt;
            &lt;/li&gt;
        {% endfor %}
    &lt;/ul&gt;
&lt;/div&gt;

&lt;div id="content"&gt;
    ...
&lt;/div&gt;
&lt;!-- ... --&gt;</pre><p>&nbsp;</p>
<h2><em>flash</em>信息</h2>
<p><em>flash</em>信息是存放在用户<em>session</em>中的简短信息（通常是用来作为反馈给用户的信息），它通常用在下一个请求中显示信息。它对表单很有用：当你想要重定向并请求一个新页面的同时，显示出一些指定的信息。我们已经在项目的发布<em>Job</em>信息的需求中使用过<em>flash</em>信息了：</p><pre class="crayon-plain-tag">// src/Ibw/JobeetBundle/Controller/JobController.php
// ...

public function publishAction($token)
{
    // ...

    $this-&gt;get('session')-&gt;getFlashBag()-&gt;add('notice', 'Your job is now online for 30 days.');

    // ...
}</pre><p><em>getFlashBag()-&gt;add()</em>函数的第一个参数是用来标志<em>flash</em>的键，第二个参数是需要显示的信息。我们可以为<em>flash</em>定义任何键名，但<em>notic</em>和<em>error</em>是最常用的两个。</p>
<p>为了显示<em>flash</em>信息，我们需要在模板中把它们包含进来。我们已经在<em>layout.html.twig</em>中做过了：</p><pre class="crayon-plain-tag">&lt;!-- src/Ibw/JobeetBundle/Resources/views/layout.html.twig --&gt;
&lt;!-- ... --&gt;

{% for flashMessage in app.session.flashbag.get('notice') %}
    &lt;div&gt;
        {{ flassMessage }}
    &lt;/div&gt;
{% endfor %}

&lt;!-- ... --&gt;</pre><p>&nbsp;</p>
<p><span style="color: #ff0000">原文链接：<a style="color: #ff0000" href="http://www.intelligentbee.com/blog/2013/08/19/symfony2-jobeet-day-13-security/">http://www.intelligentbee.com/blog/2013/08/19/symfony2-jobeet-day-13-security/</a></span></p>
<p><a rel="nofollow" href="http://www.newlifeclan.com/symfony/archives/377">jobeet第十三天:安全</a>，首发于<a rel="nofollow" href="http://www.newlifeclan.com/symfony">Symfony中文教程</a>。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.newlifeclan.com/symfony/archives/377/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>(Security)如何去搭建一个传统的登录表单</title>
		<link>http://www.newlifeclan.com/symfony/archives/300</link>
		<comments>http://www.newlifeclan.com/symfony/archives/300#comments</comments>
		<pubDate>Tue, 24 Mar 2015 15:29:01 +0000</pubDate>
		<dc:creator><![CDATA[napoleon]]></dc:creator>
				<category><![CDATA[Cook 2.6]]></category>
		<category><![CDATA[Cookbook]]></category>
		<category><![CDATA[Security]]></category>
		<category><![CDATA[security]]></category>
		<category><![CDATA[登录表单]]></category>

		<guid isPermaLink="false">http://www.newlifeclan.com/symfony/?p=300</guid>
		<description><![CDATA[<p>注意：如果你需要为存储在某种数据库中的用户做一个登录表单，那么你应该考虑使用FOSUserBundle，这有助 [&#8230;]</p>
<p><a rel="nofollow" href="http://www.newlifeclan.com/symfony/archives/300">(Security)如何去搭建一个传统的登录表单</a>，首发于<a rel="nofollow" href="http://www.newlifeclan.com/symfony">Symfony中文教程</a>。</p>
]]></description>
				<content:encoded><![CDATA[<p>注意：如果你需要为存储在某种数据库中的用户做一个登录表单，那么你应该考虑使用FOSUserBundle，这有助于你建立你的User对象，还为您提供了常见的登录、注册、忘记密码的路由和控制器。</p>
<p><span id="more-300"></span></p>
<p>在此文章中，将构建一个传统的登录表单。当然，当用户登录时，你可以从数据库或者任何地方加载用户。</p>
<p>本章假设您已经遵循了security章节并且http_basic 认证正常工作。</p>
<p>首先，启用防火墙下表单登录</p><pre class="crayon-plain-tag"># app/config/security.yml
security:
    # ...

    firewalls:
        default:
            anonymous: ~
            http_basic: ~
            form_login:
                login_path: /login
                check_path: /login_check</pre><p></p>
<blockquote><p>这个login_path和check_path也可以是路由名称（但不能有强制通配符例如 /login/{foo}）这里foo没有默认值</p></blockquote>
<p>现在，当安全系统启动认证过程，它会让用户跳转到登录表单 /login。你的工作是实现这个登录表单视觉。首先，创建一个新的SecurityController在bundle中：</p><pre class="crayon-plain-tag">// src/AppBundle/Controller/SecurityController.php
namespace AppBundle\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;

class SecurityController extends Controller
{
}</pre><p>下一步创建两个路由：分别是刚才form_login下的设置的两个路径（/login和/login_check）:</p>
<p>annotations</p><pre class="crayon-plain-tag">// src/AppBundle/Controller/SecurityController.php

// ...
use Symfony\Component\HttpFoundation\Request;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;

class SecurityController extends Controller
{
    /**
     * @Route("/login", name="login_route")
     */
    public function loginAction(Request $request)
    {
    }

    /**
     * @Route("/login_check", name="login_check")
     */
    public function loginCheckAction()
    {
        // this controller will not be executed,
        // as the route is handled by the Security system
    }
}</pre><p>&nbsp;</p>
<p>非常好！下一步，你要去添加loginAction的逻辑，并把需要的渲染到login表单：</p><pre class="crayon-plain-tag">// src/AppBundle/Controller/SecurityController.php

public function loginAction(Request $request)
{
    $authenticationUtils = $this-&gt;get('security.authentication_utils');

    // get the login error if there is one
    $error = $authenticationUtils-&gt;getLastAuthenticationError();

    // last username entered by the user
    $lastUsername = $authenticationUtils-&gt;getLastUsername();

    return $this-&gt;render(
        'security/login.html.twig',
        array(
            // last username entered by the user
            'last_username' =&gt; $lastUsername,
            'error'         =&gt; $error,
        )
    );
}</pre><p></p>
<blockquote><p> 这个security.authentication_utils服务和<tt class="docutils literal"><code><em><span style="text-decoration: underline"><span style="color: #ff0000"><a class="reference external" style="color: #ff0000;text-decoration: underline" title="Symfony\Component\Security\Http\Authentication\AuthenticationUtils" href="http://api.symfony.com/2.6/Symfony/Component/Security/Http/Authentication/AuthenticationUtils.html">AuthenticationUtils</a></span></span></em>类在symfony2.6都有介绍。</code></tt></p></blockquote>
<p>不要让这个控制器迷惑你。你看到当用户提交表单的这一刻，security系统会自动处理表单提交到这个控制器。如果用户提交了一个无效的用户名和密码，该控制器可以从security系统中读出表单提交的错误，以便把他显示给用户。</p>
<p>换句话说,你的工作是显示登录表单和任何可能发生的登录错误,但是security系统本身负责检查提交的用户名和密码并认证用户。</p>
<p>最后，创建模版：</p><pre class="crayon-plain-tag">{# app/Resources/views/security/login.html.twig #}
{# ... you will probably extends your base template, like base.html.twig #}

{% if error %}
    &lt;div&gt;{{ error.messageKey|trans(error.messageData) }}&lt;/div&gt;
{% endif %}

&lt;form action="{{ path('login_check') }}" method="post"&gt;
    &lt;label for="username"&gt;Username:&lt;/label&gt;
    &lt;input type="text" id="username" name="_username" value="{{ last_username }}" /&gt;

    &lt;label for="password"&gt;Password:&lt;/label&gt;
    &lt;input type="password" id="password" name="_password" /&gt;

    {#
        If you want to control the URL the user
        is redirected to on success (more details below)
        &lt;input type="hidden" name="_target_path" value="/account" /&gt;
    #}

    &lt;button type="submit"&gt;login&lt;/button&gt;
&lt;/form&gt;</pre><p></p>
<blockquote><p>这个传入到模版的错误变量是一个<span style="text-decoration: underline"><em><span style="color: #ff0000"><span class="goog-text-highlight" style="color: #ff0000"><a class="reference external" style="color: #ff0000" title="Symfony的\分量\安全\核心\异常\的AuthenticationException" href="http://api.symfony.com/2.6/Symfony/Component/Security/Core/Exception/AuthenticationException.html">AuthenticationException</a>。</span></span></em></span>它包含很多信息－一些敏感信息－关于认证失败信息，所以你要明智的使用它。</p></blockquote>
<p>要实现这些东西，要注意这几个要求：</p>
<ul>
<li>该表单必须提交到/login_check,因为你在security.yml的form_login健中配置的</li>
<li>这个用户名一定要name为_username并且password一定要name为_password。</li>
</ul>
<blockquote><p>其实所有的这些都可以配置到form_login健下，请查看 <em><span style="text-decoration: underline"><span style="color: #ff0000"><a style="color: #ff0000;text-decoration: underline" href="http://symfony.com/doc/current/reference/configuration/security.html#reference-security-firewall-form-login" target="_blank">form登录配置</a></span></span></em>。</p></blockquote>
<p>此登录表单目前没有使用CSRF攻击。如果需要请阅读<span style="color: #ff0000"> <a class="reference internal" style="color: #ff0000" href="http://symfony.com/doc/current/cookbook/security/csrf_in_login_form.html"><em>Using CSRF Protection in the Login Form</em></a> </span>。</p>
<p>就是这样！当您提交表单，security系统会自动检查用户的凭证，验证用户或向用户发送错误信息在登陆表单。</p>
<p>回顾整个过程：</p>
<p>1.用户试图访问被保护的资源。</p>
<p>2.防火期启用认证过程将用户重定向到登录表单（/login）</p>
<p>3.这个例子中，通过路由（route）和控制器（controller）来显示登录表单。</p>
<p>4.用户提交登录表单到 /login_check;</p>
<p>5.security系统截取请求，检查用户提交的凭据，验证他们是否正确，如果他不是系统允许的，页面会重新跳转到表单。</p>
<p>&nbsp;</p>
<h2>成功后，重定向</h2>
<p>如果提交的凭证是正确的，该用户会被重新定向到请求的原始页面（如/admin/foo）。如果用户最初直接进入登录页面，它需要跳转到首页。这些都是可以设定的，并且允许你指定到一个指定的url上。</p>
<p>更多细节，请参阅 <em><span style="text-decoration: underline"><span style="color: #ff0000"><a style="color: #ff0000;text-decoration: underline" href="http://symfony.com/doc/current/cookbook/security/form_login.html">How to Customize your Form Login</a></span></span></em>。</p>
<p>&nbsp;</p>
<h2>避免常见问错误</h2>
<p>在设置表单时应该注意一些常见的陷阱。</p>
<p><strong>1.创建正确的路由</strong></p>
<p>首先，确保你已经定义了正确的/login和/login_check，他们应该和你配置的login_path和check_path是一样的。如果你配置错误他会重定向到一个404页面，而不是登录页面，或者会出现提交表单不执行任何操作（你会一遍又一遍的看到登录表单）。</p>
<p><strong>2.确保登录页面可以访问</strong></p>
<p>此外，要确保登录页面匿名用户可以访问。例如，下面的配置-ROLE_ADMIN角色用于所有的URL（包括 /login），将会出现重定向循环：</p><pre class="crayon-plain-tag"># app/config/security.yml

# ...
access_control:
    - { path: ^/, roles: ROLE_ADMIN }</pre><p>添加access_control中添加一个 /login/*，并且指定一个任何身份都可以进入的角色。</p><pre class="crayon-plain-tag"># app/config/security.yml

# ...
access_control:
    - { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
    - { path: ^/, roles: ROLE_ADMIN }</pre><p>另外，如果您的防火墙没有允许匿名用户（没有<tt class="docutils literal"><code>anonymous</code></tt> 键），你需要去创建一个特殊的防火墙，允许匿名用户登录：</p><pre class="crayon-plain-tag"># app/config/security.yml

# ...
firewalls:
    # order matters! This must be before the ^/ firewall
    login_firewall:
        pattern:   ^/login$
        anonymous: ~
    secured_area:
        pattern:    ^/
        form_login: ~</pre><p><strong> 3.确保 /login_check 位于防火墙的后面</strong></p>
<p>确保你的check_path的url(如 /login_check)在表单登录防火墙里（例如本例，单一的防火墙匹配所有URL，包含/login_check）。如果/login_check不匹配路由，你会收到一个<span style="color: #339966">Unable to find the controller for path &#8220;/login_check&#8221;</span>的异常。</p>
<p>4.多个防火墙不共享相同的Security内容</p>
<p>如果你使用多个防火墙并且你进行认证了一个防火墙，其他的防火墙就不会对此做自动认证了。不同的防火墙，就像不同的安全系统。要做到这一点，你就必须要明确指定不同防火墙下相同的<span style="text-decoration: underline"><span style="color: #ff0000;text-decoration: underline"> <a class="reference internal" style="color: #ff0000;text-decoration: underline" href="http://symfony.com/doc/current/reference/configuration/security.html#reference-security-firewall-context"><em>Firewall Context</em></a>  </span></span>。但大多数应用，有一个主要的防火墙就足够了。</p>
<p>5.路由错误不受防火墙限制</p>
<p>Security已经把路由里的404页面设置成了不受防火墙限制。这意味着在这些页面上，你不能检测Security甚至访问用户对象。请查看 <span style="text-decoration: underline"><em><span style="color: #ff0000"><a style="color: #ff0000;text-decoration: underline" href="http://symfony.com/doc/current/cookbook/controller/error_pages.html" target="_blank">How to Customize Error Pages</a></span></em></span></p>
<p><a rel="nofollow" href="http://www.newlifeclan.com/symfony/archives/300">(Security)如何去搭建一个传统的登录表单</a>，首发于<a rel="nofollow" href="http://www.newlifeclan.com/symfony">Symfony中文教程</a>。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.newlifeclan.com/symfony/archives/300/feed</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
	</channel>
</rss>
