# ความปลอดภัย REST API Symfony ปี 2026: OAuth2, Rate Limiting และคำถามสัมภาษณ์ > เรียนรู้วิธีรักษาความปลอดภัย REST API Symfony ด้วย OAuth2 token introspection, rate limiting และการตรวจสอบ JWT ครอบคลุมฟีเจอร์ความปลอดภัย Symfony 7.3 ช่องโหว่ทั่วไป และคำถามสัมภาษณ์ทางเทคนิค - Published: 2026-09-14 - Updated: 2026-09-14 - Author: Anthony Fillion-Maillet - Tags: symfony, security, oauth2, rest-api, rate-limiting, jwt - Reading time: 10 min --- ความปลอดภัย REST API ของ Symfony ต้องการแนวทางแบบหลายชั้นที่ผสมผสาน authentication, authorization และการควบคุมทราฟฟิก Symfony 7.3 เปิดตัวการรองรับ OAuth2 token introspection แบบ native ผ่าน RFC 7662 ในขณะที่ component RateLimiter ให้การป้องกันในตัวต่อการใช้งานในทางที่ผิด > **ชั้นความปลอดภัยหลักสำหรับ Symfony API** > > API Symfony ที่พร้อมสำหรับ production ต้องมีการป้องกันสามชั้น: authentication (ใครเป็นผู้เรียก), authorization (สามารถเข้าถึงอะไรได้บ้าง) และ rate limiting (เรียกได้บ่อยแค่ไหน) การขาดชั้นใดชั้นหนึ่งจะทำให้แอปพลิเคชันเสี่ยงต่อ credential stuffing, การขโมยข้อมูล หรือ denial of service ## OAuth2 Token Introspection ใน Symfony 7.3 Symfony 7.3 เพิ่มการรองรับในตัวสำหรับ [OAuth2 Token Introspection (RFC 7662)](https://datatracker.ietf.org/doc/html/rfc7662) ฟีเจอร์นี้ตรวจสอบความถูกต้องของ access token โดยการ query โดยตรงไปยัง authorization server ซึ่งขจัดความจำเป็นในการ decode token ภายในเครื่อง สิ่งนี้สำคัญเมื่อรูปแบบ token เป็น opaque หรือถูกควบคุมโดย identity provider ภายนอก การกำหนดค่า `AccessTokenHandler` รับ endpoint introspection: ```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)%' ``` Response ของ introspection ประกอบด้วย `active`, `scope`, `client_id` และ `username` ซึ่งเป็นตัวเลือก Symfony จับคู่สิ่งเหล่านี้กับ security attributes โดยอัตโนมัติ ```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 { // Token ได้รับการตรวจสอบแล้วผ่าน introspection $user = $this->getUser(); return $this->json([ 'id' => $user->getId(), 'email' => $user->getEmail(), 'scopes' => $user->getRoles(), ]); } } ``` วิธีนี้ขจัดภาระการตรวจสอบลายเซ็น JWT ออกจากแอปพลิเคชัน Authorization server จัดการการหมุนเวียน key, การเพิกถอน token และการตรวจสอบ scope ## การใช้งาน Rate Limiting ด้วย Component RateLimiter [Component RateLimiter](https://symfony.com/doc/current/rate_limiter.html) ปกป้อง endpoint จากการใช้งานในทางที่ผิดโดยใช้ algorithm token bucket หรือ sliding window การกำหนดค่าทำใน `framework.yaml`: ```yaml # config/packages/framework.yaml framework: rate_limiter: # Rate limit API ทั่วไป: 100 request ต่อนาที api_limiter: policy: sliding_window limit: 100 interval: '1 minute' # Limit ที่เข้มงวดกว่าสำหรับ endpoint authentication login_limiter: policy: token_bucket limit: 5 rate: { interval: '1 minute', amount: 5 } ``` ใช้ rate limiting กับ controller โดยใช้ event subscriber: ```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(); // ใช้เฉพาะกับ route API if (!str_starts_with($request->getPathInfo(), '/api')) { return; } // ใช้ IP ของ client หรือ user ที่ผ่านการ authenticate เป็น limiter key $key = $request->getClientIp(); $limiter = $this->apiLimiter->create($key); if (!$limiter->consume()->isAccepted()) { throw new TooManyRequestsHttpException(); } } } ``` สำหรับ API ที่ผ่านการ authenticate ให้แทนที่ key แบบ IP ด้วย identifier ของ user เพื่อป้องกันไม่ให้ user ที่ใช้งานในทางที่ผิดคนเดียวส่งผลกระทบต่อทราฟฟิกที่ถูกต้องจากเครือข่ายเดียวกัน ## การตรวจสอบ JWT โดยไม่ต้องใช้ Dependency ภายนอก เมื่อ authorization server ออก JWT ด้วย public key ที่รู้จัก Symfony สามารถตรวจสอบ token ภายในเครื่องได้โดยใช้ `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 ``` สำหรับ JWT ที่ออกเองโดยไม่มี provider OIDC ให้ใช้ `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 { // Decode และตรวจสอบลายเซ็นโดยอัตโนมัติ $payload = $this->jwtManager->parse($accessToken); return new UserBadge($payload['sub']); } } ``` Bundle JWT จัดการการตรวจสอบลายเซ็น RS256/ES256, การตรวจสอบ expiration และการตรวจสอบ issuer เก็บ public key ไว้ที่ `config/jwt/public.pem` และอ้างอิงใน `lexik_jwt_authentication.yaml` ## การรักษาความปลอดภัย API Endpoints ด้วย Voters Symfony Voters ให้ authorization แบบละเอียดที่เกินกว่าการตรวจสอบ role แบบง่าย รูปแบบทั่วไปคือการตรวจสอบความเป็นเจ้าของ resource: ```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; // Admin สามารถทำอะไรก็ได้ if (in_array('ROLE_ADMIN', $user->getRoles(), true)) { return true; } // ผู้เขียนสามารถแก้ไข/ลบบทความของตนเองได้ return $article->getAuthor() === $user; } } ``` ใช้ voter ใน controller: ```php #[Route('/api/articles/{id}', methods: ['PUT'])] public function update(Article $article, Request $request): JsonResponse { $this->denyAccessUnlessGranted(ArticleVoter::EDIT, $article); // Logic การอัปเดตที่นี่ } ``` Voters รวมศูนย์ logic authorization และทำให้สามารถทดสอบได้ นอกจากนี้ยังปรากฏบ่อยใน [คำถามสัมภาษณ์ Symfony](/technologies/symfony/interview-questions/events-subscribers) เกี่ยวกับ component ความปลอดภัย ## ช่องโหว่ API ทั่วไปและการบรรเทา [OWASP API Security Top 10](https://owasp.org/API-Security/editions/2023/en/0x11-t10/) ระบุช่องโหว่ที่เกิดซ้ำใน API Symfony ให้การป้องกันในตัวสำหรับหลายรายการ: | ช่องโหว่ | การบรรเทา Symfony | |----------|-------------------| | Broken Object Level Authorization | Voters พร้อมการตรวจสอบความเป็นเจ้าของ | | Broken Authentication | Login throttling, secure password hashing | | Excessive Data Exposure | กลุ่ม serialization DTO | | Lack of Rate Limiting | Component RateLimiter | | Mass Assignment | การตรวจสอบ form, mapping DTO | | Security Misconfiguration | Security checker Symfony, env vars | สำหรับการป้องกัน mass assignment ห้าม hydrate entity โดยตรงจากข้อมูล request: ```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 // สิ่งเหล่านี้ตั้งค่าโดยแอปพลิเคชัน ไม่ใช่ client ) {} } ``` แนวทาง whitelist DTO ป้องกันไม่ให้ client ตั้งค่า field ที่ไม่ควรควบคุม เช่น `isAdmin` หรือ `createdAt` ## การกำหนดค่า CORS สำหรับ Consumer API Header Cross-Origin Resource Sharing ควบคุมว่า domain ใดสามารถเรียก API จาก browser ได้ Bundle `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 เสี่ยงต่อการถูกขโมย credential ผ่านเว็บไซต์ที่เป็นอันตราย ใช้ whitelist domain ที่ชัดเจนหรือ pattern regex ## คำถามสัมภาษณ์ทางเทคนิคเกี่ยวกับความปลอดภัย API Symfony คำถามเหล่านี้ปรากฏบ่อยในการสัมภาษณ์ developer Symfony ระดับ senior คำตอบสะท้อนความสามารถของ Symfony 7.3 **ถ: Symfony จัดการการตรวจสอบ access token OAuth2 อย่างไร?** Symfony 7.3 รองรับสามแนวทาง: การตรวจสอบ JWT ภายในเครื่องพร้อมการตรวจสอบลายเซ็น, การเรียก endpoint user info OIDC และ [token introspection RFC 7662](https://symfony.com/blog/new-in-symfony-7-3-security-improvements) Introspection เหมาะสำหรับ token แบบ opaque หรือสถานการณ์ที่แอปไม่ได้ควบคุม authorization server `AccessTokenHandler` abstract ทั้งสามอยู่เบื้องหลัง interface เดียว **ถ: ความแตกต่างระหว่าง firewall และ access control ใน Symfony คืออะไร?** Firewall กำหนดกลไก authentication ตาม pattern URL กฎ access control กำหนดข้อกำหนด authorization หลังจาก authentication สำเร็จ Firewall อาจต้องการ JWT ที่ถูกต้อง ในขณะที่ access control ต้องการ `ROLE_ADMIN` สำหรับ `/api/admin/*` พวกมันทำงานตามลำดับ: firewall ทำ authenticate จากนั้น access control ทำ authorize **ถ: จะป้องกันการโจมตี brute force บน endpoint login ได้อย่างไร?** Login throttling ในตัวของ Symfony จำกัดความพยายามที่ล้มเหลวต่อ username และ IP กำหนดค่า `login_throttling` ใน firewall: ```yaml security: firewalls: main: login_throttling: max_attempts: 5 interval: '15 minutes' ``` สำหรับ authentication API ให้รวมกับ component RateLimiter บน endpoint token เก็บจำนวนความพยายามใน Redis สำหรับการ deploy แบบ multi-instance **ถ: อธิบาย Symfony Voters และเมื่อใดควรใช้แทนการตรวจสอบ role แบบง่าย** Voters ใช้งาน logic authorization ที่ซับซ้อนซึ่งขึ้นอยู่กับ resource ไม่ใช่แค่ role ของ user ใช้ voters เมื่อ: user ต้องเป็นเจ้าของ resource, resource มี state machine (draft vs published) หรือ authorization ขึ้นอยู่กับกฎธุรกิจ (tier subscription) การตรวจสอบ role เพียงพอสำหรับ permission แบบ static เช่น "เฉพาะ admin เท่านั้นที่สามารถเข้าถึง settings" สำหรับคำถามความปลอดภัย Symfony เพิ่มเติม ดู [คู่มือเตรียมสัมภาษณ์ Symfony](/blog/symfony/symfony-interview-questions) ## การสร้าง API Symfony ที่ปลอดภัย: ประเด็นสำคัญ - กำหนดค่า OAuth2 token introspection สำหรับ identity provider ภายนอกที่รูปแบบ token เป็น opaque - ใช้ rate limiting ที่ระดับ infrastructure (reverse proxy) และระดับแอปพลิเคชัน (component RateLimiter) เพื่อ defense in depth - ใช้ Voters สำหรับ authorization ระดับ resource สงวนการตรวจสอบ role สำหรับ permission แบบ static - จับคู่ข้อมูล request กับ DTO ที่มี property ที่ชัดเจนเพื่อป้องกัน mass assignment - เก็บ secret ใน environment variable ห้ามเก็บในไฟล์ `config/*.yaml` ที่ commit ไปยัง version control - ทดสอบกฎความปลอดภัยด้วย functional test `WebTestCase` ที่ตรวจสอบทั้งการเข้าถึงที่อนุญาตและถูกปฏิเสธ - ตรวจสอบ dependency ด้วย `symfony security:check` ใน pipeline CI --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/th/blog/symfony/symfony-rest-api-security-oauth2-rate-limiting-2026