nazolabo

フリーランスのWebエンジニアが近況や思ったことを発信しています。

Symfony2の認証(Entity経由でログイン編)

おそらくほとんどの人が実現したいと思う、Userテーブルの情報からログインをする方法を考えてみましょう。

今回は、NazoUserBundleというサンプルバンドルを作り、そこで作業します。

chmod -R 777 app/cache app/logs
php app/console generate:bundle --namespace=Nazo/UserBundle

とりあえず基本設定をします。
app/config/parameters.iniで、DBの接続設定を書きます。(省略)

DBを作成します。

php app/console doctrine:database:create

以下、「src/Nazo/UserBundle/」で作業します。

Entity/User.phpを作成します。

<?php

namespace Nazo\UserBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\AdvancedUserInterface;

/**
 * Nazo\UserBundle\Entity\User
 *
 * @ORM\Table(name="users")
 * @ORM\Entity
 */
class User implements AdvancedUserInterface
{
    /**
     * @var integer $id
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @var string $username
     *
     * @ORM\Column(name="username", type="string", length=255)

     */
    private $username;

    /**
     * @var string $password
     *
     * @ORM\Column(name="password", type="string", length=255)
     */
    private $password;

    /**
     * @var string $salt
     *
     * @ORM\Column(name="salt", type="string", length=255)
     */
    private $salt;

    private $accountNonExpired = true;
    private $credentialsNonExpired = true;
    private $accountNonLocked = true;
    private $enabled = true;
    private $roles = array('ROLE_USER');


    /**
     * Get id
     *
     * @return integer
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * {@inheritdoc}
     */
    public function getPassword()
    {
        return $this->password;
    }

    /**
     * {@inheritdoc}
     */
    public function getSalt()
    {
        return $this->salt;
    }

    /**
     * {@inheritdoc}
     */
    public function getUsername()
    {
        return $this->username;
    }

    /**
     * Set password
     *
     * @param string $password
     */
    public function setPassword($password)
    {
        $this->password = $password;
    }

    /**

     * Set salt
     *
     * @param string $salt
     */
    public function setSalt($salt)
    {
        $this->salt = $salt;
    }

    /**
     * Set username
     *
     * @param string $username
     */
    public function setUsername($username)
    {
        $this->username = $username;
    }

    /**
     * {@inheritdoc}
     */
    public function getRoles()
    {
        return $this->roles;
    }
    /**
     * {@inheritdoc}
     */
    public function isAccountNonExpired()
    {
        return $this->accountNonExpired;
    }

    /**
     * {@inheritdoc}
     */
    public function isAccountNonLocked()
    {
        return $this->accountNonLocked;
    }

    /**
     * {@inheritdoc}
     */
    public function isCredentialsNonExpired()
    {
        return $this->credentialsNonExpired;
    }

    /**
     * {@inheritdoc}
     */
    public function isEnabled()
    {


        return $this->enabled;
    }

    /**
     * {@inheritdoc}
     */
    public function eraseCredentials()
    {
    }

    /**
     * {@inheritDoc}
     */
    public function equals(UserInterface $user)
    {
        if (!$user instanceof User) {
            return false;
        }

        if ($this->password !== $user->getPassword()) {
            return false;
        }

        if ($this->getSalt() !== $user->getSalt()) {
            return false;
        }

        if ($this->getUsername() !== $user->getUsername()) {

            return false;
        }

        if ($this->accountNonExpired !== $user->isAccountNonExpired()) {
            return false;
        }

        if ($this->accountNonLocked !== $user->isAccountNonLocked()) {
            return false;
        }

        if ($this->credentialsNonExpired !== $user->isCredentialsNonExpired()) {
            return false;
        }

        if ($this->enabled !== $user->isEnabled()) {
            return false;
        }

        return true;
    }
}

Controller/DefaultController.phpを編集します。

<?php

namespace Nazo\UserBundle\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\SecurityContext;

class DefaultController extends Controller
{
    /**
     * @Route("/user/home")
     * @Template()
     */
    public function indexAction()
    {
        return array(
        );
    }

    /**
     * @Route("/user/login")
     * @Template()
     */
    public function loginAction(Request $request)
    {
        $session = $request->getSession();

        // get the login error if there is one
        if ($request->attributes->has(SecurityContext::AUTHENTICATION_ERROR)) {
            $error = $request->attributes->get(SecurityContext::AUTHENTICATION_ERROR);
        } else {
            $error = $session->get(SecurityContext::AUTHENTICATION_ERROR);
        }

        return array(
            // last username entered by the user
            'last_username' => $session->get(SecurityContext::LAST_USERNAME),
            'error'         => $error,
        );
    }

    /**
     * @Route("/user/login_check", name="login_check")
     * @Template()
     */
    public function login_checkAction()
    {
        return array();
    }
}

Resources/views/Default/index.html.twigを編集します。

Hello !!

Resources/views/Default/login.html.twigを編集します。

{% if error %}
    <div>{{ error.message }}</div>
{% endif %}

<form action="{{ path('login_check') }}" method="post">
    <label for="username">Username:</label>
    <input type="text" id="username" name="_username" value="{{ last_username }}" />

    <label for="password">Password:</label>
    <input type="password" id="password" name="_password" />

    {#
        If you want to control the URL the user is redirected to on success (more details below)
        <input type="hidden" name="_target_path" value="/account" />
    #}

    <input type="submit" name="login" />
</form>

ルートに戻って、DBにテーブルを作成します。

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

app/config/security.ymlを編集します。

security:
    encoders:
        Nazo\UserBundle\Entity\User: plaintext

    role_hierarchy:
        ROLE_ADMIN:       ROLE_USER
        ROLE_SUPER_ADMIN: [ROLE_USER, ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH]

    providers:
        users:
            entity: { class: Nazo\UserBundle\Entity\User, property: username }

    firewalls:
        dev:
            pattern:  ^/(_(profiler|wdt)|css|images|js)/
            security: false

        login:
            pattern:  ^/user/login$
            security: false

        secured_area:
            pattern:    ^/user/.*
            form_login:
                check_path: /user/login_check
                login_path: /user/login
            logout:
                path:   /user/logout
                target: /

            #anonymous: ~
            #http_basic:
            #    realm: "Secured Demo Area"

    access_control:
        #- { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY, requires_channel: https }
        #- { path: ^/_internal, roles: IS_AUTHENTICATED_ANONYMOUSLY, ip: 127.0.0.1 }

ログインは app_dev.php/user/login から試せます。 app_dev.php/user/logout にアクセスするとログアウトします。(見た目に変化はありませんが)
ログイン中かどうかは、 app_dev.php/user/home にアクセスするとわかると思います。
これでログインすることとができるようになりましたが、パスワードが平文です。よろしくないですね。

パスワードを暗号化できるように、app/config/security.ymlを編集します。

security:
    encoders:
        Nazo\UserBundle\Entity\User:
            algorithm: sha1
            encode_as_base64: false

            iterations: 1
...

しかしこれではユーザーデータを人力で入れることができません。(がんばれば入れれますが)

人力で入れるために、管理画面を作ります。

Controller/UserAdminController.php

<?php

namespace Nazo\UserBundle\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\SecurityContext;
use Nazo\UserBundle\Entity\User;

class UserAdminController extends Controller
{
    /**
     * @Route("/admin/user/index", name="admin_user_index")
     * @Template()
     */
    public function indexAction()
    {
        $em = $this->getDoctrine()->getEntityManager();
        $users = $em->getRepository('NazoUserBundle:User')->findAll();
        return array(
            'users' => $users,
        );
    }

    /**
     * @Route("/admin/user/create", name="admin_user_create")
     * @Template()
     */
    public function createAction()
    {

        return $this->onEdit(null);
    }

    /**
     * @Route("/admin/user/edit", name="admin_user_edit")
     * @Template()
     */
    public function editAction(Request $request)
    {
        $id = $request->get('id');

        return $this->onEdit($id);
    }

    /**
     * protected
     *
     */
    protected function onEdit($id = null)
    {
        $em = $this->getDoctrine()->getEntityManager();
        $request = $this->get('request');

        if ($id === null) {
            $user = new User();
        } else {
            $user = $em->getRepository('NazoUserBundle:User')->find($id);
        }

        $form = $this->createFormBuilder($user)

            ->add('username', 'text')
            ->add('password', 'password')
            ->getForm();

        if ($request->getMethod() == 'POST') {
            $form->bindRequest($request);
            if ($form->isValid()) {
                // encrypt password
                $salt = sha1(uniqid($user->getUsername(), true));
                $password_raw = $user->getPassword();
                $user->setSalt($salt);

                $ef = $this->get('security.encoder_factory');
                $password = $ef->getEncoder($user)->encodePassword($password_raw, $salt);
                $user->setPassword($password);

                $em->persist($user);
                $em->flush();
            }
        }

        return $this->render('NazoUserBundle:UserAdmin:edit.html.twig', array(
            'user' => $user,
            'form' => $form->createView(),
        ));
    }

}

Resources/views/UserAdmin/index.html.twig

<h1>User admin</h1>

<div>
<a href="{{ path('admin_user_create') }}">create new user</a>
</div>

<table>
    <tr>
        <th>ID</th>
        <th>name</th>
        <th>action</th>
    </tr>
{% for user in users %}
<tr>
    <td>{{ user.id }}</td>
    <td>{{ user.username }}</td>
    <td>
        <a href="{{ path('admin_user_edit', {'id':user.id}) }}">edit</a>
    </td>
</tr>
{% endfor %}
</table>

Resources/views/UserAdmin/edit.html.twig

<h1>Edit User</h1>

<div>ID : {{ user.id }}</div>

<form action="{{ path('admin_user_edit') }}" method="post" {{ form_enctype(form) }}>
    <input type="hidden" name="id" value="{{ user.id }}" />
    {{ form_widget(form) }}
    <input type="submit" value="save" />
</form>

<div>
<a href="{{ path('admin_user_index') }}">back to list</a>
</div>

これで、 app_dev.php/admin/user/index から、ユーザーを作り、そのユーザーでログインができるようになりました。