تسجيل الدخول والخروج
بصمات كلمات المرور، والجلسات، وحماية الصفحات. ⏱ نحو 40 دقيقة.
- تخزّن كلمات المرور ببصمات
password_hashوتتحقق منها بـpassword_verify. - تبني تدفق دخول وخروج كامل بالجلسات والتحويلات.
- تحمي الصفحات بدالة مشتركة، وتميّز المصادقة من التفويض (401 و403).
- تعرف أشهر مخاطر الدخول وحمايتها.
١لا تخزّن كلمة المرور أبدًا
أول قاعدة في الدخول: الخادم لا يحفظ كلمات المرور كما هي. فإن سُرقت قاعدة البيانات يومًا (وهذا يحدث لشركات كبرى) لن يجد المهاجم كلمات المرور نفسها. نحفظ بدلها بصمة Hash: ناتج دالة باتجاه واحد، يسهل حسابه ويستحيل عكسه عمليًّا.
<?php$hash = password_hash('Tvtc@2026', PASSWORD_DEFAULT);echo $hash . "\n";echo strlen($hash) . " حرفًا\n\n"; var_dump(password_verify('Tvtc@2026', $hash)); // الكلمة الصحيحةvar_dump(password_verify('tvtc@2026', $hash)); // حرف واحد مختلف
password_hashتضيف «ملحًا» عشوائيًّاSalt، فتعطي بصمة مختلفة كل مرة حتى للكلمة نفسها. شغّل المثال في المختبر أكثر من مرة ولاحظ.password_verifyتتحقق من الكلمة مقابل البصمة. لا تقارن البصمات بـ==أبدًا.- لا تستخدم
md5أوsha1لكلمات المرور: سريعة جدًّا، فتُخمَّن ملايين الكلمات في الثانية.
٢تدفق الدخول
- المستخدم يرسل اسمه وكلمة مروره بـ POST (وعلى HTTPS في الموقع الحقيقي).
- الخادم يجد المستخدم ويتحقق بـ
password_verify. - إن فشل: رمز 401 ورسالة عامة لا تكشف أيهما الخطأ، الاسم أم الكلمة.
- إن نجح: رقم جلسة جديد بـ
session_regenerate_id، ثم يحفظ معلومات المستخدم (لا كلمة المرور) في$_SESSION. - تحويل 303 إلى لوحة التحكم.
- كل صفحة محمية تبدأ بفحص الجلسة؛ ومن لا جلسة له يُحوَّل إلى صفحة الدخول.
لماذا رسالة عامة؟ لو قلنا «اسم المستخدم غير موجود» لعرف المهاجم أي الأسماء موجودة، وركّز تخمينه عليها.
٣النظام كاملًا
أربعة ملفات: auth.php وحدة الدخول المشتركة، وصفحة الدخول، ولوحة تحكم محمية، والخروج. تتبّع السجل: محاولة دخول للوحة بلا جلسة، ثم دخول بكلمة خاطئة، ثم صحيحة، ثم اللوحة، ثم الخروج، ثم محاولة اللوحة مرة أخرى:
<?phprequire __DIR__ . '/auth.php';$error = ''; if ($_SERVER['REQUEST_METHOD'] === 'POST') { $username = trim($_POST['username'] ?? ''); $password = $_POST['password'] ?? ''; $user = USERS[$username] ?? null; if ($user && password_verify($password, $user['hash'])) { session_regenerate_id(true); // رقم جلسة جديد بعد الدخول $_SESSION['user'] = ['username' => $username, 'name' => $user['name'], 'role' => $user['role']]; header('Location: dashboard.php', true, 303); exit; } http_response_code(401); $error = 'اسم المستخدم أو كلمة المرور غير صحيحة';}?><h2>تسجيل الدخول</h2><p style="color:red"><?= $error ?></p><form method="post"> <p><input name="username" placeholder="اسم المستخدم" value="saud"></p> <p><input name="password" type="password" placeholder="كلمة المرور" value="Tvtc@2026"></p> <button>دخول</button></form>
<?phprequire __DIR__ . '/auth.php';$u = require_login();?><h2>أهلًا <?= htmlspecialchars($u['name']) ?></h2><p>صلاحيتك: <?= $u['role'] === 'admin' ? 'مدير' : 'متدرب' ?></p><a href="logout.php">تسجيل الخروج</a>
<?phprequire __DIR__ . '/auth.php';$_SESSION = [];session_destroy();header('Location: login.php', true, 303);
<?php// auth.php: كل ما يخص الدخول في وحدة واحدةsession_start(); // المستخدمون (لاحقًا من قاعدة البيانات). كلمات المرور مخزّنة مجزّأة لا كما هيconst USERS = [ 'saud' => ['name' => 'سعود', 'role' => 'admin', 'hash' => '$2y$10$wVC2wSQ3Nh3geGIjvSF3b.nACPYbwVcXkrHbnncrkqQ32dFKNCLU6'], 'noura' => ['name' => 'نورة', 'role' => 'trainee', 'hash' => '$2y$10$2d9WSmoF1EtZ1XG0kQHmmOkdb9sTqFHLxiDQLvFKwiMvYzFmACHIu'],]; function current_user(): ?array{ return $_SESSION['user'] ?? null;} function require_login(): array{ $u = current_user(); if (!$u) { header('Location: login.php', true, 302); exit; } return $u;}
٤الصلاحيات: من يُسمح له بماذا؟
المصادقة Authentication: من أنت؟ (الدخول). التفويض Authorization: ماذا يُسمح لك؟ متدرب مسجّل دخوله لا يجوز أن يفتح صفحة إدارة المستخدمين. وهنا نرد بـ 403 Forbidden، لا 401:
| الرمز | المعنى | مثال |
|---|---|---|
401 Unauthorized | لم تثبت هويتك (لم تسجل الدخول أو فشل الدخول) | كلمة مرور خاطئة |
403 Forbidden | عرفناك، لكن ليس لك صلاحية | متدرب يفتح صفحة المدير |
<?phpfunction require_role(string $role): array{ $u = require_login(); if ($u['role'] !== $role) { http_response_code(403); exit('ليست لديك صلاحية لهذه الصفحة'); } return $u;} // في أعلى admin.php$u = require_role('admin');
٥مخاطر يجب أن تعرفها
| الخطر | الحماية |
|---|---|
| تخمين كلمات المرور آلاف المحاولات | حد للمحاولات: بعد 5 محاولات فاشلة انتظر دقائق. |
| سرقة رقم الجلسة | HTTPS، و HttpOnly، و session_regenerate_id بعد الدخول. |
| كلمات مرور ضعيفة | طول 8 على الأقل عند التسجيل. |
| CSRF: موقع آخر يرسل نموذجًا باسمك | SameSite للكوكي، ورمز سري في النماذج المهمة (مقرر 243). |
توقّع وتحقّق
الدالة التي تنشئ بصمة كلمة المرور للتخزين:
للتحقق من كلمة المرور عند الدخول:
متدرب مسجّل يفتح صفحة المدير. الرمز:
لماذا session_regenerate_id بعد الدخول؟
Session fixation.الخلاصة
- لا كلمات مرور كما هي:
password_hashللتخزين وpassword_verifyللتحقق. - الدخول الناجح:
session_regenerate_idثم $_SESSION['user']ثم تحويل 303. - كل صفحة محمية تبدأ بـ
require_login()(معexitبعد التحويل). - 401 لم تثبت هويتك، و403 لا صلاحية لك.
- رسالة فشل عامة، وحد للمحاولات، و
HTTPS.
تحدٍّ برمجي: احمِ صفحة الإدارة
متقدمنظام الدخول من الدرس جاهز. أكمل admin.php لتكون للمدير وحده، وأصلح ثغرة في login.php.
- زائر بلا جلسة يفتح admin.php ← تحويل 302 إلى login.php (استخدم require_login).
- متدرب
(noura)مسجّل يفتح admin.php ← 403 والنصليست لديك صلاحية. - المدير
(saud)← 200 والصفحة كما هي. - في login.php ثغرة: تقارن كلمة المرور بـ == مع البصمة. استبدلها بـ password_verify.
👀 اعرض الاستجابة المطلوبة
GET /admin.php- 👤 نورة
POST /login.php(username=noura&password=Noura#123) - 👤 نورة
GET /admin.php - 👤 سعود
POST /login.php(username=saud&password=Tvtc@2026) - 👤 سعود
GET /admin.php - 👤 مهاجم
POST /login.php(username=saud&password=$2y$10$wVC2wSQ3Nh3geGIjvSF3b.nACPYbwVcXkrHbnncrkqQ32dFKNCLU6)
- الكود يعمل بلا أخطاء أو تحذيرات
- زائر ← 302
- زائر ← يُحوَّل إلى login.php
- نورة تدخل بنجاح ← 303
- نورة تفتح admin.php ← 403
- نورة ← ليست لديك صلاحية
- سعود ← صفحة الإدارة
- مهاجم يرسل البصمة نفسها كلمة مرور ← 401
- تستخدم password_verify
💡 تلميح 1
$u = require_login(); ثم افحص $u['role'].💡 تلميح 2
حاول بنفسك أولًا؛ للمسألة أكثر من حلّ صحيح.
<?phprequire __DIR__ . '/auth.php';$u = require_login();if ($u['role'] !== 'admin') { http_response_code(403); exit('ليست لديك صلاحية');}echo "<h2>إدارة المستخدمين</h2>";echo "<p>عدد المستخدمين: " . count(USERS) . "</p>";
<?phprequire __DIR__ . '/auth.php';$error = ''; if ($_SERVER['REQUEST_METHOD'] === 'POST') { $username = trim($_POST['username'] ?? ''); $password = $_POST['password'] ?? ''; $user = USERS[$username] ?? null; if ($user && password_verify($password, $user['hash'])) { session_regenerate_id(true); // رقم جلسة جديد بعد الدخول $_SESSION['user'] = ['username' => $username, 'name' => $user['name'], 'role' => $user['role']]; header('Location: dashboard.php', true, 303); exit; } http_response_code(401); $error = 'اسم المستخدم أو كلمة المرور غير صحيحة';}?><h2>تسجيل الدخول</h2><p style="color:red"><?= $error ?></p><form method="post"> <p><input name="username" placeholder="اسم المستخدم" value="saud"></p> <p><input name="password" type="password" placeholder="كلمة المرور" value="Tvtc@2026"></p> <button>دخول</button></form>
<?php// auth.php: كل ما يخص الدخول في وحدة واحدةsession_start(); // المستخدمون (لاحقًا من قاعدة البيانات). كلمات المرور مخزّنة مجزّأة لا كما هيconst USERS = [ 'saud' => ['name' => 'سعود', 'role' => 'admin', 'hash' => '$2y$10$wVC2wSQ3Nh3geGIjvSF3b.nACPYbwVcXkrHbnncrkqQ32dFKNCLU6'], 'noura' => ['name' => 'نورة', 'role' => 'trainee', 'hash' => '$2y$10$2d9WSmoF1EtZ1XG0kQHmmOkdb9sTqFHLxiDQLvFKwiMvYzFmACHIu'],]; function current_user(): ?array{ return $_SESSION['user'] ?? null;} function require_login(): array{ $u = current_user(); if (!$u) { header('Location: login.php', true, 302); exit; } return $u;}
<?phprequire __DIR__ . '/auth.php';$u = require_login();?><h2>أهلًا <?= htmlspecialchars($u['name']) ?></h2><p>صلاحيتك: <?= $u['role'] === 'admin' ? 'مدير' : 'متدرب' ?></p><a href="logout.php">تسجيل الخروج</a>
<?phprequire __DIR__ . '/auth.php';$_SESSION = [];session_destroy();header('Location: login.php', true, 303);