【问题标题】:Can i use empty() for a Undefined Variable?我可以将 empty() 用于未定义的变量吗?
【发布时间】:2018-12-12 23:02:01
【问题描述】:

index.php:

if(isset($_SESSION['user_id'])){
  $user_id = $_SESSION['user_id'];
}
###$_SESSION['user_id'] is = to $user['user_id'] that i get from database in a select query on login.php

正如您在上面看到的,我仅在用户登录时设置变量$user_id。我使用if(empty($user_id)){} 来设置一些条件并且它运行良好,但我很好奇,因为如果用户不是登录$user_id 将是一个未定义的变量,我是 php 新手,所以我想知道我是否正确执行。

在用户不应该查看的页面上,除非他已登录,否则我添加了以下代码:

if(empty($user_id)) {
  $_SESSION['message'] = "You need to loge in to view this page";
  header("location: verify/error.php");
  exit;    
}

为了显示一些与用户相关的内容,我使用以下代码:

if(!empty($user_id)){
   echo 'content related to the user, divs...etc';
}

我有两个问题:

1 - 我做得对吗?

2 - 我应该将代码更改为:

if(isset($_SESSION['user_id'])){
  $user_id = $_SESSION['user_id'];
}else{
  $user_id = '';
}

【问题讨论】:

  • 是的,你可以。来自the docs“如果变量不存在或......”。我个人会设置一个额外的布尔标志,例如isLoggedin,只是为了提高可读性
  • “正确”是非常主观的,但出于所有意图和目的,您应该始终验证$_SESSION$_COOKIE$_POST$_GET、@ 987654336@、$_ENV$_FILES$_SERVER 超全局数据。你永远不应该假设它将是预期的数据类型,并使用array_key_existsisset 以及filter_var 或在适用时进行类型转换,以防止你的应用程序受到损害。例如3v4l.org/WBIPe 请记住,数组数据是通过复制而不是通过引用传递的。因此,如果$_SESSION['xyz'] 更改,您分配的变量将不会更改。
  • @fyrye array_key_exists 是 mysql 函数对吗?我不确定,因为我还没有使用它。在我的整个项目中,我只使用了 PDO,不会有冲突吗?
  • @user236945896 array_key_exists 是所有数组的全局函数
  • @fyrye 我在下面的回答中看到可以伪造 ID,我实际上已经考虑过这一点,但是...$_SESSION['user_id'] = $user['user_id'] 用户 ID 在我的桌子上,我使用 POST 方法。我知道没有什么是 100% 安全的,但是这个条件就足够了吗? if (\array_key_exists('user_id', $_SESSION) && $user_id = \filter_var($_SESSION['user_id'], \FILTER_VALIDATE_INT)) { 如果没有,我需要做什么? PS:我担心我的数据库,因为注册用户的个人资料中不会有任何私人信息,除非电子邮件和密码。

标签: php variables session isset


【解决方案1】:

我做得对吗?

首先,我的回答不会描述在您的情况下使用empty() 来对抗未定义变量警告的实现。这个答案是为了给你指明方向,并为你的案例展示一些 OOP 以及为什么它有效。

以页面 A 为例,页面 A 不需要对用户进行身份验证以进行读取访问,但页面 B 需要。

我们可以编写一个类结构,根据访问范围为您执行此检查,而不是在整个应用程序中始终如一地重新编写此检查。

首先让应用程序知道何时通过创建接口来禁止读取访问。当我们想要在用户未登录时撤销读取访问权限时,这最终将用于扩展用户身份验证类。

namespace Application\Auth;

interface MustBeLoggedIn
{
}

接下来,我们可以继续构建身份验证类。这就是这个类将根据其实例类型执行其方法的方式或顺序的指令。

namespace Application\Auth;

class Authenticable {

    protected $_user_id;
    private $_csrf = 'user_id';

    public function __construct()
    {
        # PHP 7+
        $this->_user_id = $_SESSION[$this->_csrf] ?? '';

        # PHP < 5.6
        # $this->user_id = isset($_SESSION[$this->_csrf]) ? $_SESSION[$this->csrf] : '';

        if($this instanceof MustBeLoggedIn)
            $this->mustBeLoggedIn();
    }

    private function mustBeLoggedIn()
    {
        if(!isset($this->_user_id))
            $this->authError();
    }

    protected function authError()
    {
        exit();
    }

    protected function isLoggedIn()
    {
        return isset($this->_user_id);
    }

}

最后,我们可以构建两个 User 类。第一类将撤销任何未经授权的用户的读取权限。

namespace Application\Auth;

class User extends Authenticable implements MustBeLoggedIn
{
    public function doSomething()
    {
        if($this->isLoggedIn())
            echo $this->_user_id;
    }

    protected function authError()
    {
        $_SESSION['message'] = 'Oh snap! Looks like you need to be logged in to view this page.';
        header('Location: verify/error');
        exit();
    }
}

第二个类将允许来宾和经过身份验证的用户都具有读取权限。

namespace Application;

class User extends Authenticable
{
    public function doSomething()
    {
        if($this->isLoggedIn())
            echo $this->_user_id;

        if(!$this->isLoggedIn())
            echo 'Well, you can still view this page';
    }
}

现在部署视图很容易。无需不断地重新声明该值是否存在并决定要做什么,您可以实例化适合您范围的任何一个。

如果我们想在用户登录或注销时授予用户读取权限,我们可以使用 Application\User。

class MyView extends \Application\User {
    public function sayHi() {
        echo $this->isLoggedIn() ? "Hi, {$this->user_id}!" : "Hi, Guest!";
    }
}

(new MyView())->sayHi();

如果我们想撤销对未授权用户的读取权限,我们可以使用 Application\Auth\User。

class MyView extends \Application\Auth\User {
    public function sayHi() {
        echo "Hi, {$this->user_id}!";
    }
}

(new MyView())->sayHi(); # Guests will be redirected to error/verify

我希望通过向您展示这一点,它可以帮助您更多地了解 PHP。另外,需要注意的是,您很可能容易受到CSRF attacks 的攻击,因为 ID(假设代表一个整数)非常容易伪造。也许看看using a JSON Web Token 并将用户ID 传递给它。

$_SESSION['csrf'] = \Firebase\JWT\JWT::encode(array('user_id' => 1), 'secret'); # An integer represented a base-64 encoding is going to stand out, perhaps store a generated unique token or something else in regards to identifying this user
$user = \Firebase\JWT\JWT::decode($_SESSION['csrf'], 'secret', array('HS256')); # $user['user_id'] would hold 1

谈到 fyrye 关于 JWT 使用中的漏洞利用的评论,最重要的一点是源自秘密的 HS256 安全性多数。 这是什么意思? 这意味着您可以控制自己的安全性。我使用的方法是考虑一个安全密码,但与 JWT 有关。

$secret = '_userAuthentication%App';

然后,为了提高安全性,我在将密码提供给 HS256 以使用加密之前对密码进行哈希处理。

use \Firebase\JWT\JWT;

JWT::encode(array(
    'Data' => 'You want to secure from the user',
    'Integrity' => 'This STILL should not mean it is trusted data'
), password_hash($secret, PASSWORD_BCRYPT));

解码 JWT 时,记住基本知识也很重要 - 永远不要信任数据。我们可以利用 try catch finally 块,在这种情况下忽略 finally,以确保我们得到正确的信息。

use \Firebase\JWT\JWT;

try {
    $csrf = JWT::decode($jwt, password_hash($secret, PASSWORD_BCRYPT), array('HS256'));
} catch (Exception $e) {
    // Data was invalid - Potential forge ?
}

最后请注意,如果您使用 SSL - 现在这是一项要求,请使用 RSA 签署 JWT。

【讨论】:

  • @user236945896 请注意 JWT 是 also exploitable。并且建议不要存储 user_ids 以在其中进行身份验证。
  • @fyrye 我更新了我的答案,为您的评论提供了一点额外的信息,谢谢:)
  • @Jaquarh 您更新的示例将无法解码,因为password_hash 使用随机盐,每次调用时都会产生唯一值。请参阅:3v4l.org/65Bk9 建议每个实例使用签名版本。这意味着每个 JWT 都有自己独特的无法影响的秘密,如果版本发生变化,客户端 JWT 仅对该实例无效并且不可利用。 github.com/lcobucci/jwt/blob/3.3/README.mdauth0.com/blog/…
  • 还想建议隔离 CSRF 和 JWT 的术语。 CSRF 令牌是一种防止外部源在没有有效令牌的情况下向您的应用程序提交数据的方法。每个请求的 CSRF 令牌应该是唯一的。而 JWT 更像是一个签名,例如“授权设备”,并且不需要根据请求进行更改。一般来说,您会希望同时实现 CSRF 令牌和 JWT 双重身份验证。
  • 欢迎更新,我主要命名它,因为我在解释如何防止伪造@fyrye
【解决方案2】:

要检查是否设置了变量,您应该使用isset() 进行测试。如果返回 false 值,empty() 也将返回 true。在文档页面中,以下值被认为是错误的:

“”(一个空字符串)

0(0为整数)

0.0(0 为浮点数)

“0”(0作为字符串)

错误

array()(一个空数组)

因此,如果$user_id 可以具有这些值之一(可能是整数 0),那么您可能会得到意想不到的结果。如果$user_id 永远不会是这些值之一,那么您可以检查它是否使用!empty() 设置,没有任何问题。

【讨论】:

  • "要检查是否设置了变量,您应该使用isset() 来测试"如果我使用if(!empty()) 会出现问题?
  • 我已经更新了答案,但你应该可以使用!empty(),除非你有一个有效的$user_id === 0 或类似的东西。
猜你喜欢
  • 2011-01-15
  • 2013-06-18
  • 2018-07-02
  • 2015-01-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多