登录、用户身份验证、会话和csrf令牌


Login, user authentication, session and csrf tokens

我开始学习PHP大约一个半月前,我开始这样做,试图创建自己的非常简单的CMS,以学习如何构建基本的CRUD脚本,将与mysql数据库等进行交互。

我目前正在为我的CMS的私人管理部分构建我的登录表单,通过这样做,我想了解PHP的基本安全性。

我想做一些关于用户身份验证的基础研究,但是我遇到的解释通常包括关于最佳安全实践的信息,这些信息我并不完全理解。

因此,我寻找了更多关于我不理解的小比特的信息,其中包括我不熟悉的其他比特,我被拖入吸收了大量关于安全主题的信息,如通过模糊安全性,单向密码哈希,通过适当的php配置设置安全性,防御sql注入,php注入,会话劫持等等。

我在实现这一切时遇到了麻烦,因为我不能真正理解这一切。

第一个问题是关于在数据库中存储用户会话。

我现在知道的是,如果我使用共享主机,服务器可能没有正确配置,服务器存储会话的文件夹可能被其他人访问。

这是我想在数据库中存储会话的唯一原因吗?

第二,如何在数据库中存储会话来解决访问问题?

详细说明我的困惑,这对我来说很清楚,一旦会话存储在数据库中,它对共享主机上的人是安全的,但是我如何告诉php -"嘿,我将我的用户会话存储在数据库中,默认情况下将它从指示存储的任何地方排除"?

换句话说,仅仅因为我将会话存储在数据库中并不意味着服务器在默认情况下将它从存储会话的位置排除。这是正确的吗?如果是,我该如何控制它?

第三,我如何在数据库中实际存储会话?我假设以下过程:

session_start();
//Assume a user has successfully logged in
//For better security regenerate the session on login
$session = session_regenerate_id();
$data[]= $session;
$query = $db_connection->prepare("INSERT INTO `sessions`(`session`) VALUES (?)");
$query->execute($data);

这是否意味着以最基本的形式将会话存储在数据库中?

继续下一个问题。

假设我已经解决了上面的问题。现在,如果用户已登录,如何进行身份验证呢?

通常我会这样做:

if(!isset($_SESSION['user'])) {
    redirect_to('login.php');
}

但是由于用户会话存储在数据库中,它是直接对我可用的,还是我需要先从数据库中取出它才能使用它?

我知道这样一个事实,一旦一个会话开始有一个加密/哈希(不知道确切是哪一个)会话cookie在浏览器称为PHPSESSID。

但是由于我将会话存储在数据库中,这意味着我正在手动控制PHP通常自动执行的操作,在我的脑海中,过程链是生成->存储->加密/哈希->设置会话cookie。

这个假设正确吗?

最后是跨站伪造请求。

我的理解是,当用户登录时,他被欺骗点击链接等,这将复制他的浏览器的cookie,现在攻击者拥有会话cookie,可以冒充用户。

在这种情况下,存储在表单隐藏字段中的csrf令牌如何帮助?

如果攻击者劫持了用户的会话,根据会话检查令牌没有帮助,因为攻击者有一个有效的会话。

我错过了什么?

在我的头脑中有一些关于事物如何运作的知识空白,我希望你能为我填补这些空白。

  1. 这是我想将会话存储在数据库?

数据库有密码保护,sessions目录没有。不用说,大多数服务器应该保护/tmp/session路径(iirc),没有人可以访问它。但同样,你必须非常信任主机

第二,如何在数据库中存储会话来解决访问问题?

参见答案#1

  • 换句话说,仅仅因为我将会话存储在数据库中并不意味着服务器在默认情况下将它从存储会话的地方排除。这是正确的吗?如果是,我该如何控制它?
  • 基本上,会话是用唯一的标识符标识的。浏览器被发送一个带有该ID的cookie,服务器读取该cookie ID并将其引用到服务器上的配置位置。要设置保存路径,您可以使用session_set_save_handler -这将允许您对会话做任何您想做的事情(在保存时)-例如将其保存到DB。

  • 现在我如何验证如果用户登录?
  • **有多种方法可以确定用户是否已登录。一种基本方法是在会话中存储唯一的数据,并将其与users表关联起来。当浏览器发送会话ID时,服务器就可以根据该ID检索数据。然后,您可以使用此数据并将其与用户表交叉引用以验证用户。请记住,服务器(通常)是无状态的,这意味着在页面之间导航,服务器不会跟踪页面之间的用户。因此使用会话。一个非常基本的例子,我不会用这个,是这样的:

    <?php
    function isLoggedIn() {
      if(!empty($_SESSION['user'])) {
        $uuid = $_SESSION['user']['uuid']; // universal unique id
        $username = $_SESSION['user']['username']; // username
        $last = $_SESSION['user']['last']; // last use of session
        if($last > time() - 600) { // active last 10 minutes?
          $stmt = $db->prepare("SELECT username FROM users WHERE uuid = :uuid");
          $stmt->bindValue(":uuid", $uuid, PDO::PARAM_STR);
          $user = $stmt->fetch(PDO::FETCH_ASSOC);
          if($username == $user['username']) { // user is logged in. uuid on session matches uuid in users table
            $_SESSION['user']['last'] = time();
            return true;
          }
        }
      }
      session_destroy(); // clear everything!
      return false;
    }
    ?>
    
  • 但是由于用户会话存储在数据库中,它是直接提供给我,还是我需要先从数据库中取出它才能使用它?
  • 不要混淆user session和user对象本身。会话用于将实际的USER和APP绑定在一起。参见以上答案&示例

  • 在这种情况下,存储在表单隐藏字段中的csrf令牌如何帮助?
  • 当你有CSRF令牌在你的表单,你也有CSRF令牌在服务器上…这意味着当加载带有表单的页面时,将为特定用户生成一个CSRF令牌并将其存储在会话中。记住,拥有会话cookie的用户没有数据——只有标识符。表单还将有一个隐藏字段,例如csrf_token。在POST中,服务器将POST令牌与会话进行比较。它还将重置/清除令牌,使其无法再次发布/使用。这些令牌应该是唯一的。通常像md5(time())这样的东西效果很好。如果令牌不匹配或丢失,则可能存在攻击。此处最好的操作是使用新的CSRF令牌再次显示表单。