404 Not Found

404 Not Found


nginx

المصادقة و JWT — نظام هوية API آمن

JWT مثل مفتاح غرفة الفندق—Access Token هو تصريح يومي (صالح لفترة قصيرة)، و Refresh Token هو تصريح شهري (صالح لفترة طويلة ولكن يمكن إلغاؤه في أي وقت)؛ مكتب الاستقبال (خدمة المصادقة) مسؤول عن إصدار المفاتيح والتحقق منها.

1. ما ستتعلمه


2. القصة الحقيقية لـ Alice

(1) نقطة الألم: واجهات API غير المصادق عليها يتم استخراجها بشكل خبيث

واجهة PriceTracker API الخاصة بـ Alice مفتوحة بالكامل، ويمكن لأي شخص استدعاء جميع نقاط النهاية. كتب منافس نصًا برمجيًا يستخرج مليون سجل بيانات أسعار كل دقيقة. لأسوأ من ذلك، استخدم شخص ما واجهة API لإرسال بيانات أسعار مزيفة، مما أفسد قاعدة البيانات بالكامل. تحتاج Alice إلى تنفيذ تسجيل المستخدم وتسجيل الدخول، ومصادقة API، والتحكم في الوصول بناءً على مستويات اشتراك مختلفة، لكنها لا تعرف كيف تفعل ذلك بأمان.

(2) حلول مصادقة JWT

JWT (JSON Web Token) هو حل مصادقة عديم الحالة: يحصل المستخدمون على رمز عند تسجيل الدخول، ويتضمنون الرمز في الطلبات اللاحقة، ويتحقق الخادم من التوقيع—لا حاجة لتخزين الجلسات، ويدعم الأنظمة الموزعة بطبيعته.

PYTHON
from fastapi.security import OAuth2PasswordBearer

oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/auth/login")

@app.get("/me")
async def get_me(token: str = Depends(oauth2_scheme)):
    user = verify_token(token)
    return user

(3) العائد

بمجرد تمكين مصادقة API، لن يتمكن المستخدمون غير المسجلين من الوصول إلى نقاط النهاية المحمية، وسيتم حظر الاستخراج الخبيث بواسطة كل من برمجية تحديد المعدل الوسيطة والمصادقة. يمكن للمستخدمين Pro استيراد البيانات بشكل مجمّع، بينما يقتصر المستخدمون المجانيون على 1000 سجل؛ المنافسون يمكنهم فقط عرض البيانات العامة.


3. مبادئ وبنية JWT

(1) البنية الثلاثية لـ JWT

يتكون JWT من ثلاثة أجزاء: Header (الخوارزمية)، Payload (البيانات)، و Signature، مفصولة بـ ..

100%
sequenceDiagram
    participant Client as Bob الواجهة الأمامية
    participant Auth as /auth/login
    participant API as واجهة API المحمية
    participant Verify as مدقق الرمز

    Client->>Auth: POST بريد إلكتروني + كلمة مرور
    Auth->>Auth: التحقق من بيانات الاعتماد
    Auth-->>Client: Access Token + Refresh Token
    Client->>API: GET /products (Bearer Token)
    API->>Verify: فك تشفير والتحقق من التوقيع
    Verify-->>API: بيانات المستخدم
    API-->>Client: 200 OK + البيانات
    
    Note over Client,Auth: تنتهي صلاحية Access Token بعد 30 دقيقة
    Client->>Auth: POST /auth/refresh (Refresh Token)
    Auth-->>Client: Access Token جديد
القسم المحتوى مثال
Header نوع الخوارزمية {"alg": "HS256", "typ": "JWT"}
Payload بيانات المستخدم (Claims) {"sub": "alice", "role": "admin", "exp": 1700000000}
Signature التوقيع HMACSHA256(header.payload, secret)

(1) ▶ مثال: ترميز وفك تشفير JWT

PYTHON
from jose import jwt, JWTError
from datetime import datetime, timedelta

SECRET_KEY = "your-secret-key-change-in-production"
ALGORITHM = "HS256"

def create_access_token(data: dict, expires_delta: timedelta | None = None):
    to_encode = data.copy()
    expire = datetime.utcnow() + (expires_delta or timedelta(minutes=30))
    to_encode.update({"exp": expire})
    return jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)

def verify_token(token: str) -> dict:
    try:
        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
        return payload
    except JWTError:
        raise ValueError("Invalid token")

# اختبار
token = create_access_token({"sub": "alice", "role": "admin"})
print(f"Token: {token[:50]}...")
payload = verify_token(token)
print(f"Payload: {payload}")

الناتج:

TEXT
# تم تعريف الدالة بنجاح

(2) Access Token مقابل Refresh Token

البعد Access Token Refresh Token
فترة الصلاحية 30 دقيقة 7 أيام
الغرض الوصول إلى موارد API تحديث Access Token
التخزين الذاكرة (الواجهة الأمامية) HttpOnly Cookie
خطر التعرض عالي (مضمن في كل طلب) منخفض (يُستخدم فقط أثناء التحديث)
طريقة الإلغاء انتظار انتهاء الصلاحية القائمة السوداء للخادم

4. دمج OAuth2PasswordBearer

(1) ▶ مثال: تكوين المصادقة الكامل

PYTHON
from fastapi import FastAPI, Depends, HTTPException
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from jose import jwt, JWTError
from datetime import datetime, timedelta

SECRET_KEY = "your-secret-key"
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE = timedelta(minutes=30)

oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/auth/login")

app = FastAPI()

async def get_current_user(token: str = Depends(oauth2_scheme)):
    credentials_exception = HTTPException(
        status_code=401,
        detail="Could not validate credentials",
        headers={"WWW-Authenticate": "Bearer"},
    )
    try:
        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
        username: str = payload.get("sub")
        if username is None:
            raise credentials_exception
    except JWTError:
        raise credentials_exception
    # في الإنتاج: استعلام المستخدم من قاعدة البيانات
    user = {"username": username, "role": payload.get("role", "user")}
    return user

@app.post("/auth/login")
async def login(form_data: OAuth2PasswordRequestForm = Depends()):
    # في الإنتاج: التحقق من كلمة المرور من قاعدة البيانات
    if form_data.username != "alice" or form_data.password != "secret":
        raise HTTPException(status_code=401, detail="Incorrect credentials")
    
    access_token = create_access_token(
        data={"sub": form_data.username, "role": "admin"},
        expires_delta=ACCESS_TOKEN_EXPIRE,
    )
    return {"access_token": access_token, "token_type": "bearer"}

@app.get("/me")
async def read_me(current_user: dict = Depends(get_current_user)):
    return current_user

الناتج:

TEXT
# تم تعريف الدالة بنجاح

5. تجزئة كلمات المرور

(1) passlib + bcrypt

لا تُخزن كلمات المرور أبدًا كنص عادي؛ تُملح وتُجزأ باستخدام خوارزمية bcrypt.

(1) ▶ مثال: تجزئة كلمة المرور والمصادقة

PYTHON
from passlib.context import CryptContext

pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")

def hash_password(password: str) -> str:
    return pwd_context.hash(password)

def verify_password(plain_password: str, hashed_password: str) -> bool:
    return pwd_context.verify(plain_password, hashed_password)

# اختبار
hashed = hash_password("my-secret-password")
print(f"Hashed: {hashed[:30]}...")
print(f"Verify correct: {verify_password('my-secret-password', hashed)}")
print(f"Verify wrong: {verify_password('wrong-password', hashed)}")

الناتج:

TEXT
Hashed: $2b$12$K3YQ8z9wB5eF7gH1j...
Verify correct: True
Verify wrong: False

(2) أفضل ممارسات أمان كلمات المرور

الممارسة الوصف
استخدام bcrypt تجزئة مملحة تكيفية لمنع جداول القوس القزح
لا يستخدم MD5/SHA256 بدون ملح؛ الحسابات سريعة جدًا، مما يجعله عرضة لهجمات القوة الغاشمة
طول كلمة المرور ≥ 8 فرض الحد الأدنى للطول
التجزئة قبل التحقق منع هجمات التوقيت (مدمج في passlib)
SECRET_KEY طويل بما يكفي سلسلة عشوائية من 32 حرفًا على الأقل؛ خزنها في متغير بيئة

6. مصفوفة صلاحيات SaaS متعددة المستأجرين

(1) تصميم صلاحيات مستوى الاشتراك

(1) ▶ مثال: اعتمادات صلاحيات الاشتراك

PYTHON
from fastapi import Depends, HTTPException

SUBSCRIPTION_LIMITS = {
    "free": {"max_import": 1000, "websocket": False, "export": False},
    "pro": {"max_import": 100000, "websocket": True, "export": True},
    "enterprise": {"max_import": 1000000, "websocket": True, "export": True},
}

def require_subscription(min_level: str):
    LEVELS = {"free": 0, "pro": 1, "enterprise": 2}
    
    async def check_subscription(user: dict = Depends(get_current_user)):
        user_level = user.get("subscription", "free")
        if LEVELS.get(user_level, 0) < LEVELS.get(min_level, 0):
            raise HTTPException(
                status_code=403,
                detail=f"Requires {min_level} subscription. Current: {user_level}",
            )
        return user
    return check_subscription

@app.get("/api/v1/analytics")
async def get_analytics(user=Depends(require_subscription("pro"))):
    return {"total_products": 1000000, "active_users": 5000}

@app.post("/api/v1/prices/bulk")
async def bulk_import(
    prices: list[PriceCreate],
    user=Depends(require_subscription("free")),
):
    limits = SUBSCRIPTION_LIMITS[user.get("subscription", "free")]
    if len(prices) > limits["max_import"]:
        raise HTTPException(
            status_code=403,
            detail=f"Import limit: {limits['max_import']} for {user['subscription']} plan",
        )
    return {"imported": len(prices)}

الناتج:

TEXT
# تم تعريف الدالة بنجاح
نقطة النهاية مجاني Pro Enterprise
GET /products 1,000/يوم غير محدود غير محدود
POST /prices 1,000/دفعة 100,000/دفعة 1,000,000/دفعة
WebSocket /ws/prices -
GET /analytics -
تصدير CSV -
تحديد معدل API 100/دقيقة 1,000/دقيقة غير محدود

❓ أسئلة شائعة

س ما الفرق بين JWT و Session؟
ج JWT عديم الحالة (لا يُخزن على الخادم) ومناسب للأنظمة الموزعة والخدمات المصغرة؛ Session ذو حالة (يُخزن على الخادم) ومناسب للتطبيقات أحادية الخادم. يُوصى بـ JWT لخدمات API.
س ماذا يجب أن أفعل إذا انتهت صلاحية الرمز؟
ج عند انتهاء صلاحية Access Token، استخدم Refresh Token للحصول على Access Token جديد. إذا انتهت صلاحية Refresh Token، ستحتاج إلى تسجيل الدخول مرة أخرى.
س كيف يجب إدارة SECRET_KEY؟
ج في بيئة الإنتاج، استخدم متغيرات البيئة أو خدمة إدارة المفاتيح (مثل AWS KMS أو HashiCorp Vault)؛ لا تقم أبدًا بتشفيرها في الكود. يجب أن تكون 32 حرفًا على الأقل.
س أيهما أفضل، bcrypt أم Argon2؟
ج Argon2 هو الفائز في مسابقة التجزئة التشفيرية ويقدم مقاومة أقوى لهجمات GPU و ASIC. ومع ذلك، bcrypt مختبر زمانيًا وله نظام بيئي ناضج؛ كلاهما يفوقان MD5 و SHA بكثير.
س ما الفرق بين OAuth2PasswordBearer و HTTPBearer؟
ج OAuth2PasswordBearer ينفذ منح كلمة مرور OAuth2 ويعرض نموذج تسجيل دخول تلقائيًا في Swagger UI؛ HTTPBearer هو مستخرج رمز Bearer عام. نوصي بالأول.
س هل يمكن إلغاء JWT فورًا؟
ج نظرًا لأن JWT عديم الحالة بطبيعته، لا يمكن إلغاؤه فورًا. الحل: تعيين وقت انتهاء صلاحية قصير + استخدام قائمة سوداء Redis (لتخزين معرفات الرموز الملغاة حتى تنتهي صلاحيتها).

📖 ملخص


📝 تمارين

  1. تمرين أساسي (الصعوبة ⭐): نفذ نقطة نهاية /auth/login لاستقبال اسم المستخدم وكلمة المرور؛ بعد المصادقة، أعد JWT Access Token (صالح لمدة 30 دقيقة). استخدم Depends(oauth2_scheme) لتحليل المستخدم في نقطة النهاية /me. تلميح: OAuth2PasswordRequestForm + jwt.encode
  2. تمرين متقدم (الصعوبة ⭐⭐): أضف آلية رمز التحديث. Access Token صالح لمدة 15 دقيقة، و Refresh Token صالح لمدة 7 أيام. نفذ نقطة نهاية /auth/refresh لاستبدال رمز التحديث بـ Access Token جديد. تلميح: دالتان create_token + أوقات انتهاء صلاحية مختلفة
  3. تحدي (الصعوبة ⭐⭐⭐): نفذ نظام تسجيل/دخول/صلاحيات كامل—خزن كلمات المرور لنقطة نهاية التسجيل باستخدام تجزئة bcrypt؛ استخدم سلسلة DI require_subscription(min_level) للتحقق من مستويات الاشتراك؛ قيد المستخدمين المجانيين باستيراد 1000 سجل، بينما المستخدمون Pro بدون حد. تلميح: hash_password + verify_password + require_subscription DI

---|

Web-Tutorial.com

فريق Web-Tutorial التقني

منصة دروس برمجية يديرها عدة مطورين. كل درس يتم كتابته ومراجعته بواسطة مطورين متخصصين في المجال. نعمل على ضمان دقة وموثوقية المحتوى — إذا لاحظت أي مشكلة، فيرجى إخبارنا.

100%