شماره مستند: XIDS-SWAGGER-APIKEY-002
نسخه: ۲.۰
تاریخ تهیه: ۲۰ مهر ۱۴۰۵
طبقه‌بندی: داخلی — فنی

رفع مشکل تست XApiKey در Swagger UI

تحلیل ریشه‌ای و راه‌حل گام‌به‌گام برای فعال‌سازی کامل

۱

خلاصه مشکل شناسایی‌شده

Problem Summary

پس از بررسی دقیق کدهای پروژه xIds، مشخص شد که اگرچه زیرساخت XApiKeyAuthenticationHandler و Policyهای مربوطه به‌درستی پیاده‌سازی شده‌اند، اما چهار مشکل معماری مانع از کارکرد صحیح احراز هویت با API Key در Swagger می‌شوند.

🔴 مشکلات شناسایی‌شده:
  1. ترتیب ثبت Schemeها: XApiKey اول ثبت شده و به‌عنوان DefaultScheme انتخاب می‌شود — در نتیجه درخواست‌های Bearer Token هم به آن حواله می‌شوند و شکست می‌خورند.
  2. عدم تنظیم DefaultScheme: در AddAuthentication() هیچ DefaultScheme مشخصی تعیین نشده است.
  3. محدودیت LocalApi Policy: Policy IdentityServerConstants.LocalApi.PolicyName فقط Scheme IdentityServerAccessToken را قبول می‌کند.
  4. عدم وجود PolicyScheme هوشمند: هیچ مکانیزمی برای انتخاب پویای Scheme بر اساس Header درخواست وجود ندارد.
۲

تحلیل جریان فعلی احراز هویت

Current Authentication Flow Analysis

در کد فعلی XDIHelperExtension.AddXIdentityServerAuthentication، ترتیب ثبت به این صورت است:

// xIds/DI/XDIHelperExtension.cs — کد فعلی var authBuilder = services.AddAuthentication(); // ← DefaultScheme تنظیم نشده! // ۱. ثبت XApiKey (اول) if (addApiKeyAuthentication) { authBuilder.AddScheme<..., XApiKeyAuthenticationHandler>( "XApiKey", options => { }); } // ۲. ثبت JwtBearer authBuilder.AddJwtBearer(options => { options.ForwardDefaultSelector = context => { ... }; }) // ۳. ثبت LocalApi .AddLocalApi();
💡 نکته کلیدی: وقتی AddAuthentication() بدون پارامتر فراخوانی می‌شود و سپس اولین Scheme (XApiKey) ثبت می‌گردد، ASP.NET Core به‌صورت خودکار XApiKey را به‌عنوان DefaultScheme در نظر می‌گیرد. این یعنی تمام درخواست‌ها (حتی آن‌هایی که Bearer Token دارند) ابتدا به XApiKeyAuthenticationHandler می‌روند و چون Header X-Api-Key وجود ندارد، NoResult برمی‌گردانند.
جریان فعلی (معیوب):
درخواست با Bearer Token → DefaultScheme = XApiKey → NoResult (بدون Header) → 401 Unauthorized ❌
جریان مطلوب (پس از اصلاح):
درخواست → PolicyScheme (هوشمند) → XApiKey یا LocalApi → 200 OK ✓
۳

گام ۱ — اصلاح AddXIdentityServerAuthentication با PolicyScheme

Step 1: Fix Authentication Registration with PolicyScheme
حیاتی — فایل: xIds/DI/XDIHelperExtension.cs

توضیح:

باید از AddPolicyScheme استفاده کنیم تا یک Scheme هوشمند بسازیم که بر اساس وجود Header X-Api-Key در درخواست، Scheme مناسب را انتخاب کند. همچنین DefaultScheme و DefaultChallengeScheme باید به‌درستی تنظیم شوند.

کد اصلاح‌شده کامل:

// xIds/DI/XDIHelperExtension.cs // متد: AddXIdentityServerAuthentication public static void AddXIdentityServerAuthentication( this IServiceCollection services, IConfiguration configuration, bool addApiKeyAuthentication = false ) { // افزودنی‌های مورد نیاز services.AddXIdentityResourceConfiguration(configuration); // ═══════════════════════════════════════════════════════ // ثبت XApiKey Scheme (قبل از هر چیز دیگر) // ═══════════════════════════════════════════════════════ if (addApiKeyAuthentication) { services.AddAuthentication() .AddScheme<AuthenticationSchemeOptions, XApiKeyAuthenticationHandler>( XAuthenticationScheme.XApiKey.GetStringValue(), options => { }); } // ═══════════════════════════════════════════════════════ // تعریف PolicyScheme هوشمند برای مسیریابی درخواست‌ها // ═══════════════════════════════════════════════════════ var smartSchemeName = "SmartAuthScheme"; services.AddAuthentication(options => { // DefaultScheme = PolicyScheme هوشمند options.DefaultScheme = smartSchemeName; options.DefaultAuthenticateScheme = smartSchemeName; options.DefaultChallengeScheme = smartSchemeName; }) .AddPolicyScheme(smartSchemeName, "Smart Authentication Scheme", options => { options.ForwardDefaultSelector = context => { // بررسی وجود Header X-Api-Key var apiKeyHeader = XHeader.ApiKey.GetStringValue(); if (context.Request.Headers.ContainsKey(apiKeyHeader)) { return XAuthenticationScheme.XApiKey.GetStringValue(); } // در غیر این صورت، از LocalApi (IdentityServer) استفاده کن return XAuthentication.IDENTITY_SERVER_LOCAL_API; }; }) .AddJwtBearer(XAuthenticationScheme.XToken.GetStringValue(), options => { options.Authority = configuration .GetSection("IdentityResourceConfiguration:Authority").Value; options.TokenValidationParameters.ValidateAudience = false; options.TokenValidationParameters.ValidTypes = new[] { "at+jwt" }; }) .AddOAuth2Introspection(XAuthenticationScheme.XIntrospection.GetStringValue(), options => { var identityConfig = configuration.GetXIdentityResourceConfiguration(); options.Authority = identityConfig.Authority; options.ClientId = identityConfig.ApiName; options.ClientSecret = identityConfig.ClientSecret; }) .AddLocalApi(); }
✅ چرا این راه‌حل کار می‌کند؟
  • PolicyScheme به‌عنوان DefaultScheme عمل می‌کند و بر اساس Header تصمیم می‌گیرد.
  • اگر X-Api-Key وجود داشته باشد → به XApiKeyAuthenticationHandler حواله می‌دهد.
  • در غیر این صورت → به LocalApi (IdentityServer) حواله می‌دهد.
  • هیچ‌گاه Scheme اشتباه انتخاب نمی‌شود.
۴

گام ۲ — اصلاح LocalApi Policy برای پذیرش XApiKey

Step 2: Fix LocalApi Policy to Accept XApiKey
مهم — فایل: xIds/DI/XDIHelperExtension.cs

توضیح:

در متد AddXAuthorization، Policy LocalApi.PolicyName فقط Scheme IdentityServerAccessToken را قبول می‌کند. باید این Policy را اصلاح کنیم تا XApiKey را هم بپذیرد.

// xIds/DI/XDIHelperExtension.cs // متد: AddXAuthorization public static void AddXAuthorization( this IServiceCollection services, IDictionary<string, AuthorizationPolicy> policies = null ) { services.AddAuthorization(options => { // ... کدهای قبلی برای policies و xPolicies ... // ═══════════════════════════════════════════════════════ // اصلاح LocalApi Policy برای پذیرش هر دو Scheme // ═══════════════════════════════════════════════════════ options.AddPolicy(IdentityServerConstants.LocalApi.PolicyName, policy => { policy.AddAuthenticationSchemes(new[] { IdentityServerConstants.LocalApi.AuthenticationScheme, XAuthenticationScheme.XApiKey.GetStringValue() // ← اضافه شد }); policy.RequireAuthenticatedUser(); }); }); services.AddSingleton<IAuthorizationHandler, RequiredRolesHandler>(); }
💡 نکته: با این اصلاح، Endpointهایی که [Authorize(LocalApi.PolicyName)] دارند، هم با Bearer Token و هم با X-Api-Key قابل دسترسی خواهند بود.
۵

گام ۳ — بررسی و اصلاح Swagger Security Definition

Step 3: Verify Swagger Security Configuration
بررسی — فایل: xCommons/Extensions/DIExtensions.cs

توضیح:

در کد فعلی AddXSwaggerGenOptions، Security Definition برای XApiKey به‌درستی تعریف شده است. اما باید مطمئن شویم که addXApiKeyAuthorization در فراخوانی AddXSwagger مقدار true دارد.

بررسی Startup.cs:

// xIds/Startup.cs — ConfigureServices // ثبت Swagger — مطمئن شوید پارامترها درست هستند var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml"; var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile); services.AddXSwagger( Configuration, xmlFilePath: xmlPath, addRequiredXPoweredFilter: true, addXTokenAuthorization: true, addXApiKeyAuthorization: true, // ← باید true باشد addDefaultValueFilter: true );

خروجی مورد انتظار در Swagger UI:

🔑 در Swagger UI باید دو دکمه "Authorize" ببینید:
  • accessToken: برای Bearer Token (مقدار: Bearer {token})
  • X-Api-Key: برای API Key (مقدار: xapp_xxxxxxxxxxxx)
۶

گام ۴ — اصلاح XApiKeyAuthenticationHandler

Step 4: Verify XApiKeyAuthenticationHandler
بررسی — فایل: xIds/Providers/XApiKeyAuthenticationHandler.cs

توضیح:

Handler فعلی به‌درستی پیاده‌سازی شده است. فقط یک نکته مهم: مطمئن شوید که ValidateApiKey در XApplicationProvider مقدار requiredScope را به‌درستی بررسی می‌کند.

کد فعلی Handler (صحیح است):

// xIds/Providers/XApiKeyAuthenticationHandler.cs protected override async Task<AuthenticateResult> HandleAuthenticateAsync() { // ۱. استخراج API Key از Header var apiKeyHeader = HeaderName; // "X-Api-Key" if (!Request.Headers.ContainsKey(apiKeyHeader)) return AuthenticateResult.NoResult(); var apiKey = Request.Headers[apiKeyHeader].ToString(); if (apiKey.IsNullOrEmpty()) return AuthenticateResult.NoResult(); // ۲. استخراج Client IP var clientIP = Request.HttpContext.Connection.RemoteIpAddress?.ToString(); // ۳. اعتبارسنجی API Key var validationResult = await applicationProvider.ValidateApiKey( apiKey: apiKey, clientIP: clientIP ); if (validationResult.Errors.HasChild()) { var message = validationResult.Errors.ToListString('\n'); return AuthenticateResult.Fail(message); } // ۴. ساخت Claims var claims = new List<Claim> { new(ClaimTypes.Name, validationResult.OwnerId), new(JwtClaimTypes.Subject, validationResult.OwnerId), new(XCustomClaims.ApplicationId, validationResult.ApplicationId.ToString()), new(XCustomClaims.AuthType, "apikey"), }; // ۵. اضافه کردن Scopeها if (validationResult.Scopes != null) { foreach (var scope in validationResult.Scopes) { claims.Add(new Claim(JwtClaimTypes.Scope, scope)); } } // ۶. ساخت Principal و Ticket var identity = new ClaimsIdentity(claims, Scheme.Name); var principal = new ClaimsPrincipal(identity); var ticket = new AuthenticationTicket(principal, Scheme.Name); return AuthenticateResult.Success(ticket); }
✅ وضعیت Handler: کد فعلی Handler صحیح است و نیازی به تغییر ندارد. فقط مطمئن شوید که validationResult.Scopes مقادیر صحیح (read, write, admin, manage) را برمی‌گرداند.
۷

گام ۵ — راهنمای تست در Swagger UI

Step 5: Testing Guide in Swagger UI

مراحل تست گام‌به‌گام:

تست ۱

ساخت Application و API Key

ابتدا یک Application بسازید و سپس یک API Key برای آن ایجاد کنید:

# ۱. ساخت Application (با Bearer Token احراز هویت کنید) POST /Applications Content-Type: application/json Authorization: Bearer {access_token} X-PoweredBy: {xPoweredValue} { "name": "TestApp", "description": "Test Application", "type": 0 } # ۲. ساخت API Key (با Bearer Token) POST /Applications/{applicationId}/ApiKeys/Create Content-Type: application/json Authorization: Bearer {access_token} X-PoweredBy: {xPoweredValue} { "scopes": ["read", "write", "manage"], "rateLimit": 60, "expirationMinutes": 43200, "allowedIPs": ["127.0.0.1", "::1"] } # پاسخ شامل plainKey است — آن را ذخیره کنید! # مثال: xapp_abc123def456...
تست ۲

استفاده از API Key در Swagger UI

در Swagger UI:

  1. روی دکمه "Authorize" (بالا سمت راست) کلیک کنید.
  2. در بخش "X-Api-Key"، کلید API دریافتی را وارد کنید.
  3. روی "Authorize" و سپس "Close" کلیک کنید.
  4. Endpoint GET /Account/Test/HiApiKeyAccess را پیدا کنید.
  5. روی "Try it out" و سپس "Execute" کلیک کنید.
تست ۳

نتایج مورد انتظار

سناریو پاسخ مورد انتظار وضعیت
API Key معتبر + Scope کافی 200 OK — "Hi ApiKey Access, is Passed ..." ✓ صحیح
API Key نامعتبر 401 Unauthorized — "ApiKey not found" ✓ صحیح
API Key منقضی‌شده 401 — "ApiKey is Expired" ✓ صحیح
Scope ناکافی 403 Forbidden ✓ صحیح
IP غیرمجاز 401 — "Client IP not Allowed" ✓ صحیح
Rate Limit رد شده 401 — "ApiKey Rate Limit Reached" ✓ صحیح
تست ۴

تست با cURL (برای اطمینان بیشتر)

# تست مستقیم با cURL curl -X GET "https://localhost:5001/Account/Test/HiApiKeyAccess" \ -H "X-Api-Key: xapp_your_api_key_here" \ -H "X-PoweredBy: your_powered_value" \ -k # پاسخ مورد انتظار: # "Hi ApiKey Access, is Passed ..."
۸

چک‌لیست نهایی اصلاحات

Final Checklist
ردیف فایل تغییر اولویت
۱ xIds/DI/XDIHelperExtension.cs بازنویسی AddXIdentityServerAuthentication با PolicyScheme 🔴 حیاتی
۲ xIds/DI/XDIHelperExtension.cs اصلاح AddXAuthorization — افزودن XApiKey به LocalApi Policy 🔴 حیاتی
۳ xIds/Startup.cs بررسی addXApiKeyAuthorization: true در AddXSwagger 🟡 بررسی
۴ xIds/Providers/XApiKeyAuthenticationHandler.cs تأیید صحت کد (نیاز به تغییر ندارد) 🟢 تأیید
۵ xIdentityHelper/XAuthorizationHelper.cs تأیید Policyهای ApiKey (نیاز به تغییر ندارد) 🟢 تأیید
۹

جمع‌بندی و نکات پایانی

Conclusion

ریشه اصلی مشکل تست XApiKey در Swagger، نحوه ثبت Authentication Schemes و عدم استفاده از PolicyScheme هوشمند بود. با اعمال اصلاحات گام ۱ و ۲، سیستم احراز هویت به‌صورت خودکار بر اساس Header درخواست، Scheme مناسب را انتخاب می‌کند و Swagger UI می‌تواند به‌درستی با API Key احراز هویت نماید.

🎯 نتیجه نهایی:
  • درخواست‌های دارای Header X-Api-Key → به XApiKeyAuthenticationHandler هدایت می‌شوند.
  • درخواست‌های دارای Header Authorization: Bearer → به LocalApi (IdentityServer) هدایت می‌شوند.
  • Swagger UI هر دو روش احراز هویت را پشتیبانی می‌کند.
  • Policyهای ApiKeyAccess, ApiKeyReadAccess و غیره به‌درستی کار می‌کنند.
⚠️ نکته مهم: پس از اعمال تغییرات، حتماً پروژه را Clean و Rebuild کنید و سپس Swagger UI را با Ctrl+F5 رفرش نمایید تا تغییرات Security Definition به‌درستی اعمال شوند.