【问题标题】:Using PHP/Apache to restrict access to static files (html, css, img, etc)使用 PHP/Apache 限制对静态文件(html、css、img 等)的访问
【发布时间】:2011-01-12 07:58:48
【问题描述】:

假设您在服务器上的一个目录中有很多 html、css、js、img 等文件。通常,互联网上的任何用户都可以通过简单地输入完整的 URL 来访问这些文件,如下所示:http://example.com/static-files/sub/index.html

现在,如果您只希望授权用户能够加载这些文件怎么办?对于此示例,假设您的用户首先从如下 URL 登录:http://example.com/login.php

您如何允许登录用户查看 index.html 文件(或“静态文件”下的任何文件),但将文件限制为其他人?

到目前为止,我提出了两种可能的解决方案:

解决方案 1
在“静态文件”下创建以下 .htaccess 文件:

Options +FollowSymLinks  
RewriteEngine on  
RewriteRule ^(.*)$ ../authorize.php?file=$1 [NC]

然后在authorize.php中...

if (isLoggedInUser()) readfile('static-files/'.$_REQUEST['file']);
else echo 'denied';

这个 authorize.php 文件被大大简化了,但你明白了。

解决方案 2
在“静态文件”下创建以下 .htaccess 文件:

Order Deny,Allow
Deny from all
Allow from 000.000.000.000

然后我的登录页面可以为每个登录的用户附加一个带有 IP 的 .htaccess 文件。显然,这还需要某种清理例程来清除旧的或不再使用的 IP。


我担心随着用户和正在访问的文件数量的增加,我的第一个解决方案在服务器上可能会变得非常昂贵。我认为我的第二个解决方案会便宜得多,但由于 IP 欺骗等原因也不太安全。我还担心如果同时有许多用户,将这些 IP 地址写入 htaccess 文件可能会成为应用程序的瓶颈。

这些解决方案中哪一个听起来更好,为什么?或者,您能想出一个完全不同的解决方案,它会比这两种方法更好吗?

【问题讨论】:

  • 使用amazon s3服务怎么样

标签: php security apache .htaccess


【解决方案1】:

我会考虑使用 PHP 加载程序来处理身份验证,然后返回您需要的文件。例如,不要执行<img src='picture.jpg' />,而是执行<img src='load_image.php?image=picture.jpg' /> 之类的操作。

您的图像加载器可以验证会话、检查凭据等,然后决定是否将请求的文件返回给浏览器。这将允许您将所有安全文件存储在 Web 可访问根目录之外,因此没有人会只是 WGET 或“意外”在那里浏览。

只需记住在 PHP 中返回正确的标头并在 php 中执行类似 readfile() 的操作,这会将文件内容返回给浏览器。

我已经在几个大型安全网站上使用过这种设置,它就像一个魅力。

编辑:我目前正在构建的系统使用这种方法来加载 Javascript、图像和视频,但我们不太担心 CSS 的安全性。

【讨论】:

  • 谢谢谢恩。这个加载器基本上就是我在第一个解决方案中描述的。我担心的是服务器费用,但我很高兴听到它在您的“大型安全网站”中运行良好。
  • 我通常不使用您上面提到的 .htaccess 规则(尽管我认为没有理由不这样做)我只是创建一些包装函数来构建正确的查询字符串并传递参数。由于您的服务器通常会返回二进制数据,因此唯一的额外开销是 PHP 处理(至少对我而言)非常值得您获得的安全性,特别是与 .htaccess 开销相比,它检查许多(数百 |数千)IP 地址系统中的每个页面请求。我认为这只是给这只猫剥皮的众多方法之一!如果您遇到其他问题,请告诉我!
  • 这种情况下浏览器无法缓存图片是否正确?
  • 不一定。实际上可以在 PHP 中设置自己的标头(包括缓存处理标头)。您还可以检查从浏览器发送给您的标头以确定您的响应。它比某些人喜欢的要复杂一些,但是你可以控制一些。我确实会发回缓存控制标头,因此我可以非常严格地控制我的缓存。
  • @Shaee,你怎么能阻止用户只加载 mysite.com/picture.jpg?我知道用户必须知道文件名和路径名才能做到这一点,但是否也可以防止这种情况发生?
【解决方案2】:

维护 htaccess 文件的内容似乎是一场噩梦。此外,您声明的目标是防止未经身份验证的 users 而不是未经身份验证的 client ip 地址 访问此内容 - 因此该方法不适合目的:

多个用户可能看起来来自同一个 IP 地址

单个用户会话可能看起来来自多个地址。

我担心我的第一个解决方案在服务器上会变得非常昂贵,因为 他们正在访问的用户和文件增加了

如果您想防止内容泄露并且不想使用 HTTP 身份验证,那么将所有文件访问封装在额外的逻辑层中是唯一明智的选择。另外,您不知道使用 PHP 来解决这个问题的问题 - 您测试过吗?我想您会惊讶于它可以提供多少吞吐量,尤其是在您使用操作码缓存的情况下。

我猜你对包装器的“简化”解决了 mime 类型和缓存等问题。

C.

【讨论】:

  • PHP 在有很多调用的重型站点上绝对是一个问题。这不仅仅是数据的传递;必须对每一个小资源执行整个用户身份验证过程。这通常重几 MB 或 RAM,并且每次请求的资源都如此。
  • 定义“重型”?我有更复杂的脚本每天在 1ghz celeron 上处理 512 Mb 内存(好吧,实际上是 4 台服务器上的 4 倍)从数据库中提取数据并仍然给出平均 250 毫秒的响应时间.
  • 不,我还没有测试过。但不管细节如何,只提供文件的 Web 服务器必须比运行 PHP、执行一些文件系统功能(如 is_filestat,然后最终通过 readfile 提供文件)便宜。
  • 我认为,如果任何单个小资源受到保护,那么授权脚本请求的数量可能是实际请求数量的十倍或一百倍。但是你真的要保护在设计不佳的网站中充当分隔符的白色 6*78px 吗?如果只保护内容,对用户要加载的图片进行身份验证只会使服务器负担加倍。
【解决方案3】:

创建一个rewrite map 来验证用户的凭据并将其重定向到适当的资源或“拒绝访问”页面。

【讨论】:

  • 是的!在最初写完这个问题后,我用同样的想法玩了一点。 RewriteMap 如果经过身份验证,则重写为最初请求的 URL,如果未经过身份验证,则重写为拒绝页面。对于我的生活,我无法让它工作。在 PRG 中指定 PHP 脚本时,我什至无法启动 Apache。你有任何使用这种技术的例子或技巧吗?我的设置是:Apache 2.2 / PHP 5.2 / Windows Server 2008
  • 脚本必须是可执行的,这在 Windows 上意味着 .php 必须与 PHP CLI 可执行文件相关联。此外,脚本应在输出时调用flush(),并应循环返回并等待进一步的输入;该脚本在 httpd 启动时启动,并在 httpd 关闭时终止。
【解决方案4】:

我一直在思考同样的问题。我同样对为每一个提供的小资源运行的 PHP 引擎感到不满。几个月前我问了一个同样的问题here,尽管关注点不同。

但我有一个非常有趣的想法可能可行。

  • 在您的 Web 服务器的某处维护一个名为 /sessions 的目录。

  • 每当用户登录时,使用/sessions 中的会话 ID 创建一个空文本文件。例如。 123456

  • 在您的 PHP 应用程序中,提供如下图像:/sessions/123456/images/test.jpg

  • 在您的 htaccess 文件中,有两个重定向命令。

  • /sessions/123456/images/test.jpg 翻译成/sessions/123456?filename=images/test.jpg

  • 第二个捕获对//sessions/(.*) 的任何调用并使用-f 标志检查指定文件是否存在。如果/sessions/123456 不存在,则表示用户已注销或会话已过期。在这种情况下,Apache 会发送 403 或重定向到错误页面 - 资源不再可用。

这样,我们在 mod_rewrite 中进行准会话身份验证,只进行一次“文件存在”检查!

我没有足够的例程来即时构建 mod_rewrite 语句,但它们应该很容易编写。 (我希望你的方向@Gumbo :)

注意事项和注意事项:

  • 必须使用 cron 作业快速删除过期的会话文件,除非可以在 .htaccess 中检查文件的 mtime(这很有可能)。

  • 只要会话存在,图像/资源对任何客户端都可用,因此没有 100% 的保护。您可以通过将客户端 IP 添加到等式(= 您创建的文件名)中并额外检查 %{REMOTE_ADDR} 来解决此问题。这是高级 .htaccess 掌握,但我很确定它是可行的。

  • 资源 URL 不是静态的,每次登录时都必须检索,因此没有缓存。

非常对对此的反馈、我可能忽略的任何不足或不可能以及任何成功的实施(我现在没有时间自己设置测试)感兴趣.

【讨论】:

    【解决方案5】:

    我编写了一个动态 Web 应用程序并将其部署在 Webshere Application Server 上,这是我保护静态文件的方式:

    我先加了

    <login-config id="LoginConfig_1">
      <auth-method>FORM</auth-method>
        <realm-name>Form-Based Authentication</realm-name>
          <form-login-config>
            <form-login-page>/login.html</form-login-page>
            <form-error-page>/login_error.html</form-error-page>
           </form-login-config>
    </login-config>
    

    在 web.xml 中,它将告诉您的网络服务器使用基于表单的身份验证(下面给出了使用登录的代码)。

    登录页面代码:

    <form id="form1" name="form1" method="post" action="j_security_check" style="padding: 0px 0px 0px 12px;">
            Username: 
              <label>
              <input name="j_username" type="text" class="font2" />
            </label>
    
            <br />
            <br />
            Password:
            <span class="font2" >
            <label>
          <input name="j_password" type="password" class="font2" />
          </label>
          </span> 
            <br />
            <br />
                <label>
            <input type="submit"  class="isc-login-button" name="Login" value="Login" />
            </label>
        </form></td>
    

    要实现基于表单的登录,您必须将网络服务器配置为使用特定的用户注册表,该用户注册表可以是 LDAP 或数据库。

    您可以声明您的安全资源,并且每当用户尝试访问这些资源时,容器会自动检查用户是否经过身份验证。即使您也可以使用安全资源附加角色。为此,我在 web.xml 中添加了以下代码

    <security-constraint>
            <display-name>Authenticated</display-name>
            <web-resource-collection>
                <web-resource-name>/*</web-resource-name>
                <url-pattern>/*</url-pattern>
                <http-method>GET</http-method>
                <http-method>PUT</http-method>
                <http-method>HEAD</http-method>
                <http-method>TRACE</http-method>
                <http-method>POST</http-method>
                <http-method>DELETE</http-method>
                <http-method>OPTIONS</http-method>
            </web-resource-collection>
            <auth-constraint>
                <description>Auth Roles</description>
                <role-name>role1</role-name>            
                <role-name>role2</role-name>            
            </auth-constraint>
        </security-constraint>
    
        <security-role>
            <role-name>role1</role-name>
        </security-role>
        <security-role>
            <role-name>role2</role-name>
        </security-role>
    

    所以这段代码不会让用户看到任何静态文件(因为/*),直到他以角色role1 和role2 登录。因此,通过这种方式,您可以保护您的资源。

    【讨论】:

      【解决方案6】:

      如果您使用的是 apache,您可以在 .htaccess 或 httpd.conf 文件中进行如下配置。下面是一个防止访问 *.inc 文件的示例。它对我很有用。

      <Files ~ "\.inc$">
      Order allow,deny
      Deny from all
      </Files>
      

      详情请参阅:http://www.ducea.com/2006/07/21/apache-tips-tricks-deny-access-to-certain-file-types/

      【讨论】:

      • 这并不能完全回答问题,因为 OP 需要授权用户以某种方式访问​​这些文件。
      【解决方案7】:

      X-发送文件

      有一个用于 Apache(和其他 HTTP 服务器)的模块,它可以让您告诉 HTTP 服务器提供您在 php 代码的标头中指定的文件: 所以你的 php 脚本应该是这样的:

      // 1) Check access rights code
      // 2) If OK, tell Apache to serve the file
      header("X-Sendfile: $filename");
      

      2 个可能的问题:

      1. 您需要访问重写规则(启用 .htaccess 或直接访问配置文件)
      2. 您需要将 mod_xsendfile 模块添加到已安装的 Apache 中

      这是另一个线程中的一个很好的答案: https://stackoverflow.com/a/3731639/2088061

      【讨论】:

      • 这绝对是解决这个问题的最佳方案。
      【解决方案8】:

      假设您要保护所有静态文件,并且必须从webroot 内部 为它们提供服务,那么您可以保护除 HEAD 之外的所有 HTTP 方法。 如果您被授权,您可以通过标头发出一个头请求并将文件内容作为正文发送。 当然这很昂贵,但你受到保护并且你有同样的行为。

      【讨论】:

        【解决方案9】:

        我可能有一个基于 iframeHTTP_REFERER 的建议,但它不是万无一失的,这取决于您希望通过此访问权限保护什么。

        但如果防止在没有身份验证的情况下显示完整的静态页面,您可以执行以下操作:

        1 - 使用 PHP 页面对用户进行身份验证

        2 - 重定向到另一个 PHP 页面,其中包含 URL 中的键和链接到正文中静态内容的 iframe:

        <iframe src="static/content.html" />
        

        3 - 然后在您的 htaccess 中,您可以像这样检查 HTTP_REFERER 中的密钥:

        RewriteEngine On
        RewriteCond %{HTTP_REFERER} !AUTH_KEY
        RewriteCond %{REQUEST_URI} ^/path/to/protected/page$
        RewriteRule . - [F]
        

        4 - 最后,如果您想让它更具动态性并且不每次都使用相同的 KEY,您可以按照 Ignacio Vazquez-Abrams 的回答建议使用 rewrite map 或使用用户 IP 作为文件名创建文件并检查是否使用REMOTE_ADDR 存在文件,然后在一段时间后删除该文件。

        但请记住,iframe + HTTP_REFERER 行为可能会因一个浏览器会话以及 REMOTE_ADDR 的不同而有所不同,因此它是有限的......

        【讨论】:

          猜你喜欢
          • 2014-08-04
          • 1970-01-01
          • 2023-04-09
          • 1970-01-01
          • 2011-08-06
          • 1970-01-01
          • 2012-04-06
          • 2021-09-08
          相关资源
          最近更新 更多