【问题标题】:Whitelist in .htaccess.htaccess 中的白名单
【发布时间】:2011-03-30 13:18:10
【问题描述】:

我想使用白名单,而不是黑名单 不可访问的目录(例如deny all)。基本上,我需要这个功能:

  1. 如果 uri 请求的文件存在于 /public 目录中,则显示该文件;
  2. 否则将请求路由到/public/index.php;
  3. 请求字符串中不需要'public'字符串:http://site.com/flower.jpg显示来自文件系统的DOCUMENT_ROOT/public/flower.jpg文件;

示例:

目录结构:

 public\
   flower.jpg
   index.php
 data\
   secret_file.crt

请求字符串和预期结果:

  • site.com/flower.jpg
    • 显示flower.jpg
  • site.com/data/secret_file.crt
  • site.com/public/flower.jpg
  • site.com/public
  • site.com/data
  • site.com/any/random_url
    • 请求被路由到 public/index.php

我现在拥有的:

(甚至在外部帮助下那个

# the functionality described in #1 above
RewriteCond %{DOCUMENT_ROOT}/public%{REQUEST_URI} -f
RewriteRule .* public%{REQUEST_URI} [L]

# I'd like to take out the following line so ALL other requests route to index.php
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule .* public/index.php

如果我删除

RewriteCond %{REQUEST_FILENAME} !-f

行,它开始工作了,我尝试了无数配置,阅读了modRewrite 文档,但无法弄清楚为什么这个简单的东西拒绝简单地运行。

谁能帮助我或指出正确的方向?


完整的最终解决方案供参考


RewriteEngine On

# following line stops mod_rewrite from looping because this rule has already been applied
RewriteCond %{REQUEST_URI} !^/public/index.php
RewriteCond %{DOCUMENT_ROOT}/public%{REQUEST_URI} -f
RewriteRule .* /public%{REQUEST_URI} [L]

# don't apply this rule if the first rule has been applied
RewriteCond %{REQUEST_URI} !^/public/
RewriteRule .* /public/index.php [L]

当应用程序位于子目录(如http://site.com/uk/)中时会稍微复杂一些,但这很好用。

【问题讨论】:

    标签: apache .htaccess mod-rewrite


    【解决方案1】:

    好的,这将是一个有点令人困惑的解释。您遇到的问题是,当 mod_rewrite 重写某些东西时,没有 [R] 或 [P],它会在内部重定向,并且所有重写规则都会再次应用。这种情况一直发生,直到重写的 uri 与未重写的 uri 相同。因此,您拥有的第一条规则正在被第二条规则重写。您需要防止这种情况发生。

    首先,让我们看看第一条规则。你所拥有的一切都很好,除了你需要为警告site.com/public/flower.jpg rerouted to public/index.php 添加一个条件。这意味着如果请求本身包含/public/,它将不会为请求提供服务(并让第二条规则处理事情)。这里还有一点需要注意的是,如果您在“/public”中有一个目录“public”,例如 DOCUMENT_ROOT/public/public/,它将无法访问。

    # Make sure the request itself isn't for /public/
    RewriteCond %{THE_REQUEST} !^[A-Z]+\ /public/
    # Make sure the filename exists.
    RewriteCond %{DOCUMENT_ROOT}/public%{REQUEST_URI} -f
    RewriteRule ^ /public%{REQUEST_URI} [L]
    

    这里我们对以GET /public/flower.jpg 开头的请求进行了额外检查,如果匹配,我们将完全跳过此规则。此外,如果您尝试访问/public/ 中的目录,此规则将失效。例如,如果您在“/public”中有一个目录“stuff”,并尝试通过请求site.com/stuff/ 访问它,则此规则将不允许您查看内容(即使 / 中有 index.html 文件) stuff/) 因为您没有检查 directories 是否存在。您可以通过为 -d 添加此条件来做到这一点,如下所示:

    # Make sure the request itself isn't for /public/
    RewriteCond %{THE_REQUEST} !^[A-Z]+\ /public/
    # Make sure the filename/directory exists.
    RewriteCond %{DOCUMENT_ROOT}/public%{REQUEST_URI} -f [OR]
    RewriteCond %{DOCUMENT_ROOT}/public%{REQUEST_URI} -d
    RewriteRule ^ /public%{REQUEST_URI} [L]
    

    -d 条件与 -f 的 [OR] 一起表示:如果 %{DOCUMENT_ROOT}/public%{REQUEST_URI} 是常规文件或目录。 (See the RewriteCond docs)

    现在是第二条规则,这看起来有点令人困惑,因为我们必须处理对第一条规则条件的否定。如果第一条规则通过并且 URI 被重写,会发生 2 件事:

    1. 请求不是以以下内容开头:GET /public/
    2. URI 被重写为“/public/[something]”

    所以我们将有两个条件来处理它。如果第一条规则重写了 URI,我们不想再碰它。这解决了我在第一段中提到的问题。此外,我们不希望 URI 被重写,从而导致重写循环。因此,如果已经应用了第二条规则,我们需要添加一个停止重写的条件,这意味着 URI 现在是/public/index.php。以下是这些条件的组合:

    # stops mod_rewrite from looping because this rule has already been applied
    RewriteCond %{REQUEST_URI} !^/public/index.php
    # don't apply this rule if the first rule has been applied
    RewriteCond %{THE_REQUEST} ^[A-Z]+\ /public/  [OR]
    RewriteCond %{REQUEST_URI} !^/public/
    RewriteRule ^ /public/index.php [L]
    

    【讨论】:

    • 嘿,感谢您对 mod_rewrite 内部工作原理的洞察和解释,我推出了一个修改后的解决方案。您可能需要注意,RewriteRule . 仅适用于至少有一个符号的情况,即不会通过 index.php 路由 site.com/
    【解决方案2】:

    这可能有效:

    RewriteCond %{DOCUMENT_ROOT}/public%{REQUEST_FILENAME} -f [OR]
    RewriteCond %{DOCUMENT_ROOT}/%{REQUEST_FILENAME} -f [OR]
    RewriteCond %{DOCUMENT_ROOT}%{REQUEST_FILENAME} -f
    RewriteRule (.*) public$1 [QSA,L]
    RewriteRule .* public/index.php
    

    优化后的版本也可以,但我不确定:

    RewriteCond %{DOCUMENT_ROOT}(/public|public|)%{REQUEST_FILENAME} -f
    RewriteRule (.*) public$1 [QSA,L]
    RewriteRule .* public/index.php
    

    顺便说一句,你的逻辑很奇怪:以下规则:

    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteRule .* public/index.php
    

    意思是:“如果请求不是文件,则重写为public/index.php”。问题就在这里:如果它是一个文件,发生了什么?没有什么。 RewriteRule 被忽略。这是不安全的,想象一下如果它是一个您可能不希望用户访问的文件?删除这条规则,它没用,没有它,它更安全(从我的角度来看)。


    请问优化版是否有效?


    请尝试使用RewriteLog 指令:它可以帮助您追踪此类问题:

    # Trace:
    # (!) file gets big quickly, remove in prod environments:
    RewriteLog "/web/logs/mywebsite.rewrite.log"
    RewriteLogLevel 9
    RewriteEngine On
    

    告诉我它是否有效。

    【讨论】:

    • 感谢您的回答,我尝试了您的所有建议及其许多变体,但没有一个可悲。给我带来麻烦的是你指出的奇怪逻辑的原因。如果我删除RewriteCond %{REQUEST_FILENAME} !-f,则无法再访问公共目录中的文件,everything 将被路由到 index.php。为什么会这样,我一点都不知道:(
    • 抱歉,我在第一个示例中忘记了“%”。我可以请你重试吗?另一件事:如果所有内容都路由到 index.php,这意味着第一个重写规则没有经过验证 = 我们测试的方式不好。如果我的第一个(更正)解决方案不起作用,请尝试使用 rewritelog 指令并告诉我文件中写入的内容(无论它继续还是停止,您都会在该日志中看到)。
    • 最后是 Jon Lin 的回答让我走上了正轨,但如果系统允许我这样做,我会添加另一个赏金,以便我奖励你提供的所有帮助
    【解决方案3】:

    我对您的第一组规则有点困惑,因为如果我没记错的话,%{REQUEST_URI} 将是 /public/flower.jpg。我会这样做:

    RewriteCond public/%{REQUEST_FILENAME} -f
    RewriteRule ^.*$ public/%{REQUEST_FILENAME} [L] 
    
    RewriteCond public/%{REQUEST_FILENAME} !-f
    RewriteRule ^.*$ public/index.php [L]
    

    如果 %{REQUEST_FILENAME} 为空,我不确定行为,但基本上规则说:

    如果文件名公开存在,则重写该文件的所有URI,如果不重写为index.php

    这对你有用吗?

    【讨论】:

      【解决方案4】:

      您是否考虑过以编程方式创建您的 .htaccess 文件,以将您在用于创建它的任何文件中设置的白名单之外的任何内容列入黑名单?如果你问我,你再简单不过了。

      【讨论】:

        猜你喜欢
        • 2017-04-19
        • 1970-01-01
        • 2016-10-26
        • 2013-06-11
        • 1970-01-01
        • 2011-12-01
        • 2016-03-13
        • 1970-01-01
        • 2010-11-18
        相关资源
        最近更新 更多