🎻

[Symfony] Securityアノテーションを使って複雑な権限チェックを行う

2021/12/10に公開

Symfony Advent Calendar 2021 の10日目の記事です!🎄🌙小ネタですみません!

ちなみに、僕はよく TwitterにもSymfonyネタを呟いている ので、よろしければぜひ フォローしてやってください🕊🤲

昨日は @77web さんの Symfony5.0以降でのMessengerの改善ポイントまとめ でした✨

やりたいこと

  • あるエンティティの「詳細表示画面」兼「編集画面」にあたるような画面があるとします
  • つまり、エンティティの各プロパティの内容がフォームの状態で表示されていて内容を確認でき、その画面で内容を修正してフォームを送信すれば編集もできる、というような画面です
  • この画面のコントローラアクションでは、GET でリクエストしたときには単にフォームを表示し、POST でリクエストしたときにはフォームの送信内容を受け取ってエンティティを更新することになります
  • ところで、このアプリにはログイン認証があり、ユーザーには閲覧権限と編集権限があります
  • この場合に、このコントローラアクションに対して、GET リクエストは誰でもできるけど POST リクエストは編集権限がないとできない という振る舞いを持たせたいとしましょう

普通に

/**
* @Route("/{id}", name="show", methods={"GET", "POST"})
* @IsGranted("ROLE_EDIT")
*/
public function show(Request $request)
{
   // ...
}

のようにアクションメソッドに @IsGranted("ROLE_EDIT") をつけてしまうと、GET リクエストすらできなくなってしまいますよね。

ではどうすればいいでしょうか?

やり方

結論としては、以下のように @Security アノテーション を活用すればスマートに実現できます。

/**
* @Route("/{id}", name="show", methods={"GET", "POST"})
* @Security("request.getMethod() === 'GET' or is_granted('ROLE_EDIT')")
*/
public function show(Request $request)
{
   // ...
}

ただし、@Security アノテーションを利用するには、ExpressionLanguage コンポーネント がインストールされている必要があります。

ExpressionLanguageがインストールされていない状態で @Security アノテーションを付した画面にアクセスすると、以下のようなエラーになります。

To use the @Security tag, you need to use the Security component 2.4 or newer and install the ExpressionLanguage component.

@Security アノテーションは、ExpressionLanguageを使って複雑な条件式を記述してアクセス制御を行うことができる機能で、ドキュメントに記載されているとおりsecurity.yamlaccess_control セクションで使用可能なすべての関数(*1)および is_granted() 関数を式内で利用できます。

*1:以下のドキュメントなどをご参照ください。

How Does the Security access_control Work? (Symfony Docs)
Security: Complex Access Controls with Expressions (Symfony Docs)

また、同じくドキュメントに記載されていますが、以下の変数についても式内で利用可能です。

  • token:現在のセキュリティトークン
  • user:現在のログインユーザーの User インスタンス
  • request:現在の Request\Symfony\Component\HttpFoundation\Request)インスタンス
  • roles:現在のログインユーザーのROLEのリスト

これらの機能を使って、

@Security("request.getMethod() === 'GET' or is_granted('ROLE_EDIT')")

つまり

  • リクエストメソッドが GET の場合は常に許可
  • ROLE_EDIT 権限を持っている場合も常に許可

という設定を書いてあげることで、GET リクエストは誰でもできるけど POST リクエストは編集権限がないとできない という振る舞いを持たせることができるわけです👍

ちなみに、(これもドキュメントに載っていますが)@Security アノテーションには statusCode というプロパティがあり、以下のように拒否時のステータスコードを指定することもできます。(省略時はデフォルトで 403 Access Denied になります)

@Security("request.getMethod() === 'GET' or is_granted('ROLE_EDIT')", statusCode=404)

@Security アノテーションは色々と柔軟に書けるので、他にも例えば、特定のリクエストヘッダーが付加されていない場合に 400 Bad Request にするとかも以下のような感じで対応可能ですね。

@Security("request.headers.get('X-FOO') === 'bar'", statusCode=400)

シーンに応じて有効活用してみてください😉

以上、どこかの誰かのお役に立てば幸いです!

Symfony Advent Calendar 2021、明日は @kalibora さんです!お楽しみに!

GitHubで編集を提案

Discussion