我有一个用PHP7.3编写的应用程序,我想用redis来存储会话,我没有用redis作为会话处理程序,也没有使用 $_SESSION
在所有(我实际上尝试,但问题是相同的,所以我返工,但它没有帮助)。
我现在正在做的是:
当用户尝试登录时
正在尝试通过凭据从数据库中获取用户
如果凭据正确,我将生成随机uuid
在redis中存储用户数据1800秒,将uuid附加到redis键
将该uuid存储在用户cookies中
成功登录后,用户通过重定向到网站的安全部分 SessionAuthenticationMiddleware
,以及中间件检查:
如果用户有cookie(如果没有,则表示用户未登录)
使用该cookie值生成redis密钥并尝试从会话中获取用户(如果密钥不存在,则表示用户未登录)
如果redis中存在一个用户,我会再次将ttl重置为1800秒
检查我从redis获得的数据是否是现有用户(如果不是,我将从redis中删除它,这也意味着该用户没有登录)
如果全部正确,用户将重定向到页面的安全部分
对每个浏览器页面刷新进行相同的检查
问题是,出于某种原因,我随机地从redis中删除所有数据,例如,我登录并刷新页面10次,在第11次redis删除所有数据和应用程序注销我。这是随机发生的有时我可以登录5分钟,有时几秒钟。我在负责从redis中删除数据的代码中添加了日志条目,但从来没有调用过它。它发生在aws上(我使用自己的redis安装在ec2示例上),在我的本地机器上,一切正常。
不确定是否需要代码,但我将与您分享:
loginservice-从数据库获取用户并将其传递给 UserSession
服务:
public function login(string $email, string $password): void
{
$userData = $this->usersRepository->verifyCredentialsAndGetUser($email, $password);
if (empty($userData)) {
throw new Exception('Incorrect credentials provided');
}
$this->userSession->setUserLoginSession($userData);
}
usersession-编排会话和cookie存储以保存/获取/检查用户会话
class UserSession
{
public const AUTH_KEY = 'SOME_KEY';
/**
* @var RedisSessionHandler
*/
private $session;
/**
* @var AuthCookie
*/
private $authCookie;
public function __construct(RedisSessionHandler $session, AuthCookie $authCookie)
{
$this->session = $session;
$this->authCookie = $authCookie;
}
public function setUserLoginSession(array $userData): void
{
$this->authCookie->generateUserCookie();
$uuid = $this->authCookie->getUserCookie();
$this->session->write(self::AUTH_SESSION_KEY . ':' . $uuid, json_encode($userData));
}
public function clearUserLoginSession(): void
{
$uuid = $this->authCookie->getUserCookie();
$this->authCookie->clearUserCookie();
$this->session->destroy(self::AUTH_SESSION_KEY . ':' . $uuid);
}
public function getCurrentUser(): ?array
{
if ($this->authCookie->getUserCookie() === false) {
return null;
}
$uuid = $this->authCookie->getUserCookie();
$userData = $this->session->read(self::AUTH_SESSION_KEY . ':' . $uuid);
if (!empty($userData)) {
return json_decode($userData, true);
}
return null;
}
public function isAuthorized(): bool
{
if ($this->getCurrentUser() === null) {
return false;
}
$uuid = $this->authCookie->getUserCookie();
$authorized = $this->session->read(self::AUTH_SESSION_KEY . ':' . $uuid);
if (empty($authorized) || $authorized === false) {
return false;
}
return true;
}
}
authcookie-用于操作用户身份验证相关数据的 Package 器,从cookie中保存/获取/删除
class AuthCookie
{
public const AUTH_COOKIE_USER = '_user_session';
private $ttl = 1800; // 30 minutes default
/**
* @var CookieInterface
*/
private $cookie;
public function __construct(CookieInterface $cookie)
{
$this->cookie = $cookie;
}
public function generateUserCookie(bool $forever = false): bool
{
$uuid = Uuid::uuid4();
if ($forever === true) {
return $this->cookie->forever(self::AUTH_COOKIE_USER, $uuid->toString());
}
return $this->cookie->set(self::AUTH_COOKIE_USER, $uuid->toString(), $this->ttl);
}
public function hasUserCookie(): bool
{
return $this->getUserCookie() !== null;
}
public function getUserCookie(): ?string
{
return $this->cookie->get(self::AUTH_COOKIE_USER);
}
public function clearUserCookie(): bool
{
return $this->cookie->delete(self::AUTH_COOKIE_USER);
}
}
cookie-用于存储任何类型的cookie的基本cookie类
class Cookie implements CookieInterface
{
/**
* $_COOKIE global variable
*
* @var array
*/
private $cookie;
public function __construct()
{
$this->cookie = $_COOKIE;
}
public function get(string $name): ?string
{
if (!isset($this->cookie[$name])) {
return null;
}
return strip_tags(stripslashes($this->cookie[$name]));
}
public function set(
string $name,
?string $value,
int $expire = 0,
?string $path = null,
?string $domain = null,
?bool $secure = false,
?bool $httpOnly = false
): bool {
setcookie(
$name,
$value ?? '',
$this->calculateExpirationTime($expire),
$path ?? '',
$domain ?? '',
$secure ?? false,
$httpOnly ?? false
);
$this->cookie[$name] = $value;
return true;
}
public function forever(string $name, string $value): bool
{
$this->set( $name, $value, 31536000 * 5);
$this->cookie[$name] = $value;
}
public function delete(string $name): bool
{
unset($this->cookie[$name]) ;
return $this->set($name, null, time() - 15 * 60 );
}
private function calculateExpirationTime( $expire = 0 ): int
{
return (int)($expire > 0 ? time() + $expire : -1 );
}
}
redissessionhandler-负责操作与会话相关的数据从redis中保存/获取/删除。目前,我没有将其设置为php会话处理程序( session_set_save_handler()
),但我试过了,问题是一样的。
class RedisSessionHandler implements \SessionHandlerInterface
{
private $ttl = 1800; // 30 minutes default
private $db;
private $prefix;
/**
* @var LoggerInterface
*/
private $logger;
public function __construct(LoggerInterface $logger, \Redis $db, string $prefix = 'PHPSESSID:', int $ttl = 1800) {
$this->db = $db;
$this->prefix = $prefix;
$this->ttl = $ttl;
$this->logger = $logger;
}
public function open($savePath, $sessionName): bool
{
return true;
}
public function close(): bool
{
return true;
}
public function read($id) {
$id = $this->getRedisKey($id);
$this->logger->debug(
sprintf(
'%s Reading data for id %s',
__CLASS__,
$id
)
);
$sessData = $this->db->get($id);
$this->logger->debug(
sprintf(
'%s Result of reading data for id %s is: %s',
__CLASS__,
$id,
$sessData
)
);
if (empty($sessData)) {
return '';
}
$this->logger->debug(
sprintf(
'%s time to leave for id %s is %s ',
__CLASS__,
$id,
$this->db->ttl($id)
)
);
$this->db->expire($id, $this->ttl);
return $sessData;
}
public function write($id, $data) : bool
{
$id = $this->getRedisKey($id);
$this->logger->debug(
sprintf(
'%s Writing data %s for id %s',
__CLASS__,
$data,
$id
)
);
$result = $this->db->set($id, $data, $this->ttl);
$this->logger->debug(
sprintf(
'%s Write result for id %s is %s and expiration %s',
__CLASS__,
$id,
$result === true ? 'true' : 'false',
$this->ttl
)
);
return true;
}
public function destroy($id): bool
{
$id = $this->getRedisKey($id);
// this method never called, no log entiries written so a assume that application is not
// deleting redis key
$this->logger->debug(
sprintf(
'%s Destroy id %s ',
__CLASS__,
$id
)
);
$this->db->del($id);
$this->close();
return true;
}
public function gc($maxLifetime): bool
{
return true;
}
protected function getRedisKey($key)
{
if (empty($this->prefix)) {
return $key;
}
return $this->prefix . $key;
}
}
sessionauthenticationmiddleware-负责检查用户是否登录并重定向到适当的位置。
final class SessionAuthenticationMiddleware implements MiddlewareInterface
{
/**
* @var UserSession
*/
private $userSession;
/**
* @var RouteCollectorInterface
*/
private $routeCollector;
/**
* @var UserRepositoryInterface
*/
private $userRepo;
public function __construct(
UserSession $session,
RouteCollectorInterface $routeCollector,
UserRepositoryInterface $userRepositoryInterface
) {
$this->userSession = $session;
$this->routeCollector = $routeCollector;
$this-userRepo = userRepositoryInterface
}
/**
* @inheritDoc
*/
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
/**@var Route $currentRoute */
$currentRoute = $request->getAttribute(RouteContext::ROUTE);
$isAuthorized = $this->isAuthorised();
// if route is guarded and user not authorised redirect to login
if ($isAuthorized === false && $this->isGuardedRoute($currentRoute)) {
return $this->notAuthorizedResponse($request);
}
// if route is not guarded but user is authorized redirect to admin dashboard,
// also we need to check if provided user exist in our system,
// if not destroy the session and redirect to login
if ($isAuthorized && $this->isGuardedRoute($currentRoute) === false) {
return $this->redirectToDashboard();
}
return $handler->handle($request);
}
private function isGuardedRoute(Route $route): bool
{
return in_array($route->getName() , ['get.login', 'post.login'], true) === false;
}
private function isAuthorised(): bool
{
if (!$this->userSession->isAuthorized()) {
return false;
}
$userSession = $this->userSession->getCurrentUser();
if (empty($userSession)) {
return false;
}
try {
$userExist= $this-userRepo->getUser(
Uuid::fromString(
$userSession['id']
)
);
if ($userExist === false) {
$this->userSession->clearUserLoginSession();
return false;
}
} catch (ApplicationException $exception) {
$this->logger->info($exception->getMessage(), ['exception' => $exception]);
$this->userSession->clearUserLoginSession();
return false;
} catch (ApiException $exception) {
$this->logger->warning($exception->getMessage(), ['exception' => $exception]);
$this->userSession->clearUserLoginSession();
return false;
}
return true;
}
private function notAuthorizedResponse(RequestInterface $request): Response
{
$response = new Response();
if ($request->getHeaderLine('X-Requested-With') === 'xmlhttprequest') {
$response->getBody()->write(
json_encode(
[
'error' => [
'message' => 'Not authorized'
],
],
JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES
)
);
return $response->withHeader('Content-Type', 'application/json')
->withStatus(302);
}
return $response
->withHeader('Location', $this->routeCollector->getRouteParser()->urlFor('get.login'))
->withStatus(302);
}
private function redirectToDashboard(): Response
{
$response = new Response();
return $response
->withHeader('Location', $this->routeCollector->getRouteParser()->urlFor('get.dashboard'))
->withStatus(302);
}
}
我已经花了好几个小时来研究什么是错误的,我认为redis/php配置本身可能有问题,但代码中没有,但我不知道我需要寻找什么。
暂无答案!
目前还没有任何答案,快来回答吧!