我写了一个类来将会话存储在数据库中。我无法确定我对用户代理的检查是否有效,因为我想不出测试它的方法。
我还担心session_regent_id()在每个session_start()上被调用,并被manual's warnings regarding object destruction and the need for session_register_shutdown()混淆。
我的用户代理检查是否始终匹配?
在哪里可以更好地重新生成会话id?
构造函数是会话寄存器关闭的好地方吗?
提前谢谢。
代码:
Session.class.php会话
<?php
namespace Company\Project;
use \PDO;
class Session
{
private $dblayer;
private $user_agent;
/**
* Session constructor.
* @param PDO $dblayer
*/
public function __construct(PDO $dblayer)
{
$this->dblayer = $dblayer;
$this->user_agent = $_SERVER['HTTP_USER_AGENT'];
session_set_save_handler(
array($this, 'open'),
array($this, 'close'),
array($this, 'read'),
array($this, 'write'),
array($this, 'destroy'),
array($this, 'gc')
);
if ('LIVE' == DEVELOPMENT_MODE) {
session_set_cookie_params(0, '/', '', true, true);
} else {
session_set_cookie_params(0, '/', '', false, true);
}
session_register_shutdown();
session_start();
session_regenerate_id(true);
}
/**
* @return bool
*/
public function open()
{
if ($this->dblayer) {
return true;
}
return false;
}
/**
* @return bool
*/
public function close()
{
$this->dblayer = null;
return true;
}
/**
* @param $id
* @return bool|string
*/
public function read($id)
{
try {
$this->dblayer->beginTransaction();
$stmt = $this->dblayer->prepare("SELECT data, user_agent FROM sessions WHERE id = :id LIMIT 1");
$stmt->bindParam(':id', $id);
$stmt->execute();
$this->dblayer->commit();
if ($row = $stmt->fetch()) {
$data = $row['data'];
$original_user_agent = $row['user_agent'];
}
if ($original_user_agent != $this->user_agent) {
session_destroy();
header('Location:' . SITE_PATH . '/login.php');
exit;
}
return $data;
} catch (\Exception $e) {
$this->dblayer->rollBack();
// will use file_put_contents to save error message, file etc to error log
return '';
}
}
/**
* @param $id
* @param $data
* @return bool
*/
public function write($id, $data)
{
$access = time();
$user_agent = $_SERVER['HTTP_USER_AGENT'];
$this->dblayer->beginTransaction();
$stmt = $this->dblayer->prepare("REPLACE INTO sessions VALUES(:id, :data, :user_agent, :access)");
$stmt->bindParam(':id', $id);
$stmt->bindParam(':data', $data);
$stmt->bindParam(':user_agent', $user_agent);
$stmt->bindParam(':access', $access);
$stmt->execute();
$this->dblayer->commit();
if ($stmt) {
return true;
}
echo 'error';
$this->dblayer->rollBack();
// can i save to error log here?
return false;
}
/**
* @param $id
* @return bool
*/
public function destroy($id)
{
try {
$this->dblayer->beginTransaction();
$stmt = $this->dblayer->prepare("DELETE FROM sessions WHERE id = :id");
$stmt->bindParam(':id', $id);
$stmt->execute();
$this->dblayer->commit();
} catch (\PDOException $e) {
$this->dblayer->rollBack();
// again, will save error data to log
echo $e->getMessage();
return false;
}
}
/**
* @param $max
* @return bool
*/
public function gc($max)
{
$to_delete = time() - $max;
try {
$this->dblayer->beginTransaction();
$stmt = $this->dblayer->prepare("DELETE FROM sessions WHERE access < :to_delete");
$stmt->bindParam(':to_delete', $to_delete);
$this->dblayer->commit();
return true;
} catch (\PDOException $e) {
$this->dblayer->rollBack();
// save error data to log;
return false;
}
}
}
最佳答案
你至少有三个问题:
测试用户代理检查
要进行测试,您可以更改浏览器的用户代理;在Safari中,调试菜单中有一些可用的选项,Chrome和Firefox可能有类似的或配置选项,但肯定有插件来执行此操作。
但是,我建议不要进行这种类型的检查(基本指纹识别)-不能保证用户代理不会更改-如果他们的浏览器在会话中间安装了更新,会发生什么情况?坦率地说,如果您可以接管一个用户会话,特别是通过IDs会话的重新生成,那么您可以欺骗用户用户代理。
在构造函数中重新生成会话ID
虽然这很好,但在高负载时可能会对性能产生影响-如果可以,则更好的选择是仅在权限提升时重新生成会话ID,例如:
登录时
访问用户配置文件页时
更改密码时
创建新内容时
退房时
注销时
在构造函数中使用session_register_shutdown();
虽然手册上说最好使用session_register_shutdown();
(或者在旧版本中使用register_shutdown_function('session_write_close')
),但我实际上不同意这种说法。可以注册多个shutdown函数,并按添加register_shutdown_function()
的顺序调用它们-但是,如果其中一个调用exit
则不再调用。
我认为最好在析构函数中调用session_write_close()
,
class Session
{
…
public function __destruct()
{
session_write_close();
}
}
即使在调用
exit
之后,当堆栈被解开时,也会调用此选项。关于php - 在 session 类中检查用户代理并重新生成 session ID,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/34791615/