在 PHP 中进行用户身份验证的最佳方法是什么?

19

我已经简单地编写了2个cookie,一个包含用户ID,第二个包含密码的1/2 SH1哈希值(加盐)。它的工作原理是不言自明的。

我意识到我并没有以最安全的方式进行此操作。有什么更好的方法吗?最好使用单个身份验证cookie。

另外,使用“难以计算的哈希”有什么意义吗?我的意思是,使用bcrypt或使用whirlpool对每个项进行10000次哈希运算,使其成为(相对)缓慢的哈希函数(200毫秒与少于1毫秒的普通SHA1),这样做有意义吗?我的意思是,如果有人入侵您的数据库并获取哈希值......还有什么需要保护的呢?因为所有数据都在同一个数据库中(除非您拥有某种去中心化设置,但我没有)。


3
如果可能的话,你应该重复使用现有的身份验证框架,因为它真的很复杂。例如,查看https://github.com/delight-im/PHP-Auth,它既不依赖具体框架,也不依赖具体数据库。 - caw
请务必使用像blowfish这样的安全哈希算法! - user2345998
6个回答

23

使用 Sessions。将会话 ID 存储在 cookie 中,并将用户状态(loggedIn、userId、IP)存储在服务器端。

需要在会话数组中存储的内容如下:

  • loggedIn:布尔变量,用于判断用户是否已登录。由于您会为多个会话重复使用同一 cookie,因此可以记住用户的用户名等信息,以方便再次访问您的站点。
  • userId:用户在数据库中的唯一 ID。使用此 ID 可以获取有关用户的更多信息,例如用户名、电子邮件等。用户注销后,此信息也可以继续保存在会话数组中。
  • IP:为了防止某人窃取会话 ID 并使用它,还需要存储用户的 IP。这是可选的,因为有时您希望允许用户漫游(例如,stackoverflow 允许我在不更改 IP 的情况下随意移动)。
  • lastPing:用户最后一次被看到的时间戳。可以使用它代替 cookie 过期日期。如果还存储了会话的生命周期,则可以根据不活动时间注销用户。这意味着会话 ID cookie 可以长时间存储在用户计算机上。

当用户注销或由于不活动而被注销时,只需将 loggedIn 设置为 false。用户使用正确的用户名和密码登录后,将 loggedIn 设置为 true,并更新其他字段(userId、IP、lifetime)。当用户加载页面时,您可以将 lastPing 与当前时间和 lifetime 进行比较,并更新 lastPing 或注销用户。

会话数据可以存储在文件系统中或数据库中。如果存储在数据库中,则 userId 可以是对用户记录的外键,也可以将所有数据放入用户记录中。

哈希

多次重新计算一个值并不是一个好的想法,因为这会降低安全性。相反,使用盐,将静态盐(例如页面名称)和用户的用户名与密码组合在一起。长时间运行的哈希函数并不比快速哈希函数更好,结果为大型消息摘要的哈希函数比结果为短消息摘要的哈希函数更好(由于暴力破解)。对于普通网站(即不是银行或机密军事组织),使用SHA1应该足够安全。


这似乎会导致存储这些数据的开销很大,特别是对于具有大量用户的“记住我的登录”功能。更不用说另一个数据库调用了..... - user15063
5
如果一次数据库查询就能让你的应用崩溃,那么你肯定有一个许多人希望拥有的问题。 - Billy ONeal
目前,除了登录或某些更改时,我不会为已登录用户进行任何数据库调用。我希望保持这种状态... - user15063
我不明白重新哈希如何增加碰撞的风险。我以为SHA函数是防碰撞的。 - user15063
@user15063: 你可能指的是碰撞抗性。"每个输入量大于输出量的哈希函数必然会有碰撞。[...] 碰撞抗性并不意味着不存在碰撞;只是难以找到它们。"(http://en.wikipedia.org/wiki/Collision_resistance). - Sz.
如果你的意思是“SHA函数是完美哈希”,那就有点不同了。无论如何,这里有一个关于这个问题的很棒的专家答案:https://dev59.com/onRC5IYBdhLWcg3wSu97#17396367。(是的,我知道你已经不存在了,但我们中没有人是完美的,对吧?;)) - Sz.

5

目前,用于识别用户的唯一令牌是他们的用户名+加盐密码哈希的1/2。该令牌是静态的,这意味着在每个请求上它都将保持不变,直到用户更改其密码。这意味着,如果我想要在系统中冒充一个用户,我只需要捕获/拦截一次令牌。(除非你在创建存储在cookie中的哈希时引入了熵)。由于大多数用户很少更改密码,攻击者将拥有类似于永久有效的令牌来访问用户帐户。

更好的解决方案是使用PHP的会话机制,并在每个请求上调用session_regenerate_id以不断更新令牌。这样做使会话劫持几乎不可能,特别是在具有IP地址/范围限制的SSL连接上。

目前,对于已登录用户,我不进行任何DB调用(除了登录或发生更改时)。我希望它保持这种状态...- Yegor 7分钟前

PHP会话数据默认情况下存储在文件系统中,因此通过使用内置的会话机制,您不会进行额外的DB调用。

此外,使用“难以计算的哈希”有什么意义吗?我的意思是,使用bcrypt,或者使用whirlpool将每个项目哈希10,000次,使其成为(相对)缓慢的哈希函数(200毫秒与少于1毫秒的普通SHA1)?

将字符串哈希10,000次并不会比一次哈希更安全。使用像SHA1或Whirlpool这样的良好单向加密进行一次哈希。

我的意思是,如果有人侵入您的数据库并获得哈希值....还剩下什么需要保护,因为所有数据都在同一个数据库中(除非您拥有某种去中心化设置,但我没有)。

盐值密码哈希可保护它们免受彩虹表攻击。目前,使用彩虹表破解盐值密码非常困难,如果不是不可能的话。


PHP会话将不会存储长期数据,例如“记住”的登录信息。仍需要将它们存储在mysql中。哈希10000并不会产生更强的哈希值,只会使暴力破解所需的计算工作变得巨大。 - user15063

1
如果您想为您的站点实现“记住我”功能,则将userid存储在cookie中是可以接受的。
您不应该将用户的密码存储在cookie中。如果他们想要,可以将其保存在浏览器中。

1

要确定这个问题有多难,需要先问问自己的安全需求有多严格。我在我的PHP应用程序中使用Zend_Auth和Zend_Acl来处理身份验证/授权。如果您目前正在使用框架,可以搜索推荐的最佳实践。即使您没有使用框架,也可以搜索其他网站/应用程序以了解其感觉。但是,在使用https进行登录/整个已登录会话、仅限http的cookie等方面还有更多要考虑的内容。


1

有几种方法可以做到这一点。我不会过于深入,因为这个主题有很多文献。

  • 首先,最好使用像 SHA256 这样的东西来减少碰撞的可能性。
  • 您还应该使用盐,或者双盐,其中一个变量盐(例如 IP -- 以防止窃取会话)。在哈希之前,cookie 字符串看起来像 YOURSALT123.123.123.123USERNAMEPASSWORD
  • 与自己的 cookie 一起使用会话。
  • 根据您的安全要求,使用 SSL。

这完全取决于您需要多么安全。关于 lolcats 的社区网站与银行网站相比具有不同的安全要求。


-2

这是最好的方法,也是最简单的方法

步骤1 在您的登录/索引页面中输入以下代码

<?php if(check if username and password is correct)
{
header('location:home.php');
$_SESSION['logged']=true;
}
else
{
header('location:index.php');
}
?>

第二步,在你想要进行身份验证的页面上

<?php 
if(isset($_SESSION['logged']))
{
?>

your page content here

<?php    }
else
{
header('location:index.php');
?>

在您的注销页面中的第三步

<?php

session_start();
session_destroy();
header('Location:index.php');

?>

就是这样


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接