Files
xSaherelmWorkspace/Documents/Docs/Developer/Adding xApiKey Validation 3.html
T

920 lines
45 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="fa" dir="rtl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>رفع مشکل تست XApiKey در Swagger | مستند فنی</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=Vazirmatn:wght@300;400;500;600;700;800&display=swap');
:root {
--primary: #1a365d;
--primary-light: #2c5282;
--accent: #c9a961;
--accent-dark: #a8893f;
--bg: #faf9f6;
--card-bg: #ffffff;
--text: #1a202c;
--text-muted: #4a5568;
--border: #e2e8f0;
--success: #38a169;
--danger: #e53e3e;
--warning: #dd6b20;
--shadow: 0 4px 6px rgba(0,0,0,0.05), 0 10px 30px rgba(26,54,93,0.08);
--code-bg: #f7fafc;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: 'Vazirmatn', Tahoma, sans-serif;
background: var(--bg);
color: var(--text);
line-height: 1.9;
font-size: 15px;
padding: 30px 15px;
}
.document-container {
max-width: 1000px;
margin: 0 auto;
background: var(--card-bg);
box-shadow: var(--shadow);
border-radius: 12px;
overflow: hidden;
}
.document-header {
background: linear-gradient(135deg, var(--primary) 0%, var(--primary-light) 100%);
color: white;
padding: 40px;
position: relative;
border-bottom: 4px solid var(--accent);
}
.header-top {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.doc-meta {
text-align: left;
font-size: 12px;
opacity: 0.9;
}
.doc-meta div { margin-bottom: 3px; }
.document-title h1 {
font-size: 26px;
font-weight: 800;
margin-bottom: 8px;
}
.document-title h2 {
font-size: 16px;
font-weight: 400;
opacity: 0.9;
}
.document-body { padding: 40px; }
.section {
margin-bottom: 40px;
page-break-inside: avoid;
}
.section-header {
display: flex;
align-items: center;
gap: 15px;
padding-bottom: 12px;
border-bottom: 2px solid var(--border);
margin-bottom: 20px;
}
.section-number {
width: 45px; height: 45px;
background: linear-gradient(135deg, var(--primary), var(--primary-light));
color: white;
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
font-weight: 800;
font-size: 18px;
flex-shrink: 0;
}
.section-title h3 {
font-size: 19px;
font-weight: 700;
color: var(--primary);
margin-bottom: 2px;
}
.section-title .subtitle {
font-size: 12px;
color: var(--text-muted);
}
.section-content p {
margin-bottom: 12px;
text-align: justify;
text-indent: 20px;
}
.code-block {
background: var(--code-bg);
border: 1px solid var(--border);
border-right: 4px solid var(--accent);
border-radius: 6px;
padding: 15px 20px;
margin: 15px 0;
font-family: 'Consolas', 'Courier New', monospace;
font-size: 13px;
overflow-x: auto;
direction: ltr;
text-align: left;
line-height: 1.6;
white-space: pre-wrap;
}
.code-block .comment { color: #6a737d; }
.code-block .keyword { color: #d73a49; }
.code-block .string { color: #032f62; }
.highlight-box {
background: linear-gradient(to left, #fffaf0, #fef5e7);
border: 1px solid #f6e05e;
border-right: 4px solid var(--accent);
padding: 18px 22px;
border-radius: 8px;
margin: 20px 0;
font-size: 14px;
}
.highlight-box strong { color: var(--accent-dark); }
.warning-box {
background: linear-gradient(to left, #fff5f5, #fffaf0);
border: 1px solid #feb2b2;
border-right: 4px solid var(--danger);
padding: 18px 22px;
border-radius: 8px;
margin: 20px 0;
font-size: 14px;
}
.warning-box strong { color: var(--danger); }
.success-box {
background: linear-gradient(to left, #f0fff4, #e6fffa);
border: 1px solid #9ae6b4;
border-right: 4px solid var(--success);
padding: 18px 22px;
border-radius: 8px;
margin: 20px 0;
font-size: 14px;
}
.success-box strong { color: var(--success); }
.info-box {
background: linear-gradient(to left, #ebf8ff, #e6f6ff);
border: 1px solid #90cdf4;
border-right: 4px solid var(--primary-light);
padding: 18px 22px;
border-radius: 8px;
margin: 20px 0;
font-size: 14px;
}
.flow-diagram {
background: #f8fafc;
border: 2px solid var(--border);
border-radius: 10px;
padding: 25px;
margin: 20px 0;
text-align: center;
}
.flow-step {
display: inline-block;
background: white;
border: 2px solid var(--primary-light);
border-radius: 8px;
padding: 10px 18px;
margin: 5px;
font-size: 13px;
font-weight: 600;
color: var(--primary);
}
.flow-arrow {
display: inline-block;
color: var(--accent);
font-size: 20px;
margin: 0 8px;
font-weight: bold;
}
.flow-step.error {
border-color: var(--danger);
color: var(--danger);
background: #fff5f5;
}
.flow-step.success {
border-color: var(--success);
color: var(--success);
background: #f0fff4;
}
table {
width: 100%;
border-collapse: collapse;
margin: 20px 0;
background: white;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 2px 8px rgba(0,0,0,0.05);
}
th, td {
padding: 12px 16px;
border: 1px solid var(--border);
text-align: right;
font-size: 13px;
}
th {
background: linear-gradient(135deg, var(--primary), var(--primary-light));
color: white;
font-weight: 600;
}
tr:nth-child(even) { background: #f8fafc; }
.step-card {
background: white;
border: 2px solid var(--border);
border-radius: 10px;
padding: 20px;
margin: 20px 0;
position: relative;
}
.step-card.critical {
border-color: var(--danger);
background: #fff5f5;
}
.step-card.important {
border-color: var(--accent);
background: #fffaf0;
}
.step-badge {
position: absolute;
top: -12px;
right: 20px;
background: var(--primary);
color: white;
padding: 3px 14px;
border-radius: 15px;
font-size: 12px;
font-weight: 700;
}
.step-badge.critical { background: var(--danger); }
.step-badge.important { background: var(--accent); }
.step-card h4 {
font-size: 16px;
font-weight: 700;
color: var(--primary);
margin-bottom: 12px;
margin-top: 8px;
}
.document-footer {
background: var(--primary);
color: white;
padding: 25px 40px;
text-align: center;
font-size: 12px;
}
.footer-brand {
font-weight: 700;
color: var(--accent);
margin-bottom: 5px;
font-size: 14px;
}
.footer-divider {
width: 60px;
height: 2px;
background: var(--accent);
margin: 10px auto;
}
ul.checklist {
list-style: none;
padding: 0;
margin: 15px 0;
}
ul.checklist li {
padding: 8px 0 8px 30px;
position: relative;
border-bottom: 1px solid var(--border);
}
ul.checklist li:before {
content: "✓";
position: absolute;
right: 0;
color: var(--success);
font-weight: bold;
font-size: 16px;
}
ul.checklist li.fail:before {
content: "✗";
color: var(--danger);
}
</style>
</head>
<body>
<div class="document-container">
<header class="document-header">
<div class="header-top">
<div class="doc-meta">
<div>شماره مستند: XIDS-SWAGGER-APIKEY-002</div>
<div>نسخه: ۲.۰</div>
<div>تاریخ تهیه: ۲۰ مهر ۱۴۰۵</div>
<div>طبقه‌بندی: داخلی — فنی</div>
</div>
</div>
<div class="document-title">
<h1>رفع مشکل تست XApiKey در Swagger UI</h1>
<h2>تحلیل ریشه‌ای و راه‌حل گام‌به‌گام برای فعال‌سازی کامل</h2>
</div>
</header>
<div class="document-body">
<!-- بخش ۱: خلاصه مشکل -->
<div class="section">
<div class="section-header">
<div class="section-number">۱</div>
<div class="section-title">
<h3>خلاصه مشکل شناسایی‌شده</h3>
<div class="subtitle">Problem Summary</div>
</div>
</div>
<div class="section-content">
<p>
پس از بررسی دقیق کدهای پروژه <code>xIds</code>، مشخص شد که اگرچه زیرساخت
<code>XApiKeyAuthenticationHandler</code> و Policyهای مربوطه به‌درستی پیاده‌سازی شده‌اند،
اما <strong>چهار مشکل معماری</strong> مانع از کارکرد صحیح احراز هویت با API Key در Swagger می‌شوند.
</p>
<div class="warning-box">
<strong>🔴 مشکلات شناسایی‌شده:</strong>
<ol style="padding-right: 20px; margin-top: 10px;">
<li><strong>ترتیب ثبت Schemeها:</strong> <code>XApiKey</code> اول ثبت شده و به‌عنوان DefaultScheme انتخاب می‌شود — در نتیجه درخواست‌های Bearer Token هم به آن حواله می‌شوند و شکست می‌خورند.</li>
<li><strong>عدم تنظیم DefaultScheme:</strong> در <code>AddAuthentication()</code> هیچ DefaultScheme مشخصی تعیین نشده است.</li>
<li><strong>محدودیت LocalApi Policy:</strong> Policy <code>IdentityServerConstants.LocalApi.PolicyName</code> فقط Scheme <code>IdentityServerAccessToken</code> را قبول می‌کند.</li>
<li><strong>عدم وجود PolicyScheme هوشمند:</strong> هیچ مکانیزمی برای انتخاب پویای Scheme بر اساس Header درخواست وجود ندارد.</li>
</ol>
</div>
</div>
</div>
<!-- بخش ۲: جریان فعلی -->
<div class="section">
<div class="section-header">
<div class="section-number">۲</div>
<div class="section-title">
<h3>تحلیل جریان فعلی احراز هویت</h3>
<div class="subtitle">Current Authentication Flow Analysis</div>
</div>
</div>
<div class="section-content">
<p>
در کد فعلی <code>XDIHelperExtension.AddXIdentityServerAuthentication</code>، ترتیب ثبت به این صورت است:
</p>
<div class="code-block"><span class="comment">// xIds/DI/XDIHelperExtension.cs — کد فعلی</span>
<span class="keyword">var</span> authBuilder = services.AddAuthentication(); <span class="comment">// ← DefaultScheme تنظیم نشده!</span>
<span class="comment">// ۱. ثبت XApiKey (اول)</span>
<span class="keyword">if</span> (addApiKeyAuthentication)
{
authBuilder.AddScheme&lt;..., XApiKeyAuthenticationHandler&gt;(
<span class="string">"XApiKey"</span>, options =&gt; { });
}
<span class="comment">// ۲. ثبت JwtBearer</span>
authBuilder.AddJwtBearer(options =&gt; {
options.ForwardDefaultSelector = context =&gt; { ... };
})
<span class="comment">// ۳. ثبت LocalApi</span>
.AddLocalApi();</div>
<div class="highlight-box">
<strong>💡 نکته کلیدی:</strong>
وقتی <code>AddAuthentication()</code> بدون پارامتر فراخوانی می‌شود و سپس اولین Scheme (<code>XApiKey</code>) ثبت می‌گردد،
ASP.NET Core به‌صورت خودکار <code>XApiKey</code> را به‌عنوان <strong>DefaultScheme</strong> در نظر می‌گیرد.
این یعنی تمام درخواست‌ها (حتی آن‌هایی که Bearer Token دارند) ابتدا به <code>XApiKeyAuthenticationHandler</code> می‌روند
و چون Header <code>X-Api-Key</code> وجود ندارد، <code>NoResult</code> برمی‌گردانند.
</div>
<div class="flow-diagram">
<div style="margin-bottom: 15px; font-weight: 700; color: var(--primary);">جریان فعلی (معیوب):</div>
<span class="flow-step">درخواست با Bearer Token</span>
<span class="flow-arrow">→</span>
<span class="flow-step error">DefaultScheme = XApiKey</span>
<span class="flow-arrow">→</span>
<span class="flow-step error">NoResult (بدون Header)</span>
<span class="flow-arrow">→</span>
<span class="flow-step error">401 Unauthorized ❌</span>
</div>
<div class="flow-diagram">
<div style="margin-bottom: 15px; font-weight: 700; color: var(--primary);">جریان مطلوب (پس از اصلاح):</div>
<span class="flow-step">درخواست</span>
<span class="flow-arrow">→</span>
<span class="flow-step success">PolicyScheme (هوشمند)</span>
<span class="flow-arrow">→</span>
<span class="flow-step">XApiKey یا LocalApi</span>
<span class="flow-arrow">→</span>
<span class="flow-step success">200 OK ✓</span>
</div>
</div>
</div>
<!-- بخش ۳: راه‌حل گام ۱ -->
<div class="section">
<div class="section-header">
<div class="section-number">۳</div>
<div class="section-title">
<h3>گام ۱ — اصلاح AddXIdentityServerAuthentication با PolicyScheme</h3>
<div class="subtitle">Step 1: Fix Authentication Registration with PolicyScheme</div>
</div>
</div>
<div class="section-content">
<div class="step-card critical">
<div class="step-badge critical">حیاتی — فایل: xIds/DI/XDIHelperExtension.cs</div>
<h4>توضیح:</h4>
<p>
باید از <code>AddPolicyScheme</code> استفاده کنیم تا یک Scheme هوشمند بسازیم که بر اساس
وجود Header <code>X-Api-Key</code> در درخواست، Scheme مناسب را انتخاب کند.
همچنین DefaultScheme و DefaultChallengeScheme باید به‌درستی تنظیم شوند.
</p>
</div>
<p><strong>کد اصلاح‌شده کامل:</strong></p>
<div class="code-block"><span class="comment">// xIds/DI/XDIHelperExtension.cs</span>
<span class="comment">// متد: AddXIdentityServerAuthentication</span>
<span class="keyword">public static void</span> AddXIdentityServerAuthentication(
<span class="keyword">this</span> IServiceCollection services,
IConfiguration configuration,
<span class="keyword">bool</span> addApiKeyAuthentication = <span class="keyword">false</span>
)
{
<span class="comment">// افزودنی‌های مورد نیاز</span>
services.AddXIdentityResourceConfiguration(configuration);
<span class="comment">// ═══════════════════════════════════════════════════════</span>
<span class="comment">// ثبت XApiKey Scheme (قبل از هر چیز دیگر)</span>
<span class="comment">// ═══════════════════════════════════════════════════════</span>
<span class="keyword">if</span> (addApiKeyAuthentication)
{
services.AddAuthentication()
.AddScheme&lt;AuthenticationSchemeOptions, XApiKeyAuthenticationHandler&gt;(
XAuthenticationScheme.XApiKey.GetStringValue(),
options =&gt; { });
}
<span class="comment">// ═══════════════════════════════════════════════════════</span>
<span class="comment">// تعریف PolicyScheme هوشمند برای مسیریابی درخواست‌ها</span>
<span class="comment">// ═══════════════════════════════════════════════════════</span>
<span class="keyword">var</span> smartSchemeName = <span class="string">"SmartAuthScheme"</span>;
services.AddAuthentication(options =&gt;
{
<span class="comment">// DefaultScheme = PolicyScheme هوشمند</span>
options.DefaultScheme = smartSchemeName;
options.DefaultAuthenticateScheme = smartSchemeName;
options.DefaultChallengeScheme = smartSchemeName;
})
.AddPolicyScheme(smartSchemeName, <span class="string">"Smart Authentication Scheme"</span>, options =&gt;
{
options.ForwardDefaultSelector = context =&gt;
{
<span class="comment">// بررسی وجود Header X-Api-Key</span>
<span class="keyword">var</span> apiKeyHeader = XHeader.ApiKey.GetStringValue();
<span class="keyword">if</span> (context.Request.Headers.ContainsKey(apiKeyHeader))
{
<span class="keyword">return</span> XAuthenticationScheme.XApiKey.GetStringValue();
}
<span class="comment">// در غیر این صورت، از LocalApi (IdentityServer) استفاده کن</span>
<span class="keyword">return</span> XAuthentication.IDENTITY_SERVER_LOCAL_API;
};
})
.AddJwtBearer(XAuthenticationScheme.XToken.GetStringValue(), options =&gt;
{
options.Authority = configuration
.GetSection(<span class="string">"IdentityResourceConfiguration:Authority"</span>).Value;
options.TokenValidationParameters.ValidateAudience = <span class="keyword">false</span>;
options.TokenValidationParameters.ValidTypes = <span class="keyword">new</span>[] { <span class="string">"at+jwt"</span> };
})
.AddOAuth2Introspection(XAuthenticationScheme.XIntrospection.GetStringValue(), options =&gt;
{
<span class="keyword">var</span> identityConfig = configuration.GetXIdentityResourceConfiguration();
options.Authority = identityConfig.Authority;
options.ClientId = identityConfig.ApiName;
options.ClientSecret = identityConfig.ClientSecret;
})
.AddLocalApi();
}</div>
<div class="success-box">
<strong>✅ چرا این راه‌حل کار می‌کند؟</strong>
<ul style="padding-right: 20px; margin-top: 10px;">
<li><code>PolicyScheme</code> به‌عنوان DefaultScheme عمل می‌کند و بر اساس Header تصمیم می‌گیرد.</li>
<li>اگر <code>X-Api-Key</code> وجود داشته باشد → به <code>XApiKeyAuthenticationHandler</code> حواله می‌دهد.</li>
<li>در غیر این صورت → به <code>LocalApi</code> (IdentityServer) حواله می‌دهد.</li>
<li>هیچ‌گاه Scheme اشتباه انتخاب نمی‌شود.</li>
</ul>
</div>
</div>
</div>
<!-- بخش ۴: راه‌حل گام ۲ -->
<div class="section">
<div class="section-header">
<div class="section-number">۴</div>
<div class="section-title">
<h3>گام ۲ — اصلاح LocalApi Policy برای پذیرش XApiKey</h3>
<div class="subtitle">Step 2: Fix LocalApi Policy to Accept XApiKey</div>
</div>
</div>
<div class="section-content">
<div class="step-card important">
<div class="step-badge important">مهم — فایل: xIds/DI/XDIHelperExtension.cs</div>
<h4>توضیح:</h4>
<p>
در متد <code>AddXAuthorization</code>، Policy <code>LocalApi.PolicyName</code>
فقط Scheme <code>IdentityServerAccessToken</code> را قبول می‌کند.
باید این Policy را اصلاح کنیم تا <code>XApiKey</code> را هم بپذیرد.
</p>
</div>
<div class="code-block"><span class="comment">// xIds/DI/XDIHelperExtension.cs</span>
<span class="comment">// متد: AddXAuthorization</span>
<span class="keyword">public static void</span> AddXAuthorization(
<span class="keyword">this</span> IServiceCollection services,
IDictionary&lt;<span class="keyword">string</span>, AuthorizationPolicy&gt; policies = <span class="keyword">null</span>
)
{
services.AddAuthorization(options =&gt;
{
<span class="comment">// ... کدهای قبلی برای policies و xPolicies ...</span>
<span class="comment">// ═══════════════════════════════════════════════════════</span>
<span class="comment">// اصلاح LocalApi Policy برای پذیرش هر دو Scheme</span>
<span class="comment">// ═══════════════════════════════════════════════════════</span>
options.AddPolicy(IdentityServerConstants.LocalApi.PolicyName, policy =&gt;
{
policy.AddAuthenticationSchemes(<span class="keyword">new</span>[]
{
IdentityServerConstants.LocalApi.AuthenticationScheme,
XAuthenticationScheme.XApiKey.GetStringValue() <span class="comment">// ← اضافه شد</span>
});
policy.RequireAuthenticatedUser();
});
});
services.AddSingleton&lt;IAuthorizationHandler, RequiredRolesHandler&gt;();
}</div>
<div class="info-box">
<strong>💡 نکته:</strong>
با این اصلاح، Endpointهایی که <code>[Authorize(LocalApi.PolicyName)]</code> دارند،
هم با Bearer Token و هم با X-Api-Key قابل دسترسی خواهند بود.
</div>
</div>
</div>
<!-- بخش ۵: راه‌حل گام ۳ -->
<div class="section">
<div class="section-header">
<div class="section-number">۵</div>
<div class="section-title">
<h3>گام ۳ — بررسی و اصلاح Swagger Security Definition</h3>
<div class="subtitle">Step 3: Verify Swagger Security Configuration</div>
</div>
</div>
<div class="section-content">
<div class="step-card">
<div class="step-badge">بررسی — فایل: xCommons/Extensions/DIExtensions.cs</div>
<h4>توضیح:</h4>
<p>
در کد فعلی <code>AddXSwaggerGenOptions</code>، Security Definition برای XApiKey
به‌درستی تعریف شده است. اما باید مطمئن شویم که <code>addXApiKeyAuthorization</code>
در فراخوانی <code>AddXSwagger</code> مقدار <code>true</code> دارد.
</p>
</div>
<p><strong>بررسی Startup.cs:</strong></p>
<div class="code-block"><span class="comment">// xIds/Startup.cs — ConfigureServices</span>
<span class="comment">// ثبت Swagger — مطمئن شوید پارامترها درست هستند</span>
<span class="keyword">var</span> xmlFile = $<span class="string">"{Assembly.GetExecutingAssembly().GetName().Name}.xml"</span>;
<span class="keyword">var</span> xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
services.AddXSwagger(
Configuration,
xmlFilePath: xmlPath,
addRequiredXPoweredFilter: <span class="keyword">true</span>,
addXTokenAuthorization: <span class="keyword">true</span>,
addXApiKeyAuthorization: <span class="keyword">true</span>, <span class="comment">// ← باید true باشد</span>
addDefaultValueFilter: <span class="keyword">true</span>
);</div>
<p><strong>خروجی مورد انتظار در Swagger UI:</strong></p>
<div class="highlight-box">
<strong>🔑 در Swagger UI باید دو دکمه "Authorize" ببینید:</strong>
<ul style="padding-right: 20px; margin-top: 10px;">
<li><strong>accessToken:</strong> برای Bearer Token (مقدار: <code>Bearer {token}</code>)</li>
<li><strong>X-Api-Key:</strong> برای API Key (مقدار: <code>xapp_xxxxxxxxxxxx</code>)</li>
</ul>
</div>
</div>
</div>
<!-- بخش ۶: راه‌حل گام ۴ -->
<div class="section">
<div class="section-header">
<div class="section-number">۶</div>
<div class="section-title">
<h3>گام ۴ — اصلاح XApiKeyAuthenticationHandler</h3>
<div class="subtitle">Step 4: Verify XApiKeyAuthenticationHandler</div>
</div>
</div>
<div class="section-content">
<div class="step-card">
<div class="step-badge">بررسی — فایل: xIds/Providers/XApiKeyAuthenticationHandler.cs</div>
<h4>توضیح:</h4>
<p>
Handler فعلی به‌درستی پیاده‌سازی شده است. فقط یک نکته مهم:
مطمئن شوید که <code>ValidateApiKey</code> در <code>XApplicationProvider</code>
مقدار <code>requiredScope</code> را به‌درستی بررسی می‌کند.
</p>
</div>
<p><strong>کد فعلی Handler (صحیح است):</strong></p>
<div class="code-block"><span class="comment">// xIds/Providers/XApiKeyAuthenticationHandler.cs</span>
<span class="keyword">protected override async</span> Task&lt;AuthenticateResult&gt; HandleAuthenticateAsync()
{
<span class="comment">// ۱. استخراج API Key از Header</span>
<span class="keyword">var</span> apiKeyHeader = HeaderName; <span class="comment">// "X-Api-Key"</span>
<span class="keyword">if</span> (!Request.Headers.ContainsKey(apiKeyHeader))
<span class="keyword">return</span> AuthenticateResult.NoResult();
<span class="keyword">var</span> apiKey = Request.Headers[apiKeyHeader].ToString();
<span class="keyword">if</span> (apiKey.IsNullOrEmpty())
<span class="keyword">return</span> AuthenticateResult.NoResult();
<span class="comment">// ۲. استخراج Client IP</span>
<span class="keyword">var</span> clientIP = Request.HttpContext.Connection.RemoteIpAddress?.ToString();
<span class="comment">// ۳. اعتبارسنجی API Key</span>
<span class="keyword">var</span> validationResult = <span class="keyword">await</span> applicationProvider.ValidateApiKey(
apiKey: apiKey,
clientIP: clientIP
);
<span class="keyword">if</span> (validationResult.Errors.HasChild())
{
<span class="keyword">var</span> message = validationResult.Errors.ToListString(<span class="string">'\n'</span>);
<span class="keyword">return</span> AuthenticateResult.Fail(message);
}
<span class="comment">// ۴. ساخت Claims</span>
<span class="keyword">var</span> claims = <span class="keyword">new</span> List&lt;Claim&gt; {
<span class="keyword">new</span>(ClaimTypes.Name, validationResult.OwnerId),
<span class="keyword">new</span>(JwtClaimTypes.Subject, validationResult.OwnerId),
<span class="keyword">new</span>(XCustomClaims.ApplicationId, validationResult.ApplicationId.ToString()),
<span class="keyword">new</span>(XCustomClaims.AuthType, <span class="string">"apikey"</span>),
};
<span class="comment">// ۵. اضافه کردن Scopeها</span>
<span class="keyword">if</span> (validationResult.Scopes != <span class="keyword">null</span>)
{
<span class="keyword">foreach</span> (<span class="keyword">var</span> scope <span class="keyword">in</span> validationResult.Scopes)
{
claims.Add(<span class="keyword">new</span> Claim(JwtClaimTypes.Scope, scope));
}
}
<span class="comment">// ۶. ساخت Principal و Ticket</span>
<span class="keyword">var</span> identity = <span class="keyword">new</span> ClaimsIdentity(claims, Scheme.Name);
<span class="keyword">var</span> principal = <span class="keyword">new</span> ClaimsPrincipal(identity);
<span class="keyword">var</span> ticket = <span class="keyword">new</span> AuthenticationTicket(principal, Scheme.Name);
<span class="keyword">return</span> AuthenticateResult.Success(ticket);
}</div>
<div class="success-box">
<strong>✅ وضعیت Handler:</strong>
کد فعلی Handler صحیح است و نیازی به تغییر ندارد.
فقط مطمئن شوید که <code>validationResult.Scopes</code> مقادیر صحیح
(<code>read</code>, <code>write</code>, <code>admin</code>, <code>manage</code>)
را برمی‌گرداند.
</div>
</div>
</div>
<!-- بخش ۷: تست در Swagger -->
<div class="section">
<div class="section-header">
<div class="section-number">۷</div>
<div class="section-title">
<h3>گام ۵ — راهنمای تست در Swagger UI</h3>
<div class="subtitle">Step 5: Testing Guide in Swagger UI</div>
</div>
</div>
<div class="section-content">
<p><strong>مراحل تست گام‌به‌گام:</strong></p>
<div class="step-card">
<div class="step-badge">تست ۱</div>
<h4>ساخت Application و API Key</h4>
<p>ابتدا یک Application بسازید و سپس یک API Key برای آن ایجاد کنید:</p>
<div class="code-block"><span class="comment"># ۱. ساخت Application (با Bearer Token احراز هویت کنید)</span>
POST /Applications
Content-Type: application/json
Authorization: Bearer {access_token}
X-PoweredBy: {xPoweredValue}
{
"name": "TestApp",
"description": "Test Application",
"type": 0
}
<span class="comment"># ۲. ساخت API Key (با Bearer Token)</span>
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"]
}
<span class="comment"># پاسخ شامل plainKey است — آن را ذخیره کنید!</span>
<span class="comment"># مثال: xapp_abc123def456...</span></div>
</div>
<div class="step-card">
<div class="step-badge">تست ۲</div>
<h4>استفاده از API Key در Swagger UI</h4>
<p>در Swagger UI:</p>
<ol style="padding-right: 20px; margin-top: 10px;">
<li>روی دکمه <strong>"Authorize"</strong> (بالا سمت راست) کلیک کنید.</li>
<li>در بخش <strong>"X-Api-Key"</strong>، کلید API دریافتی را وارد کنید.</li>
<li>روی <strong>"Authorize"</strong> و سپس <strong>"Close"</strong> کلیک کنید.</li>
<li>Endpoint <code>GET /Account/Test/HiApiKeyAccess</code> را پیدا کنید.</li>
<li>روی <strong>"Try it out"</strong> و سپس <strong>"Execute"</strong> کلیک کنید.</li>
</ol>
</div>
<div class="step-card">
<div class="step-badge">تست ۳</div>
<h4>نتایج مورد انتظار</h4>
<table>
<thead>
<tr>
<th>سناریو</th>
<th>پاسخ مورد انتظار</th>
<th>وضعیت</th>
</tr>
</thead>
<tbody>
<tr>
<td>API Key معتبر + Scope کافی</td>
<td><code>200 OK</code> — "Hi ApiKey Access, is Passed ..."</td>
<td style="color: var(--success); font-weight: 600;">✓ صحیح</td>
</tr>
<tr>
<td>API Key نامعتبر</td>
<td><code>401 Unauthorized</code> — "ApiKey not found"</td>
<td style="color: var(--success); font-weight: 600;">✓ صحیح</td>
</tr>
<tr>
<td>API Key منقضی‌شده</td>
<td><code>401</code> — "ApiKey is Expired"</td>
<td style="color: var(--success); font-weight: 600;">✓ صحیح</td>
</tr>
<tr>
<td>Scope ناکافی</td>
<td><code>403 Forbidden</code></td>
<td style="color: var(--success); font-weight: 600;">✓ صحیح</td>
</tr>
<tr>
<td>IP غیرمجاز</td>
<td><code>401</code> — "Client IP not Allowed"</td>
<td style="color: var(--success); font-weight: 600;">✓ صحیح</td>
</tr>
<tr>
<td>Rate Limit رد شده</td>
<td><code>401</code> — "ApiKey Rate Limit Reached"</td>
<td style="color: var(--success); font-weight: 600;">✓ صحیح</td>
</tr>
</tbody>
</table>
</div>
<div class="step-card">
<div class="step-badge">تست ۴</div>
<h4>تست با cURL (برای اطمینان بیشتر)</h4>
<div class="code-block"><span class="comment"># تست مستقیم با cURL</span>
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
<span class="comment"># پاسخ مورد انتظار:</span>
<span class="comment"># "Hi ApiKey Access, is Passed ..."</span></div>
</div>
</div>
</div>
<!-- بخش ۸: چک‌لیست نهایی -->
<div class="section">
<div class="section-header">
<div class="section-number">۸</div>
<div class="section-title">
<h3>چک‌لیست نهایی اصلاحات</h3>
<div class="subtitle">Final Checklist</div>
</div>
</div>
<div class="section-content">
<table>
<thead>
<tr>
<th>ردیف</th>
<th>فایل</th>
<th>تغییر</th>
<th>اولویت</th>
</tr>
</thead>
<tbody>
<tr>
<td>۱</td>
<td><code>xIds/DI/XDIHelperExtension.cs</code></td>
<td>بازنویسی <code>AddXIdentityServerAuthentication</code> با <code>PolicyScheme</code></td>
<td style="color: var(--danger); font-weight: 700;">🔴 حیاتی</td>
</tr>
<tr>
<td>۲</td>
<td><code>xIds/DI/XDIHelperExtension.cs</code></td>
<td>اصلاح <code>AddXAuthorization</code> — افزودن XApiKey به LocalApi Policy</td>
<td style="color: var(--danger); font-weight: 700;">🔴 حیاتی</td>
</tr>
<tr>
<td>۳</td>
<td><code>xIds/Startup.cs</code></td>
<td>بررسی <code>addXApiKeyAuthorization: true</code> در <code>AddXSwagger</code></td>
<td style="color: var(--warning); font-weight: 700;">🟡 بررسی</td>
</tr>
<tr>
<td>۴</td>
<td><code>xIds/Providers/XApiKeyAuthenticationHandler.cs</code></td>
<td>تأیید صحت کد (نیاز به تغییر ندارد)</td>
<td style="color: var(--success); font-weight: 700;">🟢 تأیید</td>
</tr>
<tr>
<td>۵</td>
<td><code>xIdentityHelper/XAuthorizationHelper.cs</code></td>
<td>تأیید Policyهای ApiKey (نیاز به تغییر ندارد)</td>
<td style="color: var(--success); font-weight: 700;">🟢 تأیید</td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- بخش ۹: جمع‌بندی -->
<div class="section">
<div class="section-header">
<div class="section-number">۹</div>
<div class="section-title">
<h3>جمع‌بندی و نکات پایانی</h3>
<div class="subtitle">Conclusion</div>
</div>
</div>
<div class="section-content">
<p>
ریشه اصلی مشکل تست XApiKey در Swagger، <strong>نحوه ثبت Authentication Schemes</strong>
و <strong>عدم استفاده از PolicyScheme هوشمند</strong> بود. با اعمال اصلاحات گام ۱ و ۲،
سیستم احراز هویت به‌صورت خودکار بر اساس Header درخواست، Scheme مناسب را انتخاب می‌کند
و Swagger UI می‌تواند به‌درستی با API Key احراز هویت نماید.
</p>
<div class="success-box">
<strong>🎯 نتیجه نهایی:</strong>
<ul style="padding-right: 20px; margin-top: 10px;">
<li>درخواست‌های دارای Header <code>X-Api-Key</code> → به <code>XApiKeyAuthenticationHandler</code> هدایت می‌شوند.</li>
<li>درخواست‌های دارای Header <code>Authorization: Bearer</code> → به <code>LocalApi</code> (IdentityServer) هدایت می‌شوند.</li>
<li>Swagger UI هر دو روش احراز هویت را پشتیبانی می‌کند.</li>
<li>Policyهای <code>ApiKeyAccess</code>, <code>ApiKeyReadAccess</code> و غیره به‌درستی کار می‌کنند.</li>
</ul>
</div>
<div class="highlight-box">
<strong>⚠️ نکته مهم:</strong>
پس از اعمال تغییرات، حتماً پروژه را <strong>Clean</strong> و <strong>Rebuild</strong> کنید
و سپس Swagger UI را با <strong>Ctrl+F5</strong> رفرش نمایید تا تغییرات Security Definition
به‌درستی اعمال شوند.
</div>
</div>
</div>
</div>
<footer class="document-footer">
<div class="footer-brand">تهیه شده توسط: هادی خزاعی اصل | شرکت فن آوران ساحر علم</div>
<div class="footer-divider"></div>
<div>تاریخ انتشار: ۲۰ مهر ۱۴۰۵ | نسخه: ۲.۰ | طبقه‌بندی: داخلی — فنی</div>
</footer>
</div>
</body>
</html>