我的目标是每个用户只允许一个会话。
要做到这一点,我试图在有人试图连接时检测某个会话是否仍然对某个用户有效。
我提出的最佳解决方案是将用户ID添加到数据库中的会话存储中,但我无法使其正常工作。
我尝试使用this answer并将其改编为Symfony 3,但我一直有以下错误,无法找出原因?:
AuthorizationChecker cannot be converted to string
这是我的代码:
<?php
namespace UserBundle\Utils;
use Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
class UserIdPdoSessionHandler extends PdoSessionHandler
{
/**
* @var \PDO PDO instance.
*/
private $pdo;
/**
* @var array Database options.
*/
private $dbOptions;
/**
* @var AuthorizationCheckerInterface
*/
private $authorizationChecker;
/**
* @var TokenStorageInterface
*/
private $tokenStorage;
public function __construct(\PDO $pdo, array $dbOptions = array(), AuthorizationChecker $authorizationChecker, TokenStorage $tokenStorage)
{
$this->pdo = $pdo;
$this->dbOptions = array_merge(
array('db_user_id_col' => 'user_id'),
$dbOptions
);
$this->$authorizationChecker = $authorizationChecker;
$this->$tokenStorage = $tokenStorage;
parent::__construct($pdo, $dbOptions);
}
public function read($id)
{
// get table/columns
$dbTable = $this->dbOptions['db_table'];
$dbDataCol = $this->dbOptions['db_data_col'];
$dbIdCol = $this->dbOptions['db_id_col'];
try {
$sql = "SELECT $dbDataCol FROM $dbTable WHERE $dbIdCol = :id";
$stmt = $this->pdo->prepare($sql);
$stmt->bindParam(':id', $id, \PDO::PARAM_STR);
$stmt->execute();
// it is recommended to use fetchAll so that PDO can close the DB cursor
// we anyway expect either no rows, or one row with one column. fetchColumn, seems to be buggy #4777
$sessionRows = $stmt->fetchAll(\PDO::FETCH_NUM);
if (count($sessionRows) == 1) {
return base64_decode($sessionRows[0][0]);
}
// session does not exist, create it
$this->createNewSession($id);
return '';
} catch (\PDOException $e) {
throw new \RuntimeException(sprintf('PDOException was thrown when trying to read the session data: %s', $e->getMessage()), 0, $e);
}
}
/**
* {@inheritDoc}
*/
public function write($id, $data)
{
// get table/column
$dbTable = $this->dbOptions['db_table'];
$dbDataCol = $this->dbOptions['db_data_col'];
$dbIdCol = $this->dbOptions['db_id_col'];
$dbTimeCol = $this->dbOptions['db_time_col'];
$dbUserIdCol = $this->dbOptions['db_user_id_col'];
//session data can contain non binary safe characters so we need to encode it
$encoded = base64_encode($data);
$userId = $this->authorizationChecker->isGranted('IS_AUTHENTICATED_REMEMBERED') ?
$this->tokenStorage->getToken()->getUser()->getId() :
null
;
try {
$driver = $this->pdo->getAttribute(\PDO::ATTR_DRIVER_NAME);
if ('mysql' === $driver) {
// MySQL would report $stmt->rowCount() = 0 on UPDATE when the data is left unchanged
// it could result in calling createNewSession() whereas the session already exists in
// the DB which would fail as the id is unique
$stmt = $this->pdo->prepare(
"INSERT INTO $dbTable ($dbIdCol, $dbDataCol, $dbTimeCol, $dbUserIdCol) VALUES (:id, :data, :time, :user_id) " .
"ON DUPLICATE KEY UPDATE $dbDataCol = VALUES($dbDataCol), $dbTimeCol = VALUES($dbTimeCol)"
);
$stmt->bindParam(':id', $id, \PDO::PARAM_STR);
$stmt->bindParam(':data', $encoded, \PDO::PARAM_STR);
$stmt->bindValue(':time', time(), \PDO::PARAM_INT);
$stmt->bindParam(':user_id', $userId, \PDO::PARAM_STR);
$stmt->execute();
} elseif ('oci' === $driver) {
$stmt = $this->pdo->prepare("MERGE INTO $dbTable USING DUAL ON($dbIdCol = :id) ".
"WHEN NOT MATCHED THEN INSERT ($dbIdCol, $dbDataCol, $dbTimeCol, $dbUserIdCol) VALUES (:id, :data, sysdate, :user_id) " .
"WHEN MATCHED THEN UPDATE SET $dbDataCol = :data WHERE $dbIdCol = :id");
$stmt->bindParam(':id', $id, \PDO::PARAM_STR);
$stmt->bindParam(':data', $encoded, \PDO::PARAM_STR);
$stmt->bindParam(':user_id', $userId, \PDO::PARAM_STR);
$stmt->execute();
} else {
$stmt = $this->pdo->prepare("UPDATE $dbTable SET $dbDataCol = :data, $dbTimeCol = :time WHERE $dbIdCol = :id");
$stmt->bindParam(':id', $id, \PDO::PARAM_STR);
$stmt->bindParam(':data', $encoded, \PDO::PARAM_STR);
$stmt->bindValue(':time', time(), \PDO::PARAM_INT);
$stmt->execute();
if (!$stmt->rowCount()) {
// No session exists in the database to update. This happens when we have called
// session_regenerate_id()
$this->createNewSession($id, $data);
}
}
} catch (\PDOException $e) {
throw new \RuntimeException(sprintf('PDOException was thrown when trying to write the session data: %s', $e->getMessage()), 0, $e);
}
return true;
}
private function createNewSession($id, $data = '')
{
// get table/column
$dbTable = $this->dbOptions['db_table'];
$dbDataCol = $this->dbOptions['db_data_col'];
$dbIdCol = $this->dbOptions['db_id_col'];
$dbTimeCol = $this->dbOptions['db_time_col'];
$dbUserIdCol = $this->dbOptions['db_user_id_col'];
$userId = $this->authorizationChecker->isGranted('IS_AUTHENTICATED_REMEMBERED') ?
$this->tokenStorage->getToken()->getUser()->getId() :
null
;
$sql = "INSERT INTO $dbTable ($dbIdCol, $dbDataCol, $dbTimeCol, $dbUserIdCol) VALUES (:id, :data, :time, :user_id)";
//session data can contain non binary safe characters so we need to encode it
$encoded = base64_encode($data);
$stmt = $this->pdo->prepare($sql);
$stmt->bindParam(':id', $id, \PDO::PARAM_STR);
$stmt->bindParam(':data', $encoded, \PDO::PARAM_STR);
$stmt->bindValue(':time', time(), \PDO::PARAM_INT);
$stmt->bindParam(':user_id', $userId, \PDO::PARAM_STR);
$stmt->execute();
return true;
}
}
pdo:
class: PDO
arguments:
dsn: "mysql:host=%database_host%;dbname=%database_name%"
user: "%database_user%"
password: "%database_password%"
session.handler.pdo:
class: UserBundle\Utils\UserIdPdoSessionHandler
public: false
arguments: [ "@pdo", "%pdo.db_options%", "@service_container" ]
我的第一个想法是添加用户使用的最后一个sessionId,并检查它是否存活但存储在数据库中的id与
检索的id不同$request->getSession()->getId();
我做得对吗?你能想出一个更好的方法来实现我的最终目标吗?
答案 0 :(得分:0)
我的不好,我忘了从会话配置中删除“save_path”。 这就是为什么DB中保存的会话ID与从Request对象中检索的会话ID不同。
所以我在我的用户实体中添加了一个字段“lastSessionId”,所以现在我可以检查这个会话是否仍然有效。