经过几个小时的调试,我发现在大量 Google 搜索中找到的参考文章以及 Stack Overflow 答案的一个重要子集,例如here https://stackoverflow.com/a/11272422/3536236, here https://stackoverflow.com/a/2950504/3536236 and here https://stackoverflow.com/a/30290579/3536236全部提供无效或过时的信息。
将会话数据保存到数据库时可能导致[严重]问题的因素:
虽然所有在线示例都表明您可以“填写”session_set_save_handler
,他们都没有声明您还必须设置register_shutdown_function('session_write_close')
too (参考 http://php.net/manual/en/function.session-set-save-handler.php).
-
一些(较旧的)指南引用了过时的 SQL 数据库结构,并且应该not使用。将会话数据保存到数据库所需的数据库结构是:id
/access
/data
。就是这样。正如我在一些“指南”和示例中看到的那样,不需要各种额外的时间戳列。
- 一些较旧的指南也有过时的 MySQL 语法,例如
DELETE * FROM ...
[在我的问题中提出] 课程必须实施 http://php.net/manual/en/language.oop5.interfaces.php the SessionHandlerInterface
。我看过指南(上面提到的),它给出了实施sessionHandler
这不是一个合适的接口。也许以前版本的 PHP 有一个稍微不同的方法(可能
会话类方法must返回 PHP 手册中规定的值。同样,可能是从 5.4 之前的 PHP 继承的,但我读过的两本指南指出class->open
返回要读取的行,而PHP 手册说明 http://php.net/manual/en/function.session-set-save-handler.php它需要返回true
or false
only.
-
这就是我最初问题的原因:我使用自定义会话名称(实际上 id 作为会话名称和会话 id是同一个东西!) 按照这篇非常好的 StackOverflow 帖子 https://stackoverflow.com/questions/8419332/proper-session-hijacking-prevention-in-php这会生成一个 128 个字符长的会话名称。由于会话名称是需要破解才能危害会话并接管的唯一密钥会话劫持 https://www.owasp.org/index.php/Session_hijacking_attack那么更长的名称/ID 是一件非常好的事情。
- 但是,这引起了一个问题,因为MySQL 正在悄悄地分割会话 ID减少到只有 32 个字符,而不是 128 个字符,因此它永远无法在数据库中找到会话数据。这是一个完全无声的问题(可能是因为我的数据库连接类没有抛出此类警告)。但这是需要警惕的。如果您在从数据库检索会话时遇到任何问题,请首先检查full会话 ID 可以存储在提供的字段中。
因此,除了所有这些之外,还需要添加一些额外的细节:
PHP 手册页(上面链接)显示了一堆不适合类对象的行:
$handler = new MySessionHandler();
session_set_save_handler($handler, true);
session_start();
然而,如果将其放入类构造函数中,它也同样有效:
class MySessionHandler implements SessionHandlerInterface {
private $database = null;
public function __construct(){
$this->database = new Database(whatever);
// Set handler to overide SESSION
session_set_save_handler(
array($this, "open"),
array($this, "close"),
array($this, "read"),
array($this, "write"),
array($this, "destroy"),
array($this, "gc")
);
register_shutdown_function('session_write_close');
session_start();
}
...
}
This意味着要在输出页面上启动会话,您需要做的是:
<?php
require "path/to/sessionhandler.class.php";
new MySessionHandler();
//Bang session has been setup and started and works
作为参考,完整的 Session 通信类如下,这适用于 PHP 5.6(可能也适用于 7,但尚未在 7 上进行测试)
<?php
/***
* Created by PhpStorm.
***/
class MySessionHandler implements SessionHandlerInterface {
private $database = null;
public function __construct($sessionDBconnectionUrl){
/***
* Just setting up my own database connection. Use yours as you need.
***/
require_once "class.database.include.php";
$this->database = new DatabaseObject($sessionDBconnectionUrl);
// Set handler to overide SESSION
session_set_save_handler(
array($this, "open"),
array($this, "close"),
array($this, "read"),
array($this, "write"),
array($this, "destroy"),
array($this, "gc")
);
register_shutdown_function('session_write_close');
session_start();
}
/**
* Open
*/
public function open($savepath, $id){
// If successful
$this->database->getSelect("SELECT `data` FROM sessions WHERE id = ? LIMIT 1",$id,TRUE);
if($this->database->selectRowsFoundCounter() == 1){
// Return True
return true;
}
// Return False
return false;
}
/**
* Read
*/
public function read($id)
{
// Set query
$readRow = $this->database->getSelect('SELECT `data` FROM sessions WHERE id = ? LIMIT 1', $id,TRUE);
if ($this->database->selectRowsFoundCounter() > 0) {
return $readRow['data'];
} else {
return '';
}
}
/**
* Write
*/
public function write($id, $data)
{
// Create time stamp
$access = time();
// Set query
$dataReplace[0] = $id;
$dataReplace[1] = $access;
$dataReplace[2] = $data;
if ($this->database->noReturnQuery('REPLACE INTO sessions(id,access,`data`) VALUES (?, ?, ?)', $dataReplace)) {
return true;
} else {
return false;
}
}
/**
* Destroy
*/
public function destroy($id)
{
// Set query
if ($this->database->noReturnQuery('DELETE FROM sessions WHERE id = ? LIMIT 1', $id)) {
return true;
} else {
return false;
}
}
/**
* Close
*/
public function close(){
// Close the database connection
if($this->database->dbiLink->close){
// Return True
return true;
}
// Return False
return false;
}
/**
* Garbage Collection
*/
public function gc($max)
{
// Calculate what is to be deemed old
$old = time() - $max;
if ($this->database->noReturnQuery('DELETE FROM sessions WHERE access < ?', $old)) {
return true;
} else {
return false;
}
}
public function __destruct()
{
$this->close();
}
}
用法:如类代码文本上方所示。