読者です 読者をやめる 読者になる 読者になる

Doctrineを使ってユーザー認証を行う

Symfony

前エントリの続き。config.ymlにユーザーを定義していたのを、Doctrineを使ってデータベースに保存してあるユーザー情報を使うようにする。

Symfony2でDoctrineを使う設定はこのあたりのドキュメントを元ににすでに設定済みとする。

まず、app/config/config.ymlを変更。

# app/config/config.yml
security.config:
    encoders:
        default:
            class:     Application\MyBundle\Entity\User
            algorithm: sha1

    providers:
        default:
            entity: { class: MyBundle:User, property: username }

    firewalls:
        default:
            pattern:    /.*
            anonymous:  true
            form-login: true
            logout:     true

UserというEntityをSymfony\Component\Security\User\AccountInterfaceというインターフェイスを満たすように実装する。

<?php
// src/Application/MyBundle/Entity/User.php

namespace Application\MyBundle\Entity;

use Symfony\Component\Security\User\AccountInterface;

/**
 * @orm:Entity(repositoryClass="Application\MyBundle\Entity\UserRepository")
 * @orm:Table(name="user",
 *     uniqueConstraints={
 *         @orm:UniqueConstraint(name="idx_username", columns={"username"})
 *     }
 * )
 * @orm:HasLifecycleCallbacks
 */
class User implements AccountInterface
{
    /**
     * @orm:Column(name="id", type="integer")
     * @orm:Id
     * @orm:GeneratedValue(strategy="AUTO")
     * @var integer
     */
    protected $id;

    /**
     * @orm:Column(name="username", type="string", length="32")
     * @validation:Min(1)
     * @validation:Max(32)
     * @validation:NotBlank
     * @var string
     */
    protected $username;

    /**
     * @orm:Column(name="password", type="string", length="255")
     * @validation:NotBlank
     * @var string
     */
    protected $password;

    /**
     * @orm:Column(name="salt", type="string", length="40")
     * @validation:NotBlank
     * @var string
     */
    protected $salt;

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

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

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

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

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

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

    /**
     * @return array
     */
    public function getRoles()
    {
        return array('ROLE_USER');
    }

    /**
     * @return void
     */
    public function eraseCredentials()
    {
    }

    /**
     * @return bool
     */
    function equals(AccountInterface $account)
    {
        return $this->getUsername() === $account->getUsername();
    }
}

EntityRepositoryhはSymfony\Component\Security\User\UserProviderInterfaceというインターフェイスを実装しておかなくてはいけない。

<?php
// src/Application/MyBundle/Entity/UserRepository.php

namespace Application\MyBundle\Entity;

use Symfony\Component\Security\User\UserProviderInterface;
use Symfony\Component\Security\User\AccountInterface;

use Doctrine\ORM\EntityRepository;

class UserRepository extends EntityRepository implements UserProviderInterface
{
    /**
     * @param string $username
     * @return \Applicatoin\UserBundle\Entity\User
     */
    public function loadUserByUsername($username)
    {
        return $this->findOneBy(array('username' => $username));
    }

    /**
     * @param AccountInterface $user
     * @return \Applicatoin\UserBundle\Entity\User
     */
    public function loadUserByAccount(AccountInterface $user)
    {
        return $this->loadUserByUsername($user->getUsername());
    }
}

ルーティングの設定やログインフォームは前回に作ったものをそのまま使えばOK。

$ ./app/console doctrine:schema:create

のようにして、Userクラスに対応するテーブルを作り、テスト用のユーザーを入れる。

insert into user(id, username, password, salt) values(1, 'scott', sha1('tiger{salt}'), 'salt');

saltは本当はユーザー毎に異なる十分に長いランダムな文字列にしなくてはいけない。Symfonyのドキュメントのここには「getSalt()メソッドでは主キーをそのまま返せばいいよ」みたいなことを書いてあるけど、推測可能なsaltは良くないと思う。