This commit is contained in:
2026-10-10 16:24:11 +03:30
parent a1cfa566f6
commit f23c87e923
30 changed files with 2157 additions and 129265 deletions
@@ -0,0 +1,394 @@
<!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 در xIds</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.8;
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: 13px;
opacity: 0.9;
}
.doc-meta div { margin-bottom: 4px; }
.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: 40px; height: 40px;
background: linear-gradient(135deg, var(--primary), var(--primary-light));
color: white;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
font-weight: 800;
font-size: 18px;
flex-shrink: 0;
}
.section-title h3 {
font-size: 18px;
font-weight: 700;
color: var(--primary);
}
.section-content p {
margin-bottom: 15px;
text-align: justify;
}
.code-block {
background: var(--code-bg);
border: 1px solid var(--border);
border-right: 4px solid var(--accent);
border-radius: 6px;
padding: 15px;
margin: 15px 0;
font-family: 'Consolas', 'Monaco', monospace;
font-size: 13px;
overflow-x: auto;
direction: ltr;
text-align: left;
}
.highlight-box {
background: #fffaf0;
border: 1px solid #f6e05e;
border-right: 4px solid var(--accent);
padding: 15px;
border-radius: 6px;
margin: 20px 0;
}
.success-box {
background: #f0fff4;
border: 1px solid #9ae6b4;
border-right: 4px solid var(--success);
padding: 15px;
border-radius: 6px;
margin: 20px 0;
}
.warning-box {
background: #fff5f5;
border: 1px solid #feb2b2;
border-right: 4px solid var(--danger);
padding: 15px;
border-radius: 6px;
margin: 20px 0;
}
table {
width: 100%;
border-collapse: collapse;
margin: 20px 0;
}
th, td {
padding: 12px;
border: 1px solid var(--border);
text-align: right;
}
th {
background: var(--primary);
color: white;
}
tr:nth-child(even) { background: #f8fafc; }
.document-footer {
background: var(--primary);
color: white;
padding: 20px;
text-align: center;
font-size: 12px;
}
.footer-brand {
font-weight: 700;
color: var(--accent);
margin-bottom: 5px;
}
</style>
</head>
<body>
<div class="document-container">
<header class="document-header">
<div class="header-top">
<div class="doc-meta">
<div>شماره مستند: TECH-XIDS-APIKEY-001</div>
<div>نسخه: ۱.۰</div>
<div>تاریخ تهیه: ۲۰ مهر ۱۴۰۵</div>
<div>طبقه‌بندی: داخلی - فنی</div>
</div>
</div>
<div class="document-title">
<h1>راهنمای جامع پیاده‌سازی و عیب‌یابی احراز هویت XApiKey</h1>
<h2>پروژه xIds (Identity Server) | ماژول مدیریت Application و API Key</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>
</div>
<div class="section-content">
<p>
در پروژه <strong>xIds</strong>، زیرساخت کاملی برای مدیریت <code>XApplication</code> و <code>XApiKey</code> پیاده‌سازی شده است.
کلاس‌های <code>XApplicationProvider</code> و <code>XApiKeyHelper</code> وظیفه تولید، هش کردن و اعتبارسنجی کلیدها را بر عهده دارند.
با این حال، چالش اصلی در لایه <strong>Authentication Middleware</strong> و نحوه اتصال آن به Policyهای موجود بود.
</p>
<div class="highlight-box">
<strong>مشکل شناسایی شده:</strong> درخواست‌های ارسالی با هدر <code>X-Api-Key</code> توسط ASP.NET Core نادیده گرفته می‌شدند، زیرا Scheme پیش‌فرض روی JWT Bearer تنظیم شده بود و Handler مخصوص ApiKey فراخوانی نمی‌شد.
</div>
</div>
</div>
<div class="section">
<div class="section-header">
<div class="section-number">۲</div>
<div class="section-title">
<h3>راه‌حل فنی: اصلاح زنجیره احراز هویت</h3>
</div>
</div>
<div class="section-content">
<p>برای رفع مشکل، دو تغییر اساسی در فایل <code>xIds/DI/XDIHelperExtension.cs</code> اعمال شد:</p>
<h4>الف) ثبت Scheme اختصاصی برای XApiKey</h4>
<p>قبل از ثبت JwtBearer، Scheme مربوط به ApiKey ثبت می‌شود تا در لیست Authentication Schemes وجود داشته باشد.</p>
<h4>ب) استفاده از ForwardDefaultSelector هوشمند</h4>
<p>به جای استفاده ثابت از <code>ForwardDefault</code>، از یک Delegate استفاده می‌کنیم که Header درخواست را بررسی می‌کند.</p>
<div class="code-block">
// xIds/DI/XDIHelperExtension.cs
public static void AddXIdentityServerAuthentication(
this IServiceCollection services,
IConfiguration configuration,
bool addApiKeyAuthentication = false)
{
services.AddXIdentityResourceConfiguration(configuration);
var authBuilder = services.AddAuthentication();
// 1. ثبت Scheme برای XApiKey (اولویت اول)
if (addApiKeyAuthentication)
{
authBuilder.AddScheme&lt;AuthenticationSchemeOptions, XApiKeyAuthenticationHandler&gt;(
XAuthenticationScheme.XApiKey.GetStringValue(), // "XApiKey"
options => { });
}
// 2. ثبت JwtBearer با Selector هوشمند
authBuilder.AddJwtBearer(options =>
{
options.SaveToken = true;
options.RequireHttpsMetadata = false;
// انتخاب دینامیک Scheme بر اساس هدر درخواست
options.ForwardDefaultSelector = context =>
{
var apiKeyHeader = XHeader.ApiKey.GetStringValue(); // "X-Api-Key"
if (context.Request.Headers.ContainsKey(apiKeyHeader))
{
return XAuthenticationScheme.XApiKey.GetStringValue();
}
// در غیر این صورت، از احراز هویت استاندارد IdentityServer استفاده کن
return XAuthentication.IDENTITY_SERVER_LOCAL_API;
};
})
.AddLocalApi();
}
</div>
</div>
</div>
<div class="section">
<div class="section-header">
<div class="section-number">۳</div>
<div class="section-title">
<h3>اصلاح Claims تولید شده در Handler</h3>
</div>
</div>
<div class="section-content">
<p>
در فایل <code>xIds/Providers/XApiKeyAuthenticationHandler.cs</code>، متد <code>HandleAuthenticateAsync</code> باید Claimهای صحیح را تولید کند تا با Policyهای تعریف شده در <code>XAuthorizationHelper</code> همخوانی داشته باشد.
</p>
<div class="warning-box">
<strong>نکته مهم:</strong> Policyهای ApiKey بر اساس Scope (مانند read, write, admin) کار می‌کنند، نه Role. بنابراین Claim از نوع <code>JwtClaimTypes.Scope</code> ضروری است.
</div>
<div class="code-block">
protected override async Task&lt;AuthenticateResult&gt; HandleAuthenticateAsync()
{
// ... استخراج و اعتبارسنجی ApiKey ...
var validationResult = await applicationProvider.ValidateApiKey(apiKey, clientIP);
if (validationResult.Errors.HasChild())
return AuthenticateResult.Fail(string.Join(", ", validationResult.Errors));
var claims = new List&lt;Claim&gt;
{
new Claim(ClaimTypes.Name, validationResult.OwnerId),
new Claim(JwtClaimTypes.Subject, validationResult.OwnerId),
new Claim("application_id", validationResult.ApplicationId.ToString()),
new Claim("auth_type", "apikey"),
};
// افزودن Scopeهای مجاز به Claims
if (validationResult.Scopes != null)
{
foreach (var scope in validationResult.Scopes)
{
claims.Add(new Claim(JwtClaimTypes.Scope, scope));
}
}
var identity = new ClaimsIdentity(claims, Scheme.Name);
var principal = new ClaimsPrincipal(identity);
var ticket = new AuthenticationTicket(principal, Scheme.Name);
return AuthenticateResult.Success(ticket);
}
</div>
</div>
</div>
<div class="section">
<div class="section-header">
<div class="section-number">۴</div>
<div class="section-title">
<h3>نحوه استفاده در Controllerها</h3>
</div>
</div>
<div class="section-content">
<p>برای محافظت از Endpointها با ApiKey، از Attributeهای زیر استفاده کنید:</p>
<div class="code-block">
[RequireXPowered]
[Authorize(Policy = XPolicies.ApiKeyAccess)] // دسترسی کلی
public async Task&lt;ActionResult&gt; MySecureAction() { ... }
[Authorize(Policy = XPolicies.ApiKeyReadAccess)] // فقط خواندن
public async Task&lt;ActionResult&gt; GetItems() { ... }
</div>
<div class="success-box">
<strong>تست موفقیت‌آمیز:</strong> اکنون با ارسال هدر <code>X-Api-Key: xapp_...</code> به Endpointهای فوق، احراز هویت انجام شده و دسترسی اعطا می‌شود.
</div>
</div>
</div>
<div class="section">
<div class="section-header">
<div class="section-number">۵</div>
<div class="section-title">
<h3>چک‌لیست نهایی استقرار</h3>
</div>
</div>
<div class="section-content">
<table>
<thead>
<tr>
<th>ردیف</th>
<th>اقدام</th>
<th>وضعیت</th>
</tr>
</thead>
<tbody>
<tr>
<td>۱</td>
<td>بررسی ثبت <code>AddXIdentityServerAuthentication</code> در Startup با پارامتر <code>true</code></td>
<td>✅ انجام شد</td>
</tr>
<tr>
<td>۲</td>
<td>اطمینان از وجود Policyهای <code>ApiKeyAccess</code> در <code>XAuthorizationHelper</code></td>
<td>✅ موجود است</td>
</tr>
<tr>
<td>۳</td>
<td>تست ایجاد ApiKey جدید از طریق <code>/Applications/{id}/ApiKeys/Create</code></td>
<td>✅ تست شد</td>
</tr>
<tr>
<td>۴</td>
<td>تست دسترسی به <code>/Account/Test/HiApiKeyAccess</code> با کلید معتبر</td>
<td>✅ پاس شد</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<footer class="document-footer">
<div class="footer-brand">تهیه شده توسط: هادی خزاعی اصل | شرکت فن آوران ساحر علم</div>
<div>تاریخ انتشار: ۲۰ مهر ۱۴۰۵</div>
</footer>
</div>
</body>
</html>