Symfony Security

認証ライブラリSymfonyPHPファイアウォール認証認可セキュリティミドルウェアCSRF

認証ライブラリ

Symfony Security

概要

Symfony Securityは、Symfonyフレームワークの核となるセキュリティコンポーネントです。認証(Authentication)と認可(Authorization)の包括的なシステムを提供し、ファイアウォール、認証プロバイダー、アクセス制御などの機能を統合したセキュアなWebアプリケーション開発を可能にします。

詳細

Symfony Securityは、モジュラー設計により柔軟で拡張可能なセキュリティ機能を提供します。ファイアウォールシステムを中心とした認証メカニズム、ロールベースアクセス制御(RBAC)、CSRF保護、二要素認証サポートなど、企業レベルのセキュリティ要件に対応します。

主要コンポーネント

  • Firewall(ファイアウォール): リクエストフィルタリングと認証機能
  • Authenticator: 認証方式を定義するミドルウェア
  • User Provider: ユーザー情報の取得とロード
  • Access Control: URL ベースの認可ルール
  • Guard Middleware: 認証チェックの強制実行

認証方式

  • Form Login: フォームベース認証
  • HTTP Basic: Basic認証
  • Bearer Token: JWT、APIトークン認証
  • JSON Login: JSON形式のログイン
  • X.509: クライアント証明書認証
  • Remote User: 外部認証システム連携

高度なセキュリティ機能

  • 2FA(二要素認証): TOTP、SMS認証
  • Remember Me: 永続的なログイン機能
  • CSRF Protection: クロスサイトリクエストフォージェリ対策
  • Security Voters: カスタム認可ロジック

メリット・デメリット

メリット

  • 包括的セキュリティ: 認証から認可まで完全対応
  • 柔軟な設定: YAML、XML、PHPでの詳細設定
  • 多様な認証方式: 豊富な認証オプション
  • エンタープライズ対応: 大規模システムに適用可能
  • コミュニティサポート: 豊富なドキュメントとサポート
  • モジュラー設計: 必要な機能のみ選択使用可能

デメリット

  • 学習コスト: 設定が複雑で習得に時間を要する
  • パフォーマンス: 多機能ゆえの処理オーバーヘッド
  • Symfony依存: Symfonyフレームワーク専用
  • 設定の複雑さ: 細かい設定が必要で初期構築が困難

参考ページ

書き方の例

基本設定(security.yaml)

# config/packages/security.yaml
security:
    enable_authenticator_manager: true
    
    password_hashers:
        App\Entity\User:
            algorithm: 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
                check_path: app_login
            logout:
                path: app_logout
    
    access_control:
        - { path: ^/admin, roles: ROLE_ADMIN }
        - { path: ^/profile, roles: ROLE_USER }

カスタム認証プロバイダー

<?php
// src/Security/ApiKeyAuthenticator.php
namespace App\Security;

use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Http\Authenticator\AbstractAuthenticator;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\Passport;
use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport;

class ApiKeyAuthenticator extends AbstractAuthenticator
{
    public function supports(Request $request): ?bool
    {
        return $request->headers->has('X-API-TOKEN');
    }

    public function authenticate(Request $request): Passport
    {
        $apiToken = $request->headers->get('X-API-TOKEN');
        
        if (null === $apiToken) {
            throw new CustomUserMessageAuthenticationException('No API token provided');
        }

        return new SelfValidatingPassport(new UserBadge($apiToken));
    }

    public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response
    {
        return null;
    }

    public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response
    {
        $data = [
            'message' => strtr($exception->getMessageKey(), $exception->getMessageData())
        ];

        return new JsonResponse($data, Response::HTTP_UNAUTHORIZED);
    }
}

Bearer トークン認証設定

# config/packages/security.yaml
security:
    firewalls:
        api:
            pattern: ^/api/
            stateless: true
            bearer_token:
                token_handler: App\Security\AccessTokenHandler
        
        main:
            lazy: true
            form_login:
                login_path: app_login
                check_path: app_login

アクセストークンハンドラー

<?php
// src/Security/AccessTokenHandler.php
namespace App\Security;

use Symfony\Component\Security\Core\Exception\BadCredentialsException;
use Symfony\Component\Security\Http\AccessToken\AccessTokenHandlerInterface;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;

class AccessTokenHandler implements AccessTokenHandlerInterface
{
    public function getUserBadgeFrom(string $accessToken): UserBadge
    {
        // トークンの検証とユーザー識別
        if (!$this->isValidToken($accessToken)) {
            throw new BadCredentialsException('Invalid credentials.');
        }

        $userIdentifier = $this->getUserIdentifierFromToken($accessToken);
        
        return new UserBadge($userIdentifier);
    }

    private function isValidToken(string $token): bool
    {
        // トークン検証ロジック
        return hash('sha256', 'your-secret') === $token;
    }

    private function getUserIdentifierFromToken(string $token): string
    {
        // トークンからユーザー識別子を取得
        return '[email protected]';
    }
}

複数認証方式の組み合わせ

# config/packages/security.yaml
security:
    firewalls:
        main:
            lazy: true
            provider: app_user_provider
            # 複数の認証方式を同時に有効化
            form_login:
                login_path: app_login
                check_path: app_login
            custom_authenticators:
                - App\Security\SocialConnectAuthenticator
                - App\Security\ApiKeyAuthenticator
            
            # エントリーポイントを明示的に指定
            entry_point: form_login

Guard ミドルウェア使用例

<?php
// src/Controller/AdminController.php
namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;

class AdminController extends AbstractController
{
    #[Route('/admin', name: 'admin_dashboard')]
    #[IsGranted('ROLE_ADMIN')]
    public function dashboard(): Response
    {
        // 管理者のみアクセス可能
        return $this->render('admin/dashboard.html.twig');
    }
    
    #[Route('/admin/users', name: 'admin_users')]
    public function users(): Response
    {
        // プログラムでロール確認
        $this->denyAccessUnlessGranted('ROLE_ADMIN');
        
        return $this->render('admin/users.html.twig');
    }
}

CSRF保護の実装

<?php
// src/Form/LoginType.php
namespace App\Form;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

class LoginType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('email', TextType::class)
            ->add('password', PasswordType::class)
            ->add('login', SubmitType::class);
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults([
            'csrf_protection' => true,
            'csrf_field_name' => '_token',
            'csrf_token_id'   => 'authenticate',
        ]);
    }
}

ユーザー エンティティ例

<?php
// src/Entity/User.php
namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
use Symfony\Component\Security\Core\User\UserInterface;

#[ORM\Entity]
class User implements UserInterface, PasswordAuthenticatedUserInterface
{
    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column(type: 'integer')]
    private int $id;

    #[ORM\Column(type: 'string', length: 180, unique: true)]
    private string $email;

    #[ORM\Column(type: 'json')]
    private array $roles = [];

    #[ORM\Column(type: 'string')]
    private string $password;

    public function getUserIdentifier(): string
    {
        return $this->email;
    }

    public function getRoles(): array
    {
        $roles = $this->roles;
        $roles[] = 'ROLE_USER';

        return array_unique($roles);
    }

    public function getPassword(): string
    {
        return $this->password;
    }

    public function eraseCredentials(): void
    {
        // 一時的な機密データをクリア
    }
}