# 2026年版 Symfony REST APIセキュリティ完全ガイド: OAuth2、レート制限、面接対策 > Symfony 7.3で実装するREST APIセキュリティの実践ガイド。OAuth2トークンイントロスペクション、RateLimiterコンポーネント、Voter認可、技術面接対策を解説します。 - Published: 2026-09-14 - Updated: 2026-09-14 - Author: Anthony Fillion-Maillet - Tags: symfony, security, oauth2, api, rate-limiting - Reading time: 12 min --- Symfony REST APIのセキュリティを確保するには、認証、認可、トラフィック制御を組み合わせた多層防御が不可欠です。Symfony 7.3ではRFC 7662に準拠したOAuth2トークンイントロスペクションがネイティブサポートされ、RateLimiterコンポーネントによる不正アクセス対策も強化されています。 > **Symfony APIの3層セキュリティ** > > 本番環境で運用するSymfony APIには、認証(誰がアクセスしているか)、認可(何にアクセスできるか)、レート制限(どのくらいの頻度でアクセスできるか)の3つの防御層が必要です。いずれかが欠けると、クレデンシャルスタッフィング、データ漏洩、サービス拒否攻撃のリスクにさらされます。 ## Symfony 7.3のOAuth2トークンイントロスペクション Symfony 7.3では、[OAuth2トークンイントロスペクション(RFC 7662)](https://datatracker.ietf.org/doc/html/rfc7662)がビルトインでサポートされています。この機能により、認可サーバーに直接問い合わせてアクセストークンを検証でき、トークンをローカルでデコードする必要がなくなります。トークン形式が不透明な場合や、サードパーティのIDプロバイダーが管理している場合に特に有効です。 `AccessTokenHandler`の設定でイントロスペクションエンドポイントを指定します: ```yaml # config/packages/security.yaml security: firewalls: api: pattern: ^/api stateless: true access_token: token_handler: introspection: client_id: '%env(OAUTH_CLIENT_ID)%' client_secret: '%env(OAUTH_CLIENT_SECRET)%' introspection_url: '%env(OAUTH_INTROSPECTION_URL)%' ``` イントロスペクションのレスポンスには`active`、`scope`、`client_id`、オプションで`username`が含まれます。Symfonyはこれらをセキュリティ属性に自動的にマッピングします。 ```php // src/Controller/Api/ProfileController.php namespace App\Controller\Api; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\Routing\Attribute\Route; use Symfony\Component\Security\Http\Attribute\IsGranted; #[Route('/api/profile', methods: ['GET'])] #[IsGranted('ROLE_USER')] class ProfileController extends AbstractController { public function __invoke(): JsonResponse { // トークンはイントロスペクションで既に検証済み $user = $this->getUser(); return $this->json([ 'id' => $user->getId(), 'email' => $user->getEmail(), 'scopes' => $user->getRoles(), ]); } } ``` このアプローチにより、JWT署名検証の負担をアプリケーションから取り除けます。鍵のローテーション、トークンの失効、スコープの検証はすべて認可サーバーが処理します。 ## RateLimiterコンポーネントによるレート制限の実装 [RateLimiterコンポーネント](https://symfony.com/doc/current/rate_limiter.html)は、トークンバケットまたはスライディングウィンドウアルゴリズムを使用してエンドポイントを不正利用から保護します。設定は`framework.yaml`で行います: ```yaml # config/packages/framework.yaml framework: rate_limiter: # 一般的なAPIレート制限: 1分間に100リクエスト api_limiter: policy: sliding_window limit: 100 interval: '1 minute' # 認証エンドポイント用の厳格な制限 login_limiter: policy: token_bucket limit: 5 rate: { interval: '1 minute', amount: 5 } ``` イベントサブスクライバーを使用してコントローラーにレート制限を適用します: ```php // src/EventSubscriber/RateLimitSubscriber.php namespace App\EventSubscriber; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\HttpKernel\Event\RequestEvent; use Symfony\Component\HttpKernel\Exception\TooManyRequestsHttpException; use Symfony\Component\HttpKernel\KernelEvents; use Symfony\Component\RateLimiter\RateLimiterFactory; final class RateLimitSubscriber implements EventSubscriberInterface { public function __construct( private readonly RateLimiterFactory $apiLimiter, ) {} public static function getSubscribedEvents(): array { return [KernelEvents::REQUEST => ['onRequest', 10]]; } public function onRequest(RequestEvent $event): void { if (!$event->isMainRequest()) { return; } $request = $event->getRequest(); // APIルートのみに適用 if (!str_starts_with($request->getPathInfo(), '/api')) { return; } // クライアントIPまたは認証済みユーザーをリミッターキーとして使用 $key = $request->getClientIp(); $limiter = $this->apiLimiter->create($key); if (!$limiter->consume()->isAccepted()) { throw new TooManyRequestsHttpException(); } } } ``` 認証済みAPIの場合、IPベースのキーをユーザー識別子に置き換えることで、同一ネットワークからの正当なトラフィックが悪意のあるユーザーの影響を受けることを防げます。 ## 外部依存なしのJWT検証 認可サーバーが既知の公開鍵でJWTを発行する場合、Symfonyは`OidcUserInfoTokenHandler`を使用してトークンをローカルで検証できます: ```yaml # config/packages/security.yaml security: firewalls: api: pattern: ^/api stateless: true access_token: token_handler: oidc_user_info: base_uri: '%env(OIDC_ISSUER)%' claim: email ``` OIDCプロバイダーなしで自己発行JWTを使用する場合は、`lexik/jwt-authentication-bundle`を使用します: ```php // src/Security/JwtTokenAuthenticator.php namespace App\Security; use Lexik\Bundle\JWTAuthenticationBundle\Services\JWTTokenManagerInterface; use Symfony\Component\Security\Http\AccessToken\AccessTokenHandlerInterface; use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge; final class JwtTokenAuthenticator implements AccessTokenHandlerInterface { public function __construct( private readonly JWTTokenManagerInterface $jwtManager, ) {} public function getUserBadgeFrom(string $accessToken): UserBadge { // 署名を自動的にデコードして検証 $payload = $this->jwtManager->parse($accessToken); return new UserBadge($payload['sub']); } } ``` JWTバンドルは、RS256/ES256署名検証、有効期限チェック、発行者検証を処理します。公開鍵は`config/jwt/public.pem`に保存し、`lexik_jwt_authentication.yaml`で参照します。 ## Voterによるきめ細かいAPIエンドポイント認可 Symfony Voterは、単純なロールチェックを超えたきめ細かい認可を提供します。一般的なパターンとして、リソースの所有権をチェックする実装があります: ```php // src/Security/Voter/ArticleVoter.php namespace App\Security\Voter; use App\Entity\Article; use App\Entity\User; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Authorization\Voter\Voter; final class ArticleVoter extends Voter { public const EDIT = 'ARTICLE_EDIT'; public const DELETE = 'ARTICLE_DELETE'; protected function supports(string $attribute, mixed $subject): bool { return in_array($attribute, [self::EDIT, self::DELETE], true) && $subject instanceof Article; } protected function voteOnAttribute( string $attribute, mixed $subject, TokenInterface $token ): bool { $user = $token->getUser(); if (!$user instanceof User) { return false; } /** @var Article $article */ $article = $subject; // 管理者はすべての操作が可能 if (in_array('ROLE_ADMIN', $user->getRoles(), true)) { return true; } // 著者は自分の記事を編集・削除可能 return $article->getAuthor() === $user; } } ``` コントローラーでVoterを使用します: ```php #[Route('/api/articles/{id}', methods: ['PUT'])] public function update(Article $article, Request $request): JsonResponse { $this->denyAccessUnlessGranted(ArticleVoter::EDIT, $article); // 更新ロジック } ``` Voterは認可ロジックを集約し、テスト可能にします。セキュリティコンポーネントに関する[Symfony面接質問](/technologies/symfony/interview-questions/events-subscribers)でも頻出のトピックです。 ## 一般的なAPI脆弱性とその対策 [OWASP API Security Top 10](https://owasp.org/API-Security/editions/2023/en/0x11-t10/)は、APIにおける再発性の高い脆弱性を特定しています。Symfonyはこれらの多くに対してビルトインの保護を提供しています: | 脆弱性 | Symfonyの対策 | |--------|---------------| | オブジェクトレベル認可の不備 | 所有権チェック付きVoter | | 認証の不備 | ログインスロットリング、安全なパスワードハッシュ | | 過剰なデータ公開 | DTOシリアライゼーショングループ | | レート制限の欠如 | RateLimiterコンポーネント | | マスアサインメント | フォームバリデーション、DTOマッピング | | セキュリティ設定ミス | Symfonyセキュリティチェッカー、環境変数 | マスアサインメント対策として、リクエストデータからエンティティを直接ハイドレートしないでください: ```php // src/Dto/CreateArticleDto.php namespace App\Dto; use Symfony\Component\Validator\Constraints as Assert; final class CreateArticleDto { public function __construct( #[Assert\NotBlank] #[Assert\Length(max: 255)] public readonly string $title, #[Assert\NotBlank] public readonly string $content, // 意図的に省略: author, createdAt, status // これらはアプリケーションが設定し、クライアントが設定するものではない ) {} } ``` DTOホワイトリストアプローチにより、`isAdmin`や`createdAt`など、クライアントが制御すべきでないフィールドの設定を防止できます。 ## APIコンシューマー向けCORS設定 Cross-Origin Resource Sharingヘッダーは、ブラウザからAPIを呼び出せるドメインを制御します。`nelmio/cors-bundle`は宣言的な設定を提供します: ```yaml # config/packages/nelmio_cors.yaml nelmio_cors: defaults: allow_origin: ['%env(CORS_ALLOW_ORIGIN)%'] allow_methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'] allow_headers: ['Content-Type', 'Authorization'] max_age: 3600 paths: '^/api/': origin_regex: true allow_origin: ['^https://.*\.example\.com$'] allow_credentials: true ``` `allow_origin: ['*']`と`allow_credentials: true`の組み合わせは避けてください。この組み合わせは、悪意のあるサイトによるクレデンシャル窃取にAPIをさらします。明示的なドメインホワイトリストまたは正規表現パターンを使用してください。 ## Symfony APIセキュリティの技術面接質問 これらの質問は、シニアSymfony開発者の面接で頻繁に出題されます。回答はSymfony 7.3の機能を反映しています。 **Q: SymfonyはOAuth2アクセストークンの検証をどのように処理しますか?** Symfony 7.3は3つのアプローチをサポートしています:署名検証を伴うローカルJWT検証、OIDCユーザー情報エンドポイント呼び出し、[RFC 7662トークンイントロスペクション](https://symfony.com/blog/new-in-symfony-7-3-security-improvements)です。イントロスペクションは、不透明トークンや、アプリケーションが認可サーバーを制御しないシナリオに適しています。`AccessTokenHandler`は、これらすべてを統一されたインターフェースの背後に抽象化しています。 **Q: Symfonyにおけるファイアウォールとアクセス制御の違いは何ですか?** ファイアウォールは、URLパターンごとに認証メカニズムを定義します。アクセス制御ルールは、認証が成功した後の認可要件を定義します。ファイアウォールは有効なJWTを要求し、アクセス制御は`/api/admin/*`に対して`ROLE_ADMIN`を要求するといった具合です。これらは順番に動作します:ファイアウォールが認証し、次にアクセス制御が認可します。 **Q: ログインエンドポイントへのブルートフォース攻撃をどのように防止しますか?** Symfonyのビルトインログインスロットリングは、ユーザー名とIPごとに失敗した試行を制限します。ファイアウォールで`login_throttling`を設定します: ```yaml security: firewalls: main: login_throttling: max_attempts: 5 interval: '15 minutes' ``` API認証の場合、トークンエンドポイントでRateLimiterコンポーネントと組み合わせます。マルチインスタンスデプロイメントでは、試行回数をRedisに保存します。 **Q: Symfony Voterと、単純なロールチェックをいつ使い分けるべきか説明してください。** Voterは、ユーザーロールだけでなくリソースに依存する複雑な認可ロジックを実装します。Voterを使用する場面:ユーザーがリソースを所有している必要がある場合、リソースにステートマシンがある場合(下書きvs公開済み)、認可がビジネスルールに依存する場合(サブスクリプションティア)です。静的な権限(「管理者のみが設定にアクセスできる」など)にはロールチェックで十分です。 その他のSymfonyセキュリティに関する質問については、[Symfony面接対策ガイド](/blog/symfony/symfony-interview-questions)をご覧ください。 ## セキュアなSymfony API構築:重要ポイント - トークン形式が不透明なサードパーティIDプロバイダーにはOAuth2トークンイントロスペクションを設定する - 多層防御のため、インフラストラクチャレベル(リバースプロキシ)とアプリケーションレベル(RateLimiterコンポーネント)の両方でレート制限を適用する - リソースレベルの認可にはVoterを使用し、静的な権限にはロールチェックを予約する - マスアサインメントを防ぐため、明示的なプロパティを持つDTOにリクエストデータをマッピングする - シークレットは環境変数に保存し、バージョン管理にコミットされる`config/*.yaml`ファイルには決して保存しない - `WebTestCase`機能テストで、許可されたアクセスと拒否されたアクセスの両方を検証してセキュリティルールをテストする - CIパイプラインで`symfony security:check`を使用して依存関係を監査する --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/ja/blog/symfony/symfony-rest-api-security-oauth2-rate-limiting-2026