Symfony2の認証を色々試してみる(後編)
前編の続きです。
前編では下記の認証方法を説明しました。
- Basic認証
- security.ymlで設定されたUser情報によるBasic認証
- フォーム認証(User情報は固定)
- security.ymlで設定されたUser情報によるフォーム認証
- フォーム認証(Doctrineと連携)
- データベースに設定されたUser情報によるフォーム認証
後編では下記を説明していこうと思います。
- フォーム認証(DBで権限管理 ManyToOne)
- データベースに設定されているUserと権限情報によるフォーム認証(Userに対する権限は1つ)
- フォーム認証(DBで権限管理 ManyToMany)
- データベースに設定されているUserと権限情報によるフォーム認証(Userに対する権限は複数)
では早速説明していきます。
フォーム認証(DBで権限管理 ManyToOne)
前編のほうで書いたフォーム認証(Doctrineと連携)のほうでは特に権限の説明をしませんでした。
Symfony2のSecurityコンポーネントではユーザーには必ず最低でも1つの権限を割り当てなくてはいけません。
権限はその名の通り閲覧権限(アクションを実行出来る権限)だと思って頂ければ良いと思います。
前編のフォーム認証(Doctrineと連携)では説明していませんが、下記の部分で権限の割り振りはしてあります。
- src/App/TestBundle/Entity/User.php
<?php /** * 権限を返す(今回は権限管理はしない為、固定でROLE_ADMINを返す) * * @return array */ public function getRoles() { return array('ROLE_ADMIN'); }
上記の場合、ユーザーには固定でROLE_ADMINの権限が返されるようになっています。
この権限で操作可能なページはsecurity.ymlに設定されてあります。
- app/config/security.yml
# アクセス権限の設定 access_control: # /から始まるコンテンツにアクセスするにはROLE_ADMIN権限が必要 - { path: ^/, roles: ROLE_ADMIN }
今回の例ではaccess_controlの設定を理解する為にも、権限も分けてみます。
また権限が分かりやすいようにデバッグツールバーも表示してみます。
では、まずsecurity.ymlを下記のように変更します。
- src/App/TestBundle/Entity/User.php
security: # エンコーダの設定 encoders: # エンコードと対象となるユーザーモデルを指定(作成したUserモデルを指定) App\TestBundle\Entity\User: plaintext # 権限継承 role_hierarchy: # ROLE_ADMINはROLE_USERの権限も持つ ROLE_ADMIN: ROLE_USER # ユーザー情報の設定(ユーザープロバイダ) providers: # 作成したUserモデルをユーザー情報とする my_users: # Entityクラスの指定とユーザー名となるプロパティを指定 entity: { class: App\TestBundle\Entity\User, property: username } # ファイアーウォールの設定 firewalls: # デバッグツールバーのセキュリティを無効 dev: pattern: ^/(_(profiler|wdt))/ security: false # ログイン画面は認証エリア外 login: # エリア範囲(正規表現) pattern: ^/login$ # セキュリティ設定を無効 security: false #anonymous: true # 認証エリアの設定 secured_area: # エリア範囲(正規表現) pattern: ^/ # ログインフォームの設定 form_login: # ログインフォームのパス login_path: /login # ログイン状態を確認するパス check_path: /login/check # ログアウトの設定 logout: # ログアウト用のURLのパス path: /logout # ログアウト後に移行するページ target: /login # アクセス権限の設定 access_control: # /testにはROLE_USERとROLE_ADMINがアクセス可能 - { path: ^/test, roles: ROLE_USER } # /から始まるコンテンツにアクセスするにはROLE_ADMIN権限が必要 - { path: ^/, roles: ROLE_ADMIN }
追記・変更した部分は下記の部分です。
# 権限継承 role_hierarchy: # ROLE_ADMINはROLE_USERの権限も持つ ROLE_ADMIN: ROLE_USER
上記は権限の継承?設定です。
上記の設定の場合、ROLE_ADMINはROLE_USERの権限も引き継ぎます。
# アクセス権限の設定 access_control: # /testにはROLE_USERとROLE_ADMINがアクセス可能 - { path: ^/test, roles: ROLE_USER } # /から始まるコンテンツにアクセスするにはROLE_ADMIN権限が必要 - { path: ^/, roles: ROLE_ADMIN }
最後にデバッグツールバーが表示されるようにデバッグツールバーにセキュリティ設定が適用されないように設定。
dev: pattern: ^/(_(profiler|wdt))/ security: false
access_controlの部分にも追記しました。
/testへのアクセスはROLE_USER権限でもアクセスが可能という設定です。
ROLE_ADMINはROLE_USERの権限も継承している為、ROLE_ADMINでもアクセスが可能です。
次はEntityとなるRoleクラスを作成します。
下記のコマンドを打って、Entityを作成してください。
$ php app/console doctrine:generate:entity --entity="AppTestBundle:Role" --fields="name:string(255)"
作成したRoleクラスを下記のように編集します。
- src/App/TestBundle/Entity/Role.php
<?php // ... use Symfony\Component\Security\Core\Role\RoleInterface; use \Doctrine\Common\Collections\ArrayCollection; /** * App\TestBundle\Entity\Role * * @ORM\Table() * @ORM\Entity */ class Role implements RoleInterface { // ... /** * @ORM\OneToMany(targetEntity="User", mappedBy="role") */ protected $users; /** * construct */ function __construct() { $this->users = new ArrayCollection(); } // ... /** * Get Role * * @return string */ public function getRole() { return $this->getName(); } /** * Get users * * @return Doctrine\Common\Collections\Collection */ public function getUsers() { return $this->users; } }
まずは利用するクラスとインターフェースを呼び出し、RoleInterfaceを継承します。
次に$usersプロパティとコンストラクタを定義します、ここでは$usersプロパティはUserクラスのroleプロパティとの関連付けをしています。
最後に必要なメソッドを追加します。
次にUserクラスの方も編集します。
Userクラスのほうは$roleプロパティを追加し、それに対応するメソッドを追加するだけです。
- src/App/TestBundle/Entity/User.php
<?php class User implements UserInterface { // ... /** * @var integer $role * * @ORM\ManyToOne(targetEntity="Role", inversedBy="users") * @ORM\JoinColumn(name="role_id", referencedColumnName="id") */ protected $role; // ... /** * Set role * * @param integer $role */ public function setRole($role) { $this->role = $role; } /** * Get role * * @return integer */ public function getRole() { return $this->role; } /** * @return array */ public function getRoles() { return array($this->role->getName()); }
肝となる部分は$roleプロパティのアノテーションの部分です。
アノテーションを利用して、Roleクラスと結合しています。
ここまで出来たら後は適当に/testになるコンテンツを作成してみます。
routing.ymlに下記を追記します。
- src/App/TestBundle/Resources/config/routing.yml
test_test: pattern: /test defaults: { _controller: AppTestBundle:Default:test }
次にアクションの作成です。
DefaultController.phpに下記を追記します。
- src/App/TestBundle/Controller/DefaultController.php
<?php public function testAction() { return $this->render('AppTestBundle:Default:test.html.twig'); }
次にテンプレートを作成します。
デバッグツールバーを表示する為にbase.html.twigを継承します。
- src/App/TestBundle/Resources/views/Default/test.html.twig
{% extends "::base.html.twig" %} {% block body %} ここはROLE_ADMINとROLE_TESTがアクセス可能です。<br /> <a href="{{ path('test_logout') }}">ログアウト</a> {% endblock %}
ここまで出来たらデータベースにアクセスし、RoleテーブルにROLE_USERを登録し、UserテーブルにROLE_USER権限を持つユーザーを登録しましょう。
自分は以下のように登録してあります。
mysql> select * from Role \G *************************** 1. row *************************** id: 1 name: ROLE_ADMIN *************************** 2. row *************************** id: 2 name: ROLE_USER 2 rows in set (0.01 sec) mysql> select * from User \G *************************** 1. row *************************** id: 1 username: user1 password: user1 role_id: 1 *************************** 2. row *************************** id: 2 username: user2 password: user2 role_id: 2 2 rows in set (0.00 sec)
この状態でuser2でログインしてみてください。
ログインすると下記のような画面になるかと思います。
/はROLE_ADMIN権限がないと閲覧出来ないため、上記のようなエラーが発生します。
ではapp_dev.php/testにアクセスしてみてください。
下記のような画面になれば成功です。
デバッグツールバーを見ると、user2でログインしているのが分かります。
プロファイラを開いてユーザー情報を見てみるとROLE_USER権限を持っているのが分かります。
一度ログアウトし、user1でもログインして、app_dev.php/testにアクセスしてみて下さい。
user1でもアクセス出来れば問題ありません。
フォーム認証(DBで権限管理 ManyToMany)
次は1ユーザーに対して複数の権限を割り当てます。
フォーム認証(DBで権限管理 ManyToOne)をもとにいじっていくと結構簡単に出来てしまいます。
変更するのはEntityクラスのUser.phpとRole.phpのみです。
では早速いじっていきます。
- src/App/TestBundle/Entity/User.php
<?php // ... use \Doctrine\Common\Collections\ArrayCollection; class User implements UserInterface { // ... /** * @var ArrayCollection $userRoles * * @ORM\ManyToMany(targetEntity="Role") * @ORM\JoinTable(name="UserRole", * joinColumns={@ORM\JoinColumn(name="user_id", referencedColumnName="id", onDelete="CASCADE")}, * inverseJoinColumns={@ORM\JoinColumn(name="role_id", referencedColumnName="id")} * ) */ protected $userRoles; /** * construct */ public function __construct() { $this->userRoles = new ArrayCollection(); } // ... /** * @return ArrayCollection|\Doctrine\Common\Collections\ArrayCollection */ public function getUserRoles() { return $this->userRoles; } /** * @return array */ public function getRoles() { return $this->getUserRoles()->toArray(); } }
変更点としては$roleプロパティをまず削除し、それに対応するsetRole, getRoleメソッドを削除します。
その後、$userRolesプロパティを追加し、アノテーションを設定します。(アノテーションの内容についてはもう少し調べてみる)
後は各メソッドを追加し、終了。
次にRoleクラスの方を変更します。
- src/App/TestBundle/Entity/Role.php
<?php namespace App\TestBundle\Entity; use Doctrine\ORM\Mapping as ORM; use Symfony\Component\Security\Core\Role\RoleInterface; /** * App\TestBundle\Entity\Role * * @ORM\Table() * @ORM\Entity */ class Role implements RoleInterface { /** * @var integer $id * * @ORM\Column(name="id", type="integer") * @ORM\Id * @ORM\GeneratedValue(strategy="AUTO") */ protected $id; /** * @var string $name * * @ORM\Column(name="name", type="string", length=100) */ protected $name; /** * Get id * * @return integer */ public function getId() { return $this->id; } /** * Set name * * @param string $name */ public function setName($name) { $this->name = $name; } /** * Get name * * @return string */ public function getName() { return $this->name; } /** * Get Role * * @return string */ public function getRole() { return $this->getName(); } }
先ほどの$usersプロパティを削除し、それに関連するメソッドを削除したくらいです。
非常にシンプルなクラスとなりました。
ここまで完了した後は下記のコマンドを実行してください。
$ php app/console doctrine:schema:update --force
上記コマンドが完了するとデータベースにUserRoleテーブルが作成されているはずです。
ユーザーに権限を複数登録してみてください。
自分は下記のように登録してみました。
mysql> select * from User\G; *************************** 1. row *************************** id: 1 username: user1 password: user1 salt: *************************** 2. row *************************** id: 2 username: user2 password: user2 salt: 2 rows in set (0.00 sec) mysql> select * from Role\G; *************************** 1. row *************************** id: 1 name: ROLE_ADMIN *************************** 2. row *************************** id: 2 name: ROLE_USER *************************** 3. row *************************** id: 3 name: ROLE_TEST 3 rows in set (0.00 sec) mysql> select * from UserRole\G *************************** 1. row *************************** user_id: 1 role_id: 1 *************************** 2. row *************************** user_id: 1 role_id: 2 *************************** 3. row *************************** user_id: 1 role_id: 3 *************************** 4. row *************************** user_id: 2 role_id: 2 4 rows in set (0.00 sec)
ここまで完了したら、ログインしてみてプロファイラから権限を確認してみてください。
下記のように複数権限が割り当てられていれば成功です!
以上で、認証関係のメモは終わりです。
パスワードのエンコード方式もやろうとしましたが、それは別途やりたいと思います。
メモってみて再認識したけど、Securityコンポーネントは半端ない!
ちなみに自分が取りあげた機能はSecurityコンポーネントの一部に過ぎません。
Securityコンポーネントではもっと色々出来るみたいですね。