الأمان والأداء

أمان الجلسات و JWT

20 دقيقة الدرس 6 من 35

أمان الجلسات و JWT

إدارة الجلسات والرموز هي مكونات حاسمة لأمان تطبيقات الويب. فهم كيفية تأمين الجلسات بشكل صحيح وتنفيذ JWT (رموز الويب JSON) يحمي من هجمات المصادقة الشائعة.

أساسيات أمان الجلسات

تحافظ الجلسات على حالة المستخدم عبر طلبات HTTP. بدون إجراءات أمان مناسبة، يمكن أن تكون الجلسات عرضة لهجمات مختلفة:

<!-- ملف تعريف ارتباط جلسة غير آمن -->
<?php
session_start();
// الإعدادات الافتراضية تكشف الثغرات
?>

<!-- تكوين جلسة آمن -->
<?php
ini_set('session.cookie_httponly', 1);
ini_set('session.cookie_secure', 1);
ini_set('session.cookie_samesite', 'Strict');
ini_set('session.use_only_cookies', 1);
ini_set('session.use_strict_mode', 1);
session_start();
?>

منع اختطاف الجلسات

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

<?php
// إعادة توليد معرف الجلسة عند تغيير الامتيازات
function secureLogin($userId) {
session_regenerate_id(true);
$_SESSION['user_id'] = $userId;
$_SESSION['ip_address'] = $_SERVER['REMOTE_ADDR'];
$_SESSION['user_agent'] = $_SERVER['HTTP_USER_AGENT'];
$_SESSION['last_activity'] = time();
}

// التحقق من الجلسة في كل طلب
function validateSession() {
if (!isset($_SESSION['user_id'])) {
return false;
}

// فحص عنوان IP (اختياري - قد يسبب مشاكل لمستخدمي الهاتف المحمول)
if ($_SESSION['ip_address'] !== $_SERVER['REMOTE_ADDR']) {
session_destroy();
return false;
}

// فحص وكيل المستخدم
if ($_SESSION['user_agent'] !== $_SERVER['HTTP_USER_AGENT']) {
session_destroy();
return false;
}

// فحص انتهاء مهلة الجلسة (30 دقيقة)
if (time() - $_SESSION['last_activity'] > 1800) {
session_destroy();
return false;
}

$_SESSION['last_activity'] = time();
return true;
}
?>
تحذير: التحقق من عنوان IP قد يسبب مشاكل للمستخدمين على شبكات الهاتف المحمول أو خلف الوكلاء. ضع في اعتبارك تجربة المستخدم عند تطبيق التحقق الصارم من الجلسة.

منع تثبيت الجلسة

هجمات تثبيت الجلسة تخدع المستخدمين لاستخدام معرف جلسة معروف. قم دائمًا بإعادة توليد معرفات الجلسات عند حدود المصادقة:

<?php
// قبل تسجيل الدخول - قد يعرف المهاجم هذا المعرف
session_start();

// بعد مصادقة ناجحة
if (authenticateUser($username, $password)) {
// إعادة توليد المعرف لإبطال الجلسة القديمة
session_regenerate_id(true);
$_SESSION['authenticated'] = true;
$_SESSION['user_id'] = $userId;
}

// أيضًا إعادة التوليد عند تسجيل الخروج
function logout() {
session_start();
$_SESSION = [];
session_destroy();
session_regenerate_id(true);
}
?>

سمات ملفات تعريف الارتباط الآمنة

تتحكم سمات ملفات تعريف الارتباط في كيفية تعامل المتصفحات مع ملفات تعريف ارتباط الجلسة. توفر كل سمة حماية أمنية محددة:

<?php
// تعيين سمات ملفات تعريف ارتباط آمنة
session_set_cookie_params([
'lifetime' => 0, // ملف تعريف ارتباط الجلسة (ينتهي عند إغلاق المتصفح)
'path' => '/', // متاح على مستوى الموقع
'domain' => '.example.com', // تضمين النطاقات الفرعية
'secure' => true, // HTTPS فقط
'httponly' => true, // لا يوجد وصول JavaScript
'samesite' => 'Strict' // حماية CSRF
]);
session_start();
?>
ملاحظة: تتطلب سمة Secure بروتوكول HTTPS. تمنع سمة HttpOnly هجمات XSS من سرقة ملفات تعريف الارتباط. تمنع سمة SameSite هجمات CSRF.

أساسيات JWT (رموز الويب JSON)

رموز JWT هي رموز عديمة الحالة تحتوي على مطالبات مشفرة. تتكون من ثلاثة أجزاء: الرأس والحمولة والتوقيع:

// هيكل JWT
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9. // الرأس (base64)
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4ifQ. // الحمولة (base64)
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c // التوقيع

<?php
// إنشاء JWT باستخدام PHP
use Firebase\JWT\JWT;
use Firebase\JWT\Key;

$secretKey = getenv('JWT_SECRET_KEY');
$issuedAt = time();
$expirationTime = $issuedAt + 3600; // ساعة واحدة

$payload = [
'iss' => 'example.com', // المُصدر
'aud' => 'example.com', // الجمهور
'iat' => $issuedAt, // تاريخ الإصدار
'exp' => $expirationTime, // انتهاء الصلاحية
'sub' => $userId, // الموضوع (معرف المستخدم)
'data' => [
'userId' => $userId,
'email' => $userEmail
]
];

$jwt = JWT::encode($payload, $secretKey, 'HS256');

// التحقق من JWT
try {
$decoded = JWT::decode($jwt, new Key($secretKey, 'HS256'));
$userData = $decoded->data;
} catch (Exception $e) {
// رمز غير صالح
http_response_code(401);
die('غير مصرح');
}
?>

ثغرات JWT

رموز JWT لها ثغرات محددة يجب معالجتها:

<?php
// 1. هجوم الخلط بين الخوارزميات
// ضعيف: السماح بخوارزمية 'none'
$decoded = JWT::decode($jwt, null, ['HS256', 'none']);

// آمن: تحديد الخوارزمية بالضبط
$decoded = JWT::decode($jwt, new Key($secretKey, 'HS256'));

// 2. مفاتيح سرية ضعيفة
// ضعيف: مفتاح يمكن التنبؤ به
$secretKey = 'secret123';

// آمن: مفتاح عشوائي قوي (256+ بت)
$secretKey = bin2hex(random_bytes(32));

// 3. التحقق من انتهاء الصلاحية مفقود
// ضعيف: الرمز لا ينتهي أبدًا
$payload = ['userId' => $userId];

// آمن: قم دائمًا بتعيين انتهاء الصلاحية
$payload = [
'userId' => $userId,
'exp' => time() + 3600
];

// 4. بيانات حساسة في الحمولة
// ضعيف: تخزين كلمات المرور في JWT
$payload = ['password' => $hashedPassword];

// آمن: تخزين المطالبات الضرورية فقط
$payload = ['userId' => $userId, 'role' => $role];
?>
تحذير: حمولات JWT مشفرة base64، وليست مشفرة. لا تخزن أبدًا بيانات حساسة مثل كلمات المرور أو المعلومات الشخصية في رموز JWT بدون تشفير إضافي.

أفضل ممارسات تخزين الرموز

مكان تخزين الرموز يؤثر بشكل كبير على الأمان:

// مقارنة تخزين جانب العميل

// ❌ LocalStorage - عرضة لهجمات XSS
localStorage.setItem('jwt', token);

// ❌ SessionStorage - عرضة لهجمات XSS
sessionStorage.setItem('jwt', token);

// ✅ ملف تعريف ارتباط HTTP-Only - الأفضل لتطبيقات الويب
<?php
setcookie('jwt', $token, [
'expires' => time() + 3600,
'path' => '/',
'secure' => true,
'httponly' => true,
'samesite' => 'Strict'
]);
?>

// ✅ الذاكرة فقط (SPAs) - مقاوم لـ XSS
// تخزين الرمز في إغلاق JavaScript أو حالة React
// لا تخزن أبدًا في localStorage/sessionStorage
let authToken = null;

function setToken(token) {
authToken = token;
}

function getToken() {
return authToken;
}
أفضل ممارسة: لتطبيقات الويب التقليدية، استخدم ملفات تعريف ارتباط HTTP-only. لتطبيقات SPA التي تتطلب localStorage، قم بتطبيق حماية إضافية من XSS مثل سياسة أمان المحتوى.

تدوير رمز التحديث

قم بتطبيق رموز التحديث لتقليل عمر رمز الوصول مع الحفاظ على جلسات المستخدم:

<?php
// وظيفة توليد الرموز
function generateTokenPair($userId) {
$accessTokenExpiry = time() + 900; // 15 دقيقة
$refreshTokenExpiry = time() + 604800; // 7 أيام

$accessToken = JWT::encode([
'sub' => $userId,
'exp' => $accessTokenExpiry,
'type' => 'access'
], getenv('JWT_SECRET'), 'HS256');

$refreshToken = bin2hex(random_bytes(32));

// تخزين رمز التحديث في قاعدة البيانات
storeRefreshToken($userId, $refreshToken, $refreshTokenExpiry);

return [
'access_token' => $accessToken,
'refresh_token' => $refreshToken,
'expires_in' => 900
];
}

// نقطة نهاية التحديث
function refreshAccessToken($refreshToken) {
// التحقق من رمز التحديث في قاعدة البيانات
$tokenData = validateRefreshToken($refreshToken);

if (!$tokenData) {
http_response_code(401);
return ['error' => 'رمز تحديث غير صالح'];
}

// تدوير رمز التحديث (توليد واحد جديد)
invalidateRefreshToken($refreshToken);

// توليد زوج رموز جديد
return generateTokenPair($tokenData['user_id']);
}

// تخزين قاعدة البيانات لرموز التحديث
function storeRefreshToken($userId, $token, $expiry) {
$db->prepare(
'INSERT INTO refresh_tokens (user_id, token, expires_at) ' .
'VALUES (?, ?, FROM_UNIXTIME(?))' .
'ON DUPLICATE KEY UPDATE token = VALUES(token), expires_at = VALUES(expires_at)'
)->execute([$userId, hash('sha256', $token), $expiry]);
}
?>
ملاحظة: تدوير رمز التحديث يضمن أنه حتى إذا تم سرقة رمز التحديث، يمكن استخدامه مرة واحدة فقط. قم بتخزين رموز التحديث مجزأة في قاعدة البيانات.
تمرين: قم بتطبيق نظام إدارة جلسات آمن بالمتطلبات التالية:
1. قم بتكوين سمات ملفات تعريف ارتباط الجلسة الآمنة
2. قم بتطبيق إعادة توليد الجلسة عند تسجيل الدخول/الخروج
3. أضف مهلة الجلسة بعد 30 دقيقة من عدم النشاط
4. تحقق من وكيل المستخدم في كل طلب
5. أنشئ وظيفة تسجيل خروج تدمر الجلسات بشكل صحيح

ثم عززه باستخدام JWT:
6. قم بتوليد أزواج رموز الوصول والتحديث
7. قم بتطبيق تدوير الرموز عند التحديث
8. قم بتخزين رموز التحديث بشكل آمن في قاعدة البيانات
9. أضف معالجة انتهاء الصلاحية المناسبة
10. اختبر كلا من تدفقات مصادقة الجلسة و JWT