920 lines
45 KiB
HTML
920 lines
45 KiB
HTML
<!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<..., XApiKeyAuthenticationHandler>(
|
||
<span class="string">"XApiKey"</span>, options => { });
|
||
}
|
||
|
||
<span class="comment">// ۲. ثبت JwtBearer</span>
|
||
authBuilder.AddJwtBearer(options => {
|
||
options.ForwardDefaultSelector = context => { ... };
|
||
})
|
||
<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<AuthenticationSchemeOptions, XApiKeyAuthenticationHandler>(
|
||
XAuthenticationScheme.XApiKey.GetStringValue(),
|
||
options => { });
|
||
}
|
||
|
||
<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 =>
|
||
{
|
||
<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 =>
|
||
{
|
||
options.ForwardDefaultSelector = context =>
|
||
{
|
||
<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 =>
|
||
{
|
||
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 =>
|
||
{
|
||
<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<<span class="keyword">string</span>, AuthorizationPolicy> policies = <span class="keyword">null</span>
|
||
)
|
||
{
|
||
services.AddAuthorization(options =>
|
||
{
|
||
<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 =>
|
||
{
|
||
policy.AddAuthenticationSchemes(<span class="keyword">new</span>[]
|
||
{
|
||
IdentityServerConstants.LocalApi.AuthenticationScheme,
|
||
XAuthenticationScheme.XApiKey.GetStringValue() <span class="comment">// ← اضافه شد</span>
|
||
});
|
||
policy.RequireAuthenticatedUser();
|
||
});
|
||
});
|
||
|
||
services.AddSingleton<IAuthorizationHandler, RequiredRolesHandler>();
|
||
}</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<AuthenticateResult> 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<Claim> {
|
||
<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> |