اگر API شما قدرت امضای توکنهای دسترسی را دارد، در واقع یک بمب ساعتی امنیتی در دستان شماست. طبق تحلیل فنی مفصلی که در ۱ اکتبر ۲۰۲۶ منتشر شد، سازنده GroundedDocs استدلال میکند که بسیاری از سرویسهای FastAPI بهاشتباه احراز هویت را یک «قابلیت» (Feature) میبینند، نه یک «وابستگی» (Dependency)؛ خطایی که منجر به ایجاد حفرههای امنیتی عمیق در فایلهای تنظیمات (Config files) میشود.
تصور کنید یک اداره گذرنامه و یک مأمور مرزی دارید. اداره گذرنامه هویت شما را تأیید و پاسپورتی صادر میکند که جعل آن بسیار سخت است؛ مأمور مرزی اما صرفاً مهر و تاریخ انقضا را چک میکند تا اجازه ورود دهد. اکثر توسعهدهندگان APIهای خود را طوری میسازند که هم اداره گذرنامه باشند و هم مأمور مرزی. این یعنی با یک نشت ساده در فایل تنظیمات API، مهاجم میتواند خودش را جای هر کاربری در سیستم جا بزند و دسترسی کامل به دادهها پیدا کند.

برای حل این مشکل، معماری GroundedDocs قانون «تأیید کن، صادر نکن» (Verify, don't mint) را اجرا میکند. در این مدل، API صرفاً به عنوان یک سرور منابع (Resource Server) عمل میکند، در حالی که یک صادرکننده خارجی (External Issuer) مثل Keycloak، Okta یا Cognito کارهای حساس مثل ذخیره رمزهای عبور و امضای توکنها را بر عهده میگیرد. همانطور که در بحثهای گذشته ما دربارهی امنیت مدلهای بازمتن اشاره کردیم، جداسازی لایههای دسترسی، اولین قدم در کاهش سطح حمله است. در این ساختار، API هرگز رمز عبور را نمیبیند و هرگز کلید خصوصی امضا (Private Signing Key) را در اختیار ندارد.
ریسک مسیر /login
بسیاری از برنامهنویسان برای راهکار سریع، یک مسیر /login میسازند که رمز عبور را با پایگاهداده Postgres چک کرده و یک توکن (JWT) — شبیه به یک بلیط ورود موقت که تمام مجوزهای کاربر در آن ثبت شده — صادر میکند. این کار API را به یک صادرکننده تبدیل میکند که باید دو چیز بسیار خطرناک را نگه دارد: یک کلید مخفی (Signing Secret) در فایل تنظیمات و هش رمزهای عبور در دیتابیس.
به نقل از گزارش dev.to، اگر مهاجمی تنظیمات را بدزدد، میتواند برای هر شناسه کاربری (User ID) که بخواهد، توکن معتبر بسازد. اگر دیتابیس لو برود، مهاجم به یک دامپ کامل از اعتبارنامهها دست مییابد که هدفی ایدهآل برای حملات Brute-force است. در واقع API قدرتی را در اختیار گرفته که برای پاسخ دادن به یک سؤال اصلاً به آن نیاز نداشت و فقط ریسک سیستم را بالا برده است.
سازوکار فنی
این سیستم بر اساس یک جریان تأیید مشخص در یک فایل اختصاصی به نام auth.py کار میکند که فقط قادر به تأیید است و هرگز امضا نمیکند. مکانیزمهای کلیدی این سیستم عبارتند از:
- یکپارچگی JWKS: API از
jwt.PyJWKClientبرای دریافت کلیدهای عمومی از URL مجموعه کلیدهای وب JSON (JWKS) صادرکننده استفاده میکند. این کلیدها یکبار دریافت و بهصورت محلی کش (Cache) میشوند. - تأیید محلی: سیستم امضا، صادرکننده (Issuer)، مخاطب (Audience) و تاریخ انقضا را بدون ارسال درخواست شبکه برای هر ریکوئست چک میکند. الگوریتم روی
RS256قفل شده است تا سیستم به هدرِ خودِ توکن اعتماد نکند و از حملات تغییر الگوریتم جلوگیری شود. - جداسازی هویت: هویت کاربر صرفاً از ادعای
sub(subject) در توکن تأییدشده استخراج میشود. مسیرها هرگز شناسه کاربر را از بدنه درخواست (Request Body)، رشته پرسوجو (Query String) یا آرگومانهای تولیدشده توسط مدل نمیخوانند. - نگاشت اصلی: وابستگی
get_principalمقادیرsubوjti(شناسه توکن) را استخراج میکند تا یک شیء Principal بسازد و مطمئن شود هویت پیش از درگیر شدن هرگونه مدل یا سند، بهطور کامل تثبیت شده است.
در این طراحی، اگر تنظیمات API لو برود، مهاجم فقط URLهای عمومی (صادرکننده، مخاطب و JWKS) را میبیند. اگر دیتابیس دزدیده شود، فقط ردیفهای کاربر با شناسههای Keycloak یافت میشود که صرفاً یک شناسه است، نه یک اعتبارنامه. یک توکن دزدیده شده نیز فقط برای یک کاربر محدود است و پس از حدود ۱۵ دقیقه منقضی میشود.
مدیریت موارد خاص
این معماری بدون هزینه نیست. چون تأیید بهصورت محلی انجام میشود، پدیدهای به نام «تأخیر در ابطال» (Revocation Lag) وجود دارد. اگر کاربری در Keycloak غیرفعال شود، تا زمان انقضای توکن فعلی همچنان دسترسی دارد. نویسنده پیشنهاد میکند توکنهای دسترسی را کوتاه (حدود ۱۵ دقیقه) نگه دارید تا این ریسک کاهش یابد. اگر قطع دسترسی آنی مورد نیاز باشد، توسعهدهنده باید Introspection یا یک لیست سیاه (Deny-list) را پیادهسازی کند، هرچند این کار مزیت عملکردی «عدم تماس در هر درخواست» را از بین میبرد.
سایر نقاط شکست احتمالی که باید مدیریت شوند عبارتند از:
- چرخش کلید (Key Rotation): سرویس Keycloak کلیدهای امضا را میچرخاند. اگر کش JWKS فاقد
kid(شناسه کلید) جدید باشد، کلاینت باید دوباره آن را دریافت کند. نسخههای جدید PyJWT این مورد را مدیریت میکنند، اما کشهای دستساز معمولاً در اینجا شکست میخورند. - قطعی وابستگیها: سیستم باید «بسته شکست بخورد» (Fail Closed). اگر URL مربوط به JWKS در دسترس نباشد، API باید دسترسی را رد کند. به همین ترتیب، اگر Redis (که برای محدود کردن نرخ درخواست یا Rate Limiter استفاده میشود) قطع شود، مسیرهای
/askو آپلود باید خطای ۵۰۳ برگردانند، نه اینکه ترافیک نامحدود را بپذیرند. - دسترسی اسکریپتی: در حالی که کلاینتهای واقعی از Authorization Code + PKCE استفاده میکنند، اسکریپتهای تست ممکن است به دلیل نبود مرورگر از Password Grant استفاده کنند. این مورد اکیداً فقط برای محیط تست مجاز است.
برای کسانی که عامل (Agent) — سیستمی که میتواند بهطور مستقل ابزارها را برای رسیدن به هدف فراخوانی کند — پیادهسازی میکنند، این الگو حیاتی است. در GroundedDocs، که سرویسی است که فقط با استناد به اسناد خودِ کاربر پاسخ میدهد، ابزار جستوجو هویت کاربر را از Principal تأییدشده (p) میگیرد، نه از آرگومانهایی که مدل تولید کرده است. این کار مانع از آن میشود که مدل بتواند سطح دسترسی خود را ارتقا دهد (Privilege Escalation).
چکلیست ممیزی امنیتی
برای اینکه بفهمید API شما یک صادرکننده است یا یک تأییدکننده، نویسنده چندین بررسی فوری را پیشنهاد میکند:
- جستوجوی تنظیمات (Grep Config): به دنبال
SECRET_KEYیاJWT_SECRETیا کلیدهای خصوصی بگردید. اگر یافت شدند، API شما یک صادرکننده است. - جستوجوی مدلها (Grep Models): به دنبال کتابخانههای هش رمز عبور مثل
bcryptیاpasslibیاargon2بگردید. اگر حضور دارند، API شما اعتبارنامههای غیرضروری را نگه میدارد. - تأیید منطق: مطمئن شوید پارامتر
algorithmsقفل شده است و هر دو مورد Audience و Issuer در هنگام رمزگشایی (Decoding) چک میشوند. - تست شکستها: URL مربوط به JWKS را مسدود کنید یا Redis را بهصورت محلی متوقف کنید. یک مسیر محافظتشده باید پاسخ ۴۰۱ یا ۵۰۳ بدهد و هرگز نباید پاسخ ۲۰۰ برگرداند.
این تغییر در رویکرد، مرز امنیتی را از کد اپلیکیشن به زیرساخت منتقل میکند. با سلب قدرت API در تصمیمگیری درباره اینکه کاربر کیست، انگیزه مهاجمان برای هدف قرار دادن اسرار داخلی API از بین میرود.
گام بعدی شما
- فایل تنظیمات API خود را برای وجود
SECRET_KEYیاJWT_SECRETبررسی کنید؛ اگر هستند، شما یک صادرکننده هستید و باید به سمت مدل تأییدکننده حرکت کنید. - کتابخانههای هش رمز عبور مثل
bcryptیاargon2را از کد API حذف کرده و مدیریت رمزها را به یک Identity Provider منتقل کنید. - تست «شکست بسته» را اجرا کنید: دسترسی به JWKS را قطع کنید و مطمئن شوید API پاسخ ۴۰۱ یا ۵۰۳ میدهد، نه ۲۰۰.
اما تأمین زیرساخت برای این مدل تأیید، چالشهای جدیدی در مدیریت حافظه کش ایجاد میکند — به تحلیل ما دربارهی بهینهسازی Redis در مقیاس بالا مراجعه کنید.




گفتگو