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
には、主に認証ユーザを取得する方法
, パスワードの暗号方法
, 認証する方法
, 認証が必要な場所(正確には認可したユーザのみがアクセスできる場所)
を設定します。
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を複数設定することで、順番に取得を試みます。
security:
providers:
db:
entity:
# ...
memory:
memory:
# ...
all_users:
chain:
providers: ['db', 'memory'] <- `db`で取得した後、ダメだったら`memory`で取得する
また、UserProviderInterface
を実装したクラスを作って、独自のProviderを設定することもできます。
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
{
// パスワードを更新する処理
}
}
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
の子クラスの場合は、さらに簡単に取得できます。
use Symfony\Bundle\SecurityBundle\Security;
class SomeClass
{
public function __construct(private readonly Security $security)
{
}
public function invoke()
{
$user = $this->security->getUser();
}
}
class UserController extends AbstractController
{
public function detail(): Response
{
$user = $this->getUesr();
}
}
まとめ
今回はSecurity Bundle
をご紹介しました。
ここまで(かなり端折ったのにも関わらず)長く説明してきましたが、実際使うときはSymfonyが用意してくれた設定を選んでsecurity.yamlに書き込めばサクッと認証機能が作れるので、思っている以上に簡単に認証できるようになります。
他にも認可やCSRF、LDAPなどの機能もあります。詳しくは公式ドキュメントをじっくり読んでみてください!
(思った以上にボリュームがあったので、別途認可の記事をこっそり書きます。)