رتبه موضوع:
  • 1 رای - 4 میانگین
  • 1
  • 2
  • 3
  • 4
  • 5
ذخیره سشن در دیتابیس
#1
حتماً برای شما پیش اومده که بنا به دلایل امنیتی یا مواردی مثل امکان کنترل بیشتر روی ورود و خروج کاربران، بخواین مدیریت سشن رو خودتون بدست بگیرین. خوب برای این کار، من یه کلاس نوشتم که به شما اجازه میده سشن رو توی دیتابیس ذخیره کنید. البته این کلاس برای راحتی بیشتر از یه کلاس دیگه استفاده میکنه که وظیفه اون، کار با دیتابیس هست.

کلاس DB :
class DB {
   /**
    * @property Resource The connection field
    */
   protected static $con = null;
   private static $host = 'localhost';
   private static $user = 'root';
   private static $pass = '';
   private static $db = 'test';
   
   public function __construct() {
       throw new Exception('Cannot create an object from this class.');
   }
   
   /**
    * Database connector
    * Initializes the object and connects to MySQL
    */
   public static function Connect() {
       self::$con = new mysqli(self::$host, self::$user, self::$pass, self::$db);
       if (self::$con->connect_errno) {
           die('Connect failed');
       }
       self::Query('SET NAMES \'utf8\'');
       self::$con->set_charset('utf8');
   }
   
   /**
    * How many rows affected?
    * @return The number of affected rows by the last excuted query
    */
   public static function AffectedRows() {
       if(!self::$con) {
           self::Connect();
       }
       return self::$con->affected_rows;
   }
   
   /**
    * Executes a select query and return the result as standard PHP array
    * @param string $query The select query to execute 
    * @return array The result array
    */
   public static function ArrayQuery($query) {
       if(!self::$con) {
           self::Connect();
       }
       $result = array();
       $rows = self::Query($query);
       if($rows && ($rows->num_rows > 0)) {
           while($row = $rows->fetch_assoc()) {
               $result[] = $row;
           }
       }
       return $result;
   }
   
   /**
    * Escape a value to use safely in queries
    * @param string $value The value to escape
    * @return string|boolean The escaped value if connection exists, false otherwise
    */
   public static function Escape($value) {
       if(!self::$con) {
           self::Connect();
       }
       if($value !== null) {
           return self::Quote(self::$con->real_escape_string($value));
       }
       return 'NULL';
   }
   
   /**
    * Execute a query and return the result as a MySQL resource
    * @param string $query The query to execute
    * @return resource|boolean The result resource if connection exists, false otherwise
    */
   public static function Query($query) {
       if(!self::$con) {
           self::Connect();
       }
       return self::$con->query($query);
   }
   
   /**
    * Get the last automatic generated id in insert queries
    */
   public static function InsertId() {
       if(!self::$con) {
           self::Connect();
       }
       return self::$con->insert_id;
   }
   
   /**
    * Quote the values
    * @param string @value The value to quote
    * @return string The quoted value
    */
   public static function Quote($value) {
       if($value !== null) {
           return '\'' . trim($value, '\'') . '\'';
       }
       return $value;
   }
}

و این هم کلاس مدیریت سشن در دیتابیس:
class DBSessionHandler {
  public $sessionTable = 'DBSession';
   public $autoCreateSessionTable = true;
   public $expire = 1200; // 20 minutes
   public $autoStart = true;
   
   /**
    * Create the session table
    */
   private function _createTable() {
       DB::Query("
           CREATE TABLE `{$this->sessionTable}` (
               `id` char(32) COLLATE utf8_bin NOT NULL,
               `expire` int(11) DEFAULT NULL,
               `data` longblob,
               PRIMARY KEY (`id`)
           ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin
       ");
   }
   
   /**
    * Deletes expired session
    */
   private function _deleteExpired() {
       $expire = time();
       DB::Query("DELETE FROM `{$this->sessionTable}` WHERE (`expire`<'{$expire}')");
   }
   
   /**
    * Constructor
    */
   public function __construct($autoStart = true) {
       ini_set('session.save_handler', 'user');
       $this->autoStart = $autoStart;
       if($this->autoStart) {
           $this->start();
       }
       register_shutdown_function(array($this, 'sessionClose'));
   }
   
   /**
    * Update the current session ID with a newly generated one.
    * @param boolean $deleteOldSession Whether to delete the old associated session values or not
    */
   public function regenerateId($deleteOldSession = false) {
       $oldId = session_id();
       if(empty($oldId)) {
           return;
       }
       session_regenerate_id();
       $newId = session_id();
       if(!$deleteOldSession) {
           DB::Query("UPDATE `{$this->sessionTable}` SET `id`='{$newId}' WHERE (`id`='{$oldId}')");
       }
       else {
           $expire = time() + $this->expire;
           DB::Query("INSERT INTO `{$this->sessionTable}` VALUES ('{$newId}','{$expire}','')");
       }
   }
   
   /**
    * Actually start the session
    */
   public function start() {
       @session_set_save_handler(
           array($this, 'sessionOpen'),
           array($this, 'sessionClose'),
           array($this, 'sessionRead'),
           array($this, 'sessionWrite'),
           array($this, 'sessionDestroy'),
           array($this, 'sessionGC')
       );
       @session_start();
       if(session_id() == '') {
           throw new Exception('Failed to start session.');
       }
   }
   
   /**
    * Ends the current session and store session data
    * Do not call this method directly.
    */
   public function sessionClose() {
       $this->_deleteExpired();
       if(session_id() !== '') {
           @session_write_close();
       }
   }
   
   /**
    * Session destroy handler
    * Do not call this method directly.
    * param string $id session ID
    * @return boolean whether session is destroyed successfully
    */
   public function sessionDestroy($id) {
       DB::Query("DELETE FROM `{$this->sessionTable}` WHERE (`id`='{$id}')");
       return (DB::AffectedRows() > 0);
   }
   
   /**
    * Session GC (garbage collector) handler
    * Do not call this method directly.
    * @param integer $maxLifetime The number of seconds after which data will be seen as 'garbage' and cleaned up.
    * @return boolean whether session is GCed successfully.
    */
   public function sessionGC($id) {
       $this->_deleteExpired();
       return true;
   }
   
   /**
    * Session open handler
    * Do not call this method directly.
    * @param string @savePath session save path
    * @param @sessionName session name
    * @return boolean Whether session is opened successfully
    */
   public function sessionOpen($savePath, $sessionName) {
       if($this->autoCreateSessionTable) {
           $this->_deleteExpired();
           if(DB::AffectedRows() < 0) {
               $this->_createTable();
           }
       }
       return true;
   }
   
   /**
    * Session read handler
    * Do not call this method directly.
    * @param string $id session ID
    * @return string the session data
    */
   public function sessionRead($id) {
       $expire = time();
       $data = DB::ArrayQuery("SELECT `data` FROM `{$this->sessionTable}` WHERE (`id`='{$id}' AND `expire`>='{$expire}')");
       return (count($data) > 0 ? base64_decode($data[0]['data']) : null);
   }
   
   /**
    * Session write hanlder
    * Do not call this method directly.
    * @param string $id Session ID
    * @param string $data session data
    * @return boolean Whether session write is successful
    */
   public function sessionWrite($id, $data) {
       $data = base64_encode($data);
       $expire = time() + $this->expire;
       DB::Query("INSERT INTO `{$this->sessionTable}` VALUES ('{$id}','{$data}','{$expire}') ON DUPLICATE KEY UPDATE `data`='{$data}',`expire`='{$expire}'");
   }
   
   /**
    * Count online users
    */
   public function onlineCount() {
       $expire = time();
       $data = DB::ArrayQuery("SELECT COUNT(*) AS `total` FROM `{$this->sessionTable}` WHERE (`expire`>='{$expire}')");
       return $data[0]['total'];
   }
   public function onlineUsers() {
       $expire = time();
       $data = DB::ArrayQuery("SELECT `data` FROM `{$this->sessionTable}` WHERE (`expire`>='{$expire}')");
       $users = array();
       foreach($data as $item) {
           $item = base64_decode($item['data']);
           if(preg_match('#username.*?"(.*?)"#i', $item, $match)) {
               $users[] = $match[1];
           }
       }
       return $users;
   }
}

برای استفاده از این کلاس، کافیه یه شئ ازش بسازین تا بطور خودکار سشن توی دیتابیس ذخیره بشه و بعد از اون، به شکل عادی با سشنها کار میکنید. مثالی از نحوه استفاده:
<?php
function AutoLoad($class) {
   require_once 'class.' . $class . '.php';
}
spl_autoload_register('AutoLoad');
$dbsh = new DBSessionHandler();
?>
<!doctype html>
<html>
<head>
<title>Database Session Handler</title>
<meta charset="utf-8" />
</head>
<body>
<?php
echo '<p>There are ' . $dbsh->onlineCount() . ' user(s) online.</p>' . PHP_EOL;
echo 'Online users: <br />' . PHP_EOL;
foreach($dbsh->onlineUsers() as $user) {
   echo $user . '<br />' . PHP_EOL;
}
?>
</body>
</html>

اگه سؤالی بود در خدمتم.
پاسخ
تشکر شده توسط: mgbg , mohammadjavad , meysam1366
#2
میشه در مورد ماهیت این کار بگید , چه قابلیت هایی در اختیار ما قرار میده , اصلا چرا باید سشن تو دیتابیس ذخیره بشه , اون موارد امنیتی که گفتین چین , اگه میشه توضیحاتی در این خصوص بفرمائید ...
ممنون
پاسخ
تشکر شده توسط: PHPOnline
#3
توی هاست اشتراکی، همه کاربران به پوشه tmp/ که محل ذخیره سازی سشنها و سایر فایلهای موقته دسترسی دارن و اگه هاست به درستی تنظیم نشده باشه، به فایلهای سایر کاربران داخلش هم دسترسی دارن (خیلی از هاستها این مشکل رو دارن). ذخیره سشن در دیتابیس به شما امنیت بیشتری میده چون جلوی خونده شدن سشنهاتون توسط بقیه گرفته میشه و درنتیجه احتمال بروز حملاتی نظیر Session Hijacking و Session Fixation گرفته میشه. منظورم اینه که کسی نمیتونه با داشتن سشن آیدی یکنفر (مثلاً مدیر) و ساخت دستی کوکی برای سایت شما توی سیستم خودش و ست کردن PHPSESSID با مقدار سشن آیدی اون کاربر، خودش رو بجاش جا بزنه و وارد سیستم بشه چون دسترسی به دیتابیس، نیازمند آگاهی از رمز عبور و اسم دیتابیس و نام کاربریه. ازطرفی شما نمیتونید به راحتی بفهمین توی پوشه سشن، دقیقاً چه فایلهایی هست و محتواشون رو هم بخواین بخونین، باید یکی یکی Unserialize کنید تا بفهمین فرضاً کدوما مال سایت شماست و مال چه کاربریه و کی لاگین کرده و آخرین زمان فعالیتش چی بوده و... ولی وقتی خودتون بصورت دستی مدیریت سشن رو بعهده بگیرین، میتونین دقیقاً تنظیم کنید که چه چیزهایی توی جدول دیتابیستون ثبت بشه و با کمک همونها، آماری که میخواین رو بدست بیارین. توی مثالی که گذاشتم، تعداد افراد آنلاین نشون داده میشه. برای تستش کافیه فایلها رو توی یه پوشه بگذارین و با دو مرورگر باز کنید تا ببینید آمار افراد آنلاین رو 2 نفر میزنه.
پاسخ
تشکر شده توسط: mgbg , mohammadjavad
#4
سلام

وقتی کاربر فرم ورود رو پر میکنه من از این کد استفاده میکنم و سشن رو به وجود میارم
if(count($_POST) > 0){
 $user = DBEscape(strtolower($_POST['user']));
$pass = MakeHash($_POST['pass'],$user);
$userinfo = ArrayQuary("SELECT * FROM `users` WHERE (username='{$user}' AND password='{$pass}')");
if(count($userinfo) > 0){
$_SESSION['uid'] = $userinfo[0]['id'];
$_SESSION['name'] = $userinfo[0]['username'];
Redirect('index.php');
}

}

حالا  چطور سشن رو با کلاس شما ارتباط بدم؟ یعنی چطور یوذر و ای دی رو با کلاس شما داخل دیتابیس ذخیره کنم؟
 $_SESSION['uid'] = $userinfo[0]['id'];
 $_SESSION['name'] = $userinfo[0]['username'];
پاسخ
تشکر شده توسط:
#5
شما مثل قبل کار میکنید. فقط قبلش یه شئ از کلاس بسازین. روش کار شما با سشن هیچ فرقی نمیکنه.
پاسخ
تشکر شده توسط: mohammadjavad
#6
ببخشید استاد، این کلاس رو باید توی همه صفحاتی که از سشن استفاده می کنیم فراخوانی کنیم یا نه فقط هنگام لاگین کاربر کافی هست ؟
پاسخ
تشکر شده توسط:
#7
بله. میتونید توی کدی که همه جا ضمیمه میشه (مثل فایل config) ازش استفاده کنید.
پاسخ
تشکر شده توسط:
#8
1 - یعنی اینجوری باید سشن ذخیره کنیم؟
if(count($_POST) > 0){
$user = DBEscape(strtolower($_POST['user']));
$pass = MakeHash($_POST['pass'],$user);
$userinfo = ArrayQuary("SELECT * FROM `users` WHERE (username='{$user}' AND password='{$pass}')");
if(count($userinfo) > 0){

$_SESSION['uid'] = $userinfo[0]['id'];
$_SESSION['name'] = $userinfo[0]['username'];

$dbsh = new DBSessionHandler();
$dbsh->sessionWrite($_SESSION['uid'],$_SESSION['name']);

Redirect('index.php');
}

}

 2 - و یه چیز این که سشن ها مگه نباید بعذ از 20 دقیقه حذف بشن؟  من تست کردم خذف نشدن

3  - چرا data از نوع longblob هستش؟

4 - و چرا وقتی اطلاعات توسط خود کلاس داخل دیتابیس ذخیره میشن ID شون اینطوری میشه؟  3gmffn7keib5bh6nq9dsabkr35

ببخشید اینقدر سوال پرسیدم
پاسخ
تشکر شده توسط:
#9
1- خیر، قبل از هر کاری با سشن باید شئ رو بسازین. درست مثل حالت عادی که قبل از هر کاری باید session_start رو صدا بزنید.
2- بستگی به مرورگر داره که کوکی رو حذف کنه یا نه.
3- برای اینکه بشه اطلاعات زیادی رو ذخیره کرد. مزیت اصلی این کلاس سشن هم همینه. شما حتی میتونید محتوای یک فایل رو هم توی سشن ذخیره کنید.
3- اطلاعات بصورت رمزگذاری شده ذخیره میشن. اگه دقت کنید میبینید که اطلاعات با base64 رمزگذاری و بعد ذخیره میشن. اینطوری جلوی حملات SQL Injection گرفته میشه.
پاسخ
تشکر شده توسط: mohammadjavad
#10
اقای شهرکی اگه یادتون باشه گفتم

نقل قول:  2 - و یه چیز این که سشن ها مگه نباید بعذ از 20 دقیقه حذف بشن؟  من تست کردم خذف نشدن

بعد از کلی سرو کله زدن با  کلاس مشکلش رو فهمیدم Big Grin
لاین 26

       DB::Query("DELETE FROM '{$this->sessionTable}` WHERE (`expire`<'{$expire}')");

باید تبدیل بشه به
       DB::Query("DELETE FROM `{$this->sessionTable}` WHERE (`expire`<'{$expire}')");


مشکلش این بود که بجای '  باید ` بشه
پاسخ
تشکر شده توسط:
#11
ممنون بابت اطلاع رسانی. کد کلاس رو اصلاح کردم.
پاسخ
تشکر شده توسط:
#12
حالا چطور سشن رو با کلاس شما ارتباط بدم؟
1- خیر، قبل از هر کاری با سشن باید شئ رو بسازین. درست مثل حالت عادی که قبل از هر کاری باید session_start رو صدا بزنید.

متوجه نشدم به چه شکل باید این کار رو انجام بدم
پاسخ
تشکر شده توسط:
#13
new DBSessionHandler();
پاسخ
تشکر شده توسط: PHPOnline
#14
به همین شکل ؟

if(count($_POST) > 0){
$user = DBEscape(strtolower($_POST['user']));
$pass = MakeHash($_POST['pass'],$user);
$userinfo = ArrayQuary("SELECT * FROM `users` WHERE (username='{$user}' AND password='{$pass}')");
if(count($userinfo) > 0){
 
$_SESSION['uid'] = $userinfo[0]['id'];
$_SESSION['name'] = $userinfo[0]['username'];
 
$dbsh = new DBSessionHandler();
$dbsh->sessionWrite($_SESSION['uid'],$_SESSION['name']);
 
Redirect('index.php');
}
 
}
پاسخ
تشکر شده توسط:
#15
نه باید اول شئ رو ایجاد کنین بعد با سشن کار کنین. لازم نیست متدهای کلاس رو صدا بزنین. فقط شئ رو بسازین و بعدش همون حالت عادی کار کنین. این کد رو ببینین:
$dbsh = new DBSessionHandler();
if(count($_POST) > 0){
    $user = DBEscape(strtolower($_POST['user']));
    $pass = MakeHash($_POST['pass'],$user);
    $userinfo = ArrayQuary("SELECT * FROM `users` WHERE (username='{$user}' AND password='{$pass}')");
    if(count($userinfo) > 0){
        $_SESSION['uid'] = $userinfo[0]['id'];
        $_SESSION['name'] = $userinfo[0]['username'];
        Redirect('index.php');
    }
}
پاسخ
تشکر شده توسط: mohammadjavad , PHPOnline , ADMIN , PoriaB




کاربران در حال بازدید این موضوع: 3 مهمان