PHP: 无法访问包含文件中的 $_SESSION
PHP: Can't access $_SESSION inside included file
我正在 PHP 中进行用户身份验证 class,在会话处理方面遇到了一些问题。
这是基础知识:
global.php
我有一个名为 global.php
的文件,它包含在每个页面加载的开头。在这个文件中,我还包含了其他使用的 classes,例如我正在处理的 class.uservalidation.php
。
我在 global.php
文件中启动会话。
class.uservalidation.php
当这个 class 在 global.php
文件的开头实例化时,在构造函数中调用 checkLogin
方法检查会话变量 email
和 hash
,如果它们匹配,它将 auth
属性 设置为所选用户的级别。
login.php
是登录页面(显然...),提交后将调用用户验证 class 的 login
方法。此方法将在成功登录时设置两个会话变量,email
和hash
。
index.php
为默认登录页面,会根据登录状态显示不同的内容
这是它如何工作的一个例子:
我去login.php
。会话启动,classes 加载实例化。 checkLogin
方法将首先报告auth=0
。我提交表单并再次加载同一页面。 checkLogin
将在实例化 class 时再次报告 auth=0
。然后在 login.php
脚本中调用 login
方法并设置会话变量。
但是...
当我只从 login.php
文件而不是 global.php
或 class.uservalidation.php
执行 print_r($_SESSION);
时,我可以看到会话变量(即使这是我设置的地方会话变量)。
这是一个问题,因为我需要在后续页面加载时检查来自 loginCheck
方法的 email
和 hash
会话变量。
由于 $_SESSION
是一个超全局的,我认为它可以从任何地方访问,但我不知道哪里出了问题...
我觉得我在这里遗漏了一些非常基本的东西...我对 OOP 很陌生,所以可能是我遗漏了一些关于如何声明变量或其他东西的知识,但因为它是一个超全局的我以为没关系。
[编辑#1]
这里是一些代码(由于某些原因我不能粘贴到这个文本框中,所以我创建了指向 pastebin 的链接):
global.php:
<?php
// Load configuration
require 'config.php';
// Start secure session
session_start();
// Include libraries
require 'class.uservalidation.php';
//Connect to database
// Initialize user validation
$USER=new Uservalidation();
?>
class.uservalidation.php
<?php
/*
--------------------------------------------------------------------------------------
class.uservalidation.php
--------------------------------------------------------------------------------------
Based on example at http://www.wikihow.com/Create-a-Secure-Login-Script-in-PHP-and-MySQL
*/
class Uservalidation {
public function __construct() {
$this->data=FALSE;
$this->auth=0;
$this->loginCheck();
}
// login function is provided a hashed password directly from the browser (see uservalidation.js)
public function login($email,$hash) {
global $DB;
if($email=filter_var($email,FILTER_VALIDATE_EMAIL)) {
$email=$DB->real_escape_string($email);
$hash=$DB->real_escape_string($hash);
if($user=sql_fetch("SELECT * FROM users WHERE user_email='$email' AND user_hash='$hash' AND user_status>1 LIMIT 1")) {
// Successful login
$user_browser=$_SERVER['HTTP_USER_AGENT'];
$_SESSION['session_email']=$user['user_email'];
$_SESSION['session_hash']=hash('sha512',$user['hash'].$user_browser);
$this->data=$user;
$this->auth=$user['user_auth'];
return TRUE;
} else {
// Email and hash does not match
// Record attempt in login_attempts table
return FALSE;
}
} else {
// Not a valid email address
return FALSE;
}
}
public function logout() {
$this->destroySession();
$this->data=FALSE;
$this->auth=0;
}
// Validate user session
private function loginCheck() {
$user_browser=$_SERVER['HTTP_USER_AGENT'];
if($user=$this->getUser($_SESSION['session_email'])) {
$user_hash=hash('sha512',$user['user_hash'].$user_browser);
if(hash_equals($user_hash,$_SESSION['session_hash'])) {
// Successful match
$this->data=$user;
$this->auth=$user['user_auth'];
return TRUE;
} else {
// Hashes does not match
return FALSE;
}
} else {
// User doesn't exist
return FALSE;
}
}
// Get data for specific user (either by email, uid or hash)
public function getUser($string) {
global $DB;
//echo "User: $string";
if($email=filter_var($string,FILTER_VALIDATE_EMAIL)) {
$checkuser=sql_fetch("SELECT * FROM users WHERE user_email='$email' LIMIT 1");
} elseif($id=filter_var($string,FILTER_VALIDATE_INT)) {
$checkuser=sql_fetch("SELECT * FROM users WHERE uid='$id' LIMIT 1");
} else {
$hash=$DB->real_escape_string($string);
$checkuser=sql_fetch("SELECT * FROM users WHERE user_hash='$hash' LIMIT 1");
}
return $checkuser;
}
private function clearSession() {
// Unset all of the session variables.
$_SESSION=array();
}
private function destroySession() {
$this->clearSession();
// If it's desired to kill the session, also delete the session cookie.
// Note: This will destroy the session, and not just the session data!
if(ini_get("session.use_cookies")) {
$params=session_get_cookie_params();
setcookie(session_name(), '', time() - 42000,
$params["path"], $params["domain"],
$params["secure"], $params["httponly"]
);
}
// Finally, destroy the session.
session_destroy();
}
}
?>
login.php
<?php
require_once 'global.php';
// Process login (field "p" is created in uservalidation.js and contain the SHA512 hash of the password so raw password will never be sent to server)
if(isset($_POST['user_email']) && isset($_POST['p'])) {
if($USER->login($_POST['user_email'],$_POST['p'])) {
//header('location:index.php');
$html.="<pre>";
$html.=print_r($_SESSION,TRUE);
$html.=print_r($USER,TRUE);
$html.="</pre>";
$html.="<a href='index.php'>Go on!</a>";
} else {
$html="<p>Could not log in...</p>";
}
} else {
$theform=new htmlForm('login.php');
$theform->addInput('Username',array('type' => 'email', 'name' => 'user_email', 'required' => '', 'autocomplete' => 'off'));
$theform->addInput('Password',array('type' => 'password', 'name' => 'password', 'required' => ''));
$theform->addInput(FALSE,array('type' => 'button', 'value' => 'Login', 'class' => 'button', 'onclick' => 'formhash(this.form);'));
$html=$theform->render();
}
// Render Page
//=================================================================================================
?>
<!doctype html>
<html class="no-js" lang="en" dir="ltr">
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>NGI Skunkworks</title>
<link rel="stylesheet" href="css/foundation.css">
<link rel="stylesheet" href="css/app.css">
<link rel="stylesheet" href="css/icons/foundation-icons.css" />
</head>
<body>
<?php require '_menu.php'; ?>
<div class="row">
<br>
<div class="large-12 columns">
<?php echo $html; ?>
</div>
</div>
<script src="js/vendor/jquery.js"></script>
<script src="js/vendor/what-input.js"></script>
<script src="js/vendor/foundation.js"></script>
<script src="js/app.js"></script>
<script src="js/sha512.js"></script>
<script src="js/uservalidation.js"></script>
</body>
</html>
既然你不再回复评论中的问题,我将继续我的最新想法:
不要在 php-only 文件中使用结束 ?>
php 标签。他们往往会引入被遗忘的空格,导致 HTML body 在您的代码中发送意想不到的点。
这些空格可能会导致 headers 在您开始 session 之前发送,这意味着 session-cookie 被 FUBARed。
另一个可能的原因是,由于 require_once
,您的 global.php
文件没有包含在您认为包含的时候。我建议删除 _once
部分。
无关部分
There is much to unpack in this post, so what you get is a "stream of contentiousness" style of code-review.
你所拥有的是"include oriented programming"。我强烈建议您了解自动加载器。特别是 Composer.
附带的 PSR4 自动加载器
使用 sha512 哈希算法(尤其是未加盐的)是一个非常糟糕的主意。您应该学习使用(相对)新的 Password API.
用户电子邮件应该已经是唯一参数。在请求帐户详细信息时,附加 WHERE
和 LIMIT
条件毫无意义。
用户登录后,无需将登录凭据存储在session中。您应该只存储帐户 ID。
你的 classes 在构造函数中不应该有任何业务逻辑,因为这使得测试变得非常困难class。
而且你在整个代码库中都有 SQL 注入。您不应在查询中连接数据,这与您在 PHP 代码中不使用 eval()
的原因相同。
我正在 PHP 中进行用户身份验证 class,在会话处理方面遇到了一些问题。
这是基础知识:
global.php
我有一个名为global.php
的文件,它包含在每个页面加载的开头。在这个文件中,我还包含了其他使用的 classes,例如我正在处理的class.uservalidation.php
。 我在global.php
文件中启动会话。class.uservalidation.php
当这个 class 在global.php
文件的开头实例化时,在构造函数中调用checkLogin
方法检查会话变量email
和hash
,如果它们匹配,它将auth
属性 设置为所选用户的级别。login.php
是登录页面(显然...),提交后将调用用户验证 class 的login
方法。此方法将在成功登录时设置两个会话变量,email
和hash
。index.php
为默认登录页面,会根据登录状态显示不同的内容
这是它如何工作的一个例子:
我去login.php
。会话启动,classes 加载实例化。 checkLogin
方法将首先报告auth=0
。我提交表单并再次加载同一页面。 checkLogin
将在实例化 class 时再次报告 auth=0
。然后在 login.php
脚本中调用 login
方法并设置会话变量。
但是...
当我只从 login.php
文件而不是 global.php
或 class.uservalidation.php
执行 print_r($_SESSION);
时,我可以看到会话变量(即使这是我设置的地方会话变量)。
这是一个问题,因为我需要在后续页面加载时检查来自 loginCheck
方法的 email
和 hash
会话变量。
由于 $_SESSION
是一个超全局的,我认为它可以从任何地方访问,但我不知道哪里出了问题...
我觉得我在这里遗漏了一些非常基本的东西...我对 OOP 很陌生,所以可能是我遗漏了一些关于如何声明变量或其他东西的知识,但因为它是一个超全局的我以为没关系。
[编辑#1]
这里是一些代码(由于某些原因我不能粘贴到这个文本框中,所以我创建了指向 pastebin 的链接):
global.php:
<?php
// Load configuration
require 'config.php';
// Start secure session
session_start();
// Include libraries
require 'class.uservalidation.php';
//Connect to database
// Initialize user validation
$USER=new Uservalidation();
?>
class.uservalidation.php
<?php
/*
--------------------------------------------------------------------------------------
class.uservalidation.php
--------------------------------------------------------------------------------------
Based on example at http://www.wikihow.com/Create-a-Secure-Login-Script-in-PHP-and-MySQL
*/
class Uservalidation {
public function __construct() {
$this->data=FALSE;
$this->auth=0;
$this->loginCheck();
}
// login function is provided a hashed password directly from the browser (see uservalidation.js)
public function login($email,$hash) {
global $DB;
if($email=filter_var($email,FILTER_VALIDATE_EMAIL)) {
$email=$DB->real_escape_string($email);
$hash=$DB->real_escape_string($hash);
if($user=sql_fetch("SELECT * FROM users WHERE user_email='$email' AND user_hash='$hash' AND user_status>1 LIMIT 1")) {
// Successful login
$user_browser=$_SERVER['HTTP_USER_AGENT'];
$_SESSION['session_email']=$user['user_email'];
$_SESSION['session_hash']=hash('sha512',$user['hash'].$user_browser);
$this->data=$user;
$this->auth=$user['user_auth'];
return TRUE;
} else {
// Email and hash does not match
// Record attempt in login_attempts table
return FALSE;
}
} else {
// Not a valid email address
return FALSE;
}
}
public function logout() {
$this->destroySession();
$this->data=FALSE;
$this->auth=0;
}
// Validate user session
private function loginCheck() {
$user_browser=$_SERVER['HTTP_USER_AGENT'];
if($user=$this->getUser($_SESSION['session_email'])) {
$user_hash=hash('sha512',$user['user_hash'].$user_browser);
if(hash_equals($user_hash,$_SESSION['session_hash'])) {
// Successful match
$this->data=$user;
$this->auth=$user['user_auth'];
return TRUE;
} else {
// Hashes does not match
return FALSE;
}
} else {
// User doesn't exist
return FALSE;
}
}
// Get data for specific user (either by email, uid or hash)
public function getUser($string) {
global $DB;
//echo "User: $string";
if($email=filter_var($string,FILTER_VALIDATE_EMAIL)) {
$checkuser=sql_fetch("SELECT * FROM users WHERE user_email='$email' LIMIT 1");
} elseif($id=filter_var($string,FILTER_VALIDATE_INT)) {
$checkuser=sql_fetch("SELECT * FROM users WHERE uid='$id' LIMIT 1");
} else {
$hash=$DB->real_escape_string($string);
$checkuser=sql_fetch("SELECT * FROM users WHERE user_hash='$hash' LIMIT 1");
}
return $checkuser;
}
private function clearSession() {
// Unset all of the session variables.
$_SESSION=array();
}
private function destroySession() {
$this->clearSession();
// If it's desired to kill the session, also delete the session cookie.
// Note: This will destroy the session, and not just the session data!
if(ini_get("session.use_cookies")) {
$params=session_get_cookie_params();
setcookie(session_name(), '', time() - 42000,
$params["path"], $params["domain"],
$params["secure"], $params["httponly"]
);
}
// Finally, destroy the session.
session_destroy();
}
}
?>
login.php
<?php
require_once 'global.php';
// Process login (field "p" is created in uservalidation.js and contain the SHA512 hash of the password so raw password will never be sent to server)
if(isset($_POST['user_email']) && isset($_POST['p'])) {
if($USER->login($_POST['user_email'],$_POST['p'])) {
//header('location:index.php');
$html.="<pre>";
$html.=print_r($_SESSION,TRUE);
$html.=print_r($USER,TRUE);
$html.="</pre>";
$html.="<a href='index.php'>Go on!</a>";
} else {
$html="<p>Could not log in...</p>";
}
} else {
$theform=new htmlForm('login.php');
$theform->addInput('Username',array('type' => 'email', 'name' => 'user_email', 'required' => '', 'autocomplete' => 'off'));
$theform->addInput('Password',array('type' => 'password', 'name' => 'password', 'required' => ''));
$theform->addInput(FALSE,array('type' => 'button', 'value' => 'Login', 'class' => 'button', 'onclick' => 'formhash(this.form);'));
$html=$theform->render();
}
// Render Page
//=================================================================================================
?>
<!doctype html>
<html class="no-js" lang="en" dir="ltr">
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>NGI Skunkworks</title>
<link rel="stylesheet" href="css/foundation.css">
<link rel="stylesheet" href="css/app.css">
<link rel="stylesheet" href="css/icons/foundation-icons.css" />
</head>
<body>
<?php require '_menu.php'; ?>
<div class="row">
<br>
<div class="large-12 columns">
<?php echo $html; ?>
</div>
</div>
<script src="js/vendor/jquery.js"></script>
<script src="js/vendor/what-input.js"></script>
<script src="js/vendor/foundation.js"></script>
<script src="js/app.js"></script>
<script src="js/sha512.js"></script>
<script src="js/uservalidation.js"></script>
</body>
</html>
既然你不再回复评论中的问题,我将继续我的最新想法:
不要在 php-only 文件中使用结束 ?>
php 标签。他们往往会引入被遗忘的空格,导致 HTML body 在您的代码中发送意想不到的点。
这些空格可能会导致 headers 在您开始 session 之前发送,这意味着 session-cookie 被 FUBARed。
另一个可能的原因是,由于 require_once
,您的 global.php
文件没有包含在您认为包含的时候。我建议删除 _once
部分。
无关部分
There is much to unpack in this post, so what you get is a "stream of contentiousness" style of code-review.
你所拥有的是"include oriented programming"。我强烈建议您了解自动加载器。特别是 Composer.
附带的 PSR4 自动加载器使用 sha512 哈希算法(尤其是未加盐的)是一个非常糟糕的主意。您应该学习使用(相对)新的 Password API.
用户电子邮件应该已经是唯一参数。在请求帐户详细信息时,附加 WHERE
和 LIMIT
条件毫无意义。
用户登录后,无需将登录凭据存储在session中。您应该只存储帐户 ID。
你的 classes 在构造函数中不应该有任何业务逻辑,因为这使得测试变得非常困难class。
而且你在整个代码库中都有 SQL 注入。您不应在查询中连接数据,这与您在 PHP 代码中不使用 eval()
的原因相同。