Help us understand the problem. What is going on with this article?

Symfonyの認可について

初めに

Userに紐づいたPostエンティティがあったとき「自分の所有するPost以外は見れない」という用件を達成するにはどのような実装方法があるか、という話です。

最も単純な方法

最も単純な方法は、コントローラーでログインしているUserPostかどうか判断する方法です。特に難しいこともありません。
が、しかし、 アクセス権を確認する際に同じようなコードが至る所に出てきてしまうことが容易に想像できます。

    /**
     * @Route("/{id}/detail", name="post_detail")
     */
    public function edit(Post $post)
    {
        $user = $this->getUser();
        if ($post->getUser() !== $user) {
            throw $this->createAccessDeniedException();
        }
        ...
    }

Voterを用いる方法

SymfonyにはVoterという認可のための仕組みがあります1
こちらを用いることで、認可のロジックを分離することができ、あらゆる箇所で使い回すことが出来るようになります。

makeコマンドで作れます。

bin/console make:voter

 The name of the security voter class (e.g. BlogPostVoter):
 > PostVoter

 created: src/Security/Voter/PostVoter.php

makeコマンドで作ると雛形が作成されます。今回は閲覧だけなのでこのように書き換えます。

src/Security/Voter/PostVoter.php
<?php

namespace App\Security\Voter;

use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
use Symfony\Component\Security\Core\User\UserInterface;

class PostVoter extends Voter
{
    protected function supports($attribute, $subject)
    {
        return in_array($attribute, ['VIEW'])
            && $subject instanceof \App\Entity\Post;
    }

    protected function voteOnAttribute($attribute, $subject, TokenInterface $token)
    {
        $user = $token->getUser();
        if (!$user instanceof UserInterface) {
            return false;
        }

        switch ($attribute) {
            case 'VIEW':
                return $subject->getUser() === $user; // $subjectに認可対象のエンティティが入る
        }

        return false;
    }
}

これを、コントローラーからこのようにして呼び出すことができます。内部ではVotervoteOnAttributeメソッドが呼び出され、アクセス権がないと判断されると、自動でAccessDeniedExceptionが投げられます。

    /**
     * @Route("/{id}/detail", name="post_detail")
     */
    public function edit(Post $post)
    {
        $this->denyAccessUnlessGranted('VIEW', $post);
        ...
    }

更に美しく

コントローラーでの認可に限った話で言えば、アノテーションを使うことで更にメソッド内部をすっきりさせることができます。
認可のためのコードがメソッド内に1行も現れず、本来のロジックのみに集中出来るのでとても見やすくて良いと思います。

    /**
     * @Route("/{id}/detail", name="post_detail")
     * @IsGranted("VIEW", subject="post")
     */
    public function edit(Post $post)
    {
        ...
    }

参考文献

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away