讨论
如果您选择将高价值的可下载内容文件直接保存在 文件系统 上,最好的办法是将它们保存在 webroot 之外。
然后,您的应用程序必须解决为内容(PDF、Word 文档、歌曲等)创建 URL(必要时进行 URL 编码)的问题。
通常,这可以通过使用查询来检索文件路径,然后在用户单击锚点(所有这不会让用户看到真正的服务器端文件路径)。
如果您不希望用户 A 将高价值可下载内容的 URL 共享给用户 B,那么您的应用程序必须以某种方式使链接专门与用户 相关联一个。可以做什么?我应该从哪里开始?
显然,您希望确保用户 A 在会话期间已登录,然后他或她才能下载文件。 不太明显的是如何防止登录用户B使用用户A发送的URL (给用户B)下载A的数字内容。
使用$_SESSION 存储登录用户的ID(数字或字符串)并进行最终查询的那部分(假设内容与用户购买或其他内容相关)将阻止登录用户B 下载他们没有购买的东西,但是您仍然会在处理他们没有购买的物品的 SQL 空集时产生资源损失。这听起来是不错的第二步。
第一步呢?有什么东西可以防止需要先进行查询吗?
好吧,让我们看看。在 HTML 表单中,可以在隐藏字段中使用 XSRF 令牌来验证提交的表单是否确实来自接收 POST/GET 请求的 Web 服务器。一个令牌用于整个表单。
给定一页用户特定的东西要下载(锚点),可以将单个令牌(相同的令牌,但每个页面请求不同)以查询字符串参数的形式嵌入每个锚点的href 属性并存储$_SESSION 中此令牌的副本。
现在,当登录用户 B 尝试使用登录用户 A 的 共享 URL 时,整个事情都失败了,因为用户 A和用户 B 有不同的会话(或根本没有会话),因此有不同的令牌。换句话说,“我的链接和你的一样,但不同。”锚点将与会话相关联,而不仅仅是与页面、用户或内容相关联。
有了这个系统,PHP 可以在不涉及数据库的情况下确定内容请求是否有效(通过将提交的令牌与$_SESSION 中的令牌进行比较)。更重要的是,可以在$_SESSION 中建立时间限制来限制有效 XSRF 令牌的持续时间/生命周期。只需使用time() 函数和基本数学即可。在这种情况下,对于锚点来说,60 分钟可能是理想的令牌寿命。如果单击的锚点的令牌已过期,则让用户再次登录。
总结
如果您使用文件系统上的文件并将路径存储在数据库中,请确保也执行以下操作(至少)。
- 对您的内容目录(webroot 之外)应用适当的文件权限。
- 对上传的文件使用随机名称。
- 在保存上传的文件之前检查重复的文件名。
- 只有登录用户才能下载高价值内容。
- 拥有一个有效的
$_SESSION 系统来阻止会话固定。
- 使用散列的 XSRF 令牌使高价值可下载内容的 URL 在每个页面上都是唯一的。
- XSRF 令牌在具有终端生命周期时涵盖更多场景。
- 根据登录的用户 ID,而不是仅根据产品对用户内容进行 SQL 查询。
- 过滤和验证所有用户输入。
- 对 SQL 查询使用准备好的语句。