0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Symfony ComponentAdvent Calendar 2022

Day 14

Webアプリケーションをセキュアに、"Security Bundle"

Last updated at Posted at 2022-12-13

Symfony Component Advent Calendar 2022の14日目の記事です。

最初に

SymfonyはPHPのフレームワークのひとつです。しかし、公式サイトの説明文には

Symfony is a set of PHP Components, a Web Application framework, a Philosophy, and a Community — all working together in harmony.
(SymfonyはPHPコンポーネントのセットで、Webアプリケーションフレームワークで、哲学、そしてコミュニティです。それらがハーモニーを奏でながら動作しています。)

と書かれている通り、PHPコンポーネントのセットで、たくさんのコンポーネントを提供しており、それらを組み合わせてひとつのフレームワークとして動作しています。Symfonyのコンポーネントは、Symfony上だけで動作するのではなく、他のPHPフレームワークやアプリケーションでも動作している強力なものが揃っています。

今回はそれらの中から、役立ちそうなもの・お薦めしたいものを紹介していきたいと思います。

※記事内ではautoloadのインポートは省略します。

Webアプリケーションをセキュアに、"Security Bundle"

Security Bundleは、SymfonyにおおけるWebに関わるセキュリティ向上をサポートするコンポーネントです。特に、認証・認可の機能が有名です。

ちょっとボリュームが大きいので、今回は認証部分について紹介します。

インストール

composer require symfony/security-bundle

認証

認証、ざっくりいうとログイン機能に関してはFirewallというものが用意されています。このFirewallには認証に関わる設定や利用するクラスを設定することで、簡単に認証機能を用意することができます。

認証ユーザEntityとその他諸々の作成

まず認証ユーザを扱うためのEntityクラスと、それに関わるファイルをサクッと作ります。
Maker Bundleを使うことで、簡単に作成できます。
UserクラスはUserInterface, PasswordAuthenticatedUserInterface を実装しています。

bin/console make:user

Firewall設定

Firewallには、主に認証ユーザを取得する方法, パスワードの暗号方法, 認証する方法, 認証が必要な場所(正確には認可したユーザのみがアクセスできる場所) を設定します。

config/packages/security.yaml
security:
    # パスワードの暗号方法
    password_hashers:
        Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 'auto'

    # 認証ユーザを取得する方法
    providers:
        app_user_provider: <- 設定名
            entity:
                class: App\Entity\User
                property: email
    firewalls:
        dev: <- 設定名
            pattern: ^/(_(profiler|wdt)|css|images|js)/
            security: false
        main: <- 設定名
            lazy: true
            provider: app_user_provider <- 利用するプロバイダ名
            form_login: <- 認証方法
                login_path: app_login
            login_throttling: <- ログイン試行回数
                max_attempts: 3
            # https://symfony.com/doc/current/security/impersonating_user.html
            switch_user: true

    # 認可したユーザのみアクセスできる場所
    access_control:
        - { path: ^/admin, roles: ROLE_ADMIN }
        - { path: ^/mypage, roles: ROLE_USER }
認証ユーザを取得する方法

providersに認証ユーザをどこから取得する方法を設定できます。デフォルトで4つのProviderを用意してあり、

設定名 取得箇所
entity DBから(Doctrineを使って)
ldap LDAPサーバから
memory 設定ファイルから
chain プロバイダの組み合わせ

が利用できます。主にentity, テストでmemoryを利用します。chainはちょっと特殊で、別途設定したproviderを複数設定することで、順番に取得を試みます。

config/packages/security.yaml
security:
    providers:
        db:
            entity:
                # ...

        memory:
            memory:
                # ...
        all_users:
            chain:
                providers: ['db', 'memory'] <- `db`で取得した後、ダメだったら`memory`で取得する

また、UserProviderInterfaceを実装したクラスを作って、独自のProviderを設定することもできます。

UserProvider.php

use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
use Symfony\Component\Security\Core\Exception\UserNotFoundException;
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
use Symfony\Component\Security\Core\User\PasswordUpgraderInterface;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;

class UserProvider implements UserProviderInterface, PasswordUpgraderInterface
{
    public function loadUserByIdentifier(string $identifier): UserInterface
    {
        // 入力されたIDからユーザを取得する処理(代理ログインや、パスワード忘れなどで利用)
    }

    public function refreshUser(UserInterface $user)
    {
        // セッション内のユーザを取得して妥当かチェックする処理
    }

    public function supportsClass(string $class)
    {
        // このProviderの対象クラスかチェックする処理
    }

    /**
     * Upgrades the hashed password of a user, typically for using a better hash algorithm.
     */
    public function upgradePassword(PasswordAuthenticatedUserInterface $user, string $newHashedPassword): void
    {
        // パスワードを更新する処理
    }
}
config/packages/security.yaml
security:
    providers:
        my_provider:
            id: App\Security\UserProvider
パスワードの暗号方法

password_hashersに暗号方法を設定できます。デフォルトではautoが指定されていますが、この場合最もセキュアだとSymfonyが考えるアルゴリズムを選択してくれます。自分でsha256, sodium, plaintext(←超危険!テストの時だけ!) などを選択できますが、必要に迫られた時だけにした方が無難です。

認証する方法

firewallに認証する方法を設定します。上記の例であるdevはデバッグツールに対しての設定で、security: falseにより、ここは認証しないという設定になっています。

providerは先ほど紹介した、ユーザを取得する方法で設定したProvider名を指定することで、認証ユーザの取得先を設定できます。

patternは認証が必要な場所を設定できますが、基本的に認証をする方法が複数ある場合のみ設定します。(例:ユーザ画面と管理画面で別のProviderを使うときなど)

switch_userですが、これはユーザ切り替え、いわゆる代理ログインができるようになります。

さらにどのように認証するか設定する必要がありますが、SymfonyではAuthenticatorというものを使ってユーザ認証します。Authenticatorはいくつか用意されているものがあり

設定名 概要
form_login フォームからログイン
json_login APIでjsonを送ってログイン
http_basic Basic認証でログイン
login_link ログイン用のURLを発行しててログイン
access_token アクセストークンでログイン
x509 公開鍵でログイン
remote_user REMOTE_USERヘッダでログイン

さらにAbstractAuthenticatorの子クラスを作ることで、独自の認証方法を用意することができます。
くわしくは以前書いたこちらの記事をご参照ください。
(書いててよかった)

認証したユーザを取得する

認証方法を介して認証したユーザはSecurityオブジェクトからUserInterfaceオブジェクトとして取得できます。さらに、AbstractControllerの子クラスの場合は、さらに簡単に取得できます。

SomeClass.php

use Symfony\Bundle\SecurityBundle\Security;

class SomeClass
{
    public function __construct(private readonly Security $security)
    {
    }

    public function invoke()
    {
        $user = $this->security->getUser();
    }
}
UserController.php

class UserController extends AbstractController
{
    public function detail(): Response
    {
        $user = $this->getUesr();
    }
}

まとめ

今回はSecurity Bundleをご紹介しました。
ここまで(かなり端折ったのにも関わらず)長く説明してきましたが、実際使うときはSymfonyが用意してくれた設定を選んでsecurity.yamlに書き込めばサクッと認証機能が作れるので、思っている以上に簡単に認証できるようになります。

他にも認可CSRFLDAPなどの機能もあります。詳しくは公式ドキュメントをじっくり読んでみてください!

(思った以上にボリュームがあったので、別途認可の記事をこっそり書きます。)

0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?