LoginSignup
1
1

More than 3 years have passed since last update.

Symfonyの認可について

Posted at

初めに

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)
    {
        ...
    }

参考文献

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1