apache: 配置 - rewrite



0. 环境介绍

# 虚机配置
<VirtualHost *:80>
        ServerName www.example.com
        DocumentRoot "/var/www/html"
        CustomLog "logs/server0_vhost_log" combined
        <Directory "/var/www/html">
                <RequireAll>
                        Require all granted
                </RequireAll>
        </Directory>
</VirtualHost>

1. 加载重定向模块

# 编辑主配文件,加载rewrite模块
LoadModule rewrite_module modules/mod_rewrite.so

# 重载服务配置,检查模块加载情况
apachectl graceful
apachectl -M |grep rewrite
 rewrite_module (shared)

2. 重定向配置

# 虚拟主机中重定向配置
<IfModule mod_rewrite.c>
        RewriteEngine on
        RewriteCond %{HTTP_HOST} ^bbs.example.com$
        RewriteRule ^/(.*)$ http://www.example.com/$1 [R=301,L]
</IfModule>

# 重载服务配置,检查重定向结果
apachectl graceful
curl -x localhost:80 bbs.example.com -I
HTTP/1.1 301 Moved Permanently
Date: Wed, 25 May 2016 14:26:12 GMT
Server: Apache/2.4.20 (Unix) PHP/5.6.21
Location: http://www.example.com/
Content-Type: text/html; charset=iso-8859-1

3. 理论知识漫谈

RewriteRule的正则知识
官网链接: http://httpd.apache.org/docs/2.4/rewrite/intro.html

1) 什么是$1,$2和%1…?

2) RewriteRule语法

  1. Pattern - 请求的URL(http://hostname:port之后的部分)匹配的规则
  2. substitution - 匹配到的URL部分发送到此处重置位置
    • 文件系统绝对路径
      RewriteRule “^/games” “/usr/local/games/web”
    • web路径
      RewriteRule “^/foo$” “/bar”
      相当于$DocumentRoot/bar
    • URL绝对路径
      RewriteRule “^/product/view$” “http://site2.example.com/seeproduct.html” [R]
  3. [flags] - 影响rewrite请求的选项

3) RewriteRule的执行逻辑

## 访问bbs.example.com时,根据后面是纯数字或纯字母做出反应
# RewriteEngine on
# RewriteCond %{HTTP_HOST} ^(http://)?bbs.example.com
# RewriteRule ^/([0-9]+)$ http://www.example.com/first$1
# RewriteRule ^/([0-9]+)$ http://www.example.com/second$1
# RewriteRule ^/([a-z]+)$ http://www.example.com/third$1
# RewriteRule ^/(.*)$ http://www.example.com/fourth$1

## 通过下面的三次测试可以得出以下结论
## 当RewriteCond条件匹配到的情况下
# 匹配到的位置最上面的rule生效
## 当RewriteCond条件不匹配的情况下
# 跳过第一条rule,匹配到的最上面的rule生效

# RewriteCond匹配,三条rule匹配,上面的生效
curl -x localhost:80 bbs.example.com/11 -I
HTTP/1.1 302 Found
Date: Thu, 26 May 2016 14:25:29 GMT
Server: Apache/2.4.20 (Unix) PHP/5.6.21
Location: http://www.example.com/first11
Content-Type: text/html; charset=iso-8859-1

# RewriteCond匹配,若只有一条rule匹配,该rule生效
curl -x localhost:80 bbs.example.com/aa1 -I
HTTP/1.1 302 Found
Date: Thu, 26 May 2016 15:33:50 GMT
Server: Apache/2.4.20 (Unix) PHP/5.6.21
Location: http://www.example.com/fourthaa1
Content-Type: text/html; charset=iso-8859-1


# RewriteCond不匹配时,因为第4条rule匹配到,该rule生效
# 但当我测试将第4条rule换去第一条,未生效
curl -x localhost:80 www.baidu.com -I
HTTP/1.1 302 Found
Date: Thu, 26 May 2016 15:26:00 GMT
Server: Apache/2.4.20 (Unix) PHP/5.6.21
Location: http://www.example.com/fourth
Content-Type: text/html; charset=iso-8859-1

RewriteCond语法

  1. teststring - request的相关变量
  2. dondition - 正则匹配字符串
  3. [flags] - NC(忽略大小写), OR(或), NV(No Vary)
  4. 可有1个或多个RewriteCond
  5. 当有多个RewriteCond时,需要满足所有条件

4. 实践扩展

扩展1 - 用rewrite来匹配特定文件类型,并禁止访问

<IfModule mod_rewrite.c>
RewriteEngine on
RewriteCond %{REQUEST_URI} ^.*/tmp/* [NC]
RewriteRule .* - [F]
</IfModule>
## 条件: 当request请求的url中包含/tmp/时
## NC: 判断条件不区分大小写
## RewriteRule中的"-"代表什么都不做
## F: 拒绝访问

扩展2 - 用rewrite匹配特定useragent,并禁止访问

user_agent是什么?

user_agent就是访问网络的工具(浏览器),因为user是通过浏览器和server沟通的,所以浏览器就是user的agent

目前,一方面浏览器基本都通过给server发送一条user agent字符串,来告诉server自己的访问信息;另外一方面,搜索引擎(google、baidu)在放出网络爬虫来收集网站link时,网络爬虫也会发送user agent字符串来表明自己的身份。

user_agent string 此图来自网络http://whatsmyuseragent.com/WhatsAUserAgent

# rewrite写法
<IfModule mod_rewrite.c>
    RewriteEngine on
    RewriteCond %{HTTP_USER_AGENT}  ^*Firefox/4.0* [NC,OR]
    RewriteCond %{HTTP_USER_AGENT}  ^*Tomato Bot/1.0* [NC]
    RewriteRule  .*  -  [F]
</IfModule>
# 不推荐用rewrite到404,会产生死循环

扩展3 - 死循环相关知识

# 原意是把所有访问请求rewrite到/111/$1
RewriteRule ^(.*) /111/$1 [R,L]
# 但当访问类似于www.111.com/111,它就会无限循环在网址后面增加111

# 为了避免死循环产生,可以按如下方法处理
RewriteCond   %{REQUEST_URI} !^/111
RewriteRule ^(.*) /111/$1 [R,L]

扩展4 - 多RewriteRule对应RewriteCond

# 使用rewrite flags - [E=var:varvalue]
RewriteCond %{HTTP_HOST} ^(www\.)?([a-z0-9-]+)\.example\.com [NC]
RewriteRule .? - [E=Wa:%1,E=Wb:%2]
RewriteRule ^(.*?)-([a-z]+) %{ENV:Wb}/$1.%{ENV:Wb} [L]
RewriteRule ^(.*?)-([0-9]+)([a-z]) %{ENV:Wb}/$1$3.$2 [L]
# 将RewriteCond的%1和%2赋值给变量
# rewrite flag [L]含义是last,代表此规则是最后一条规则;

扩展5 - 用rewrite flag [S]来改变扩展5中的逻辑处理方式

# 当访问非bbs.example.com时,跳过3行Rule
# 当访问bbs.example.com时,因为不匹配条件,跳至第二行rule
# 根据第二行rule规则,跳过第三行,执行第四行规则
RewriteEngine on
RewriteCond %{HTTP_HOST} !bbs.example.com
RewriteRule .? - [S=3]
RewriteRule .? - [S=1]
RewriteRule ^/(.*)$ http://bbs.example.com/$1s
RewriteRule ^/(.*)$ http://bbs.example.com/$1f

# 验证结果
curl -x localhost:80 bbs.example.com -I
HTTP/1.1 302 Found
Date: Thu, 26 May 2016 11:03:51 GMT
Server: Apache/2.4.20 (Unix) PHP/5.6.21
Location: http://bbs.example.com/f
Content-Type: text/html; charset=iso-8859-1

扩展6 - 配置禁止的HTTP METHOD,并配置自定义的返回码

某些场景下,为了安全,只会开放特定的HTTP请求方法,其余的全部禁用掉。

下面的配置示例,禁用了除GET和POST之外的所有请求方法,并返回405状态码

RewriteEngine On
RewriteCond %{REQUEST_METHOD} !^(GET|POST)
RewriteRule .* - [R=405]

扩展7 - rewrite但不改变浏览器中的url

在一些安全类的认证中,比如PCI-DSS,对网站加载资源的来源要求是十分严格的。比如说,a.com的站点加载了b.com的资源,那么就必须要求a.com所属的组织,对b.com加载的资源要有可控性。这种可控性可以使用两种方式实现,1是把资源文件拿到a.com的服务器上,使用自己的域名来提供响应,那么就做到了完全可控;2是在a.com上对b.com的资源进行加载时,对其版本进行校验,确保这个资源的版本是经过审核无篡改的,防止b.com通过更新夹杂一些私货。

上面的安全措施是完全可以理解的,但一般如果考虑到a.com和b.com背后的组织之间的协调性,#2这个方案是更加合适的。但是实现#2的时候需要等开发排期,那么临时是否可以在基于对b.com提供的资源的基本信任的前提下,临时让访问a.com的时候,假装看不到b.com的资源了呢?

其实是有方案的,就是相当于之前客户访问a.com的时候,返回的资源中告诉用户,你还需要去b.com拿一个资源。改造为,客户访问a.com的时候,a.com所属的服务器充当一个代理服务器,帮用户请求b.com来把资源请求到,然后转发给客户,以造成一种客户只访问了a.com资源的假象。

落实到具体的实现上来说,就是需要配置apache,来通过rewrite实现浏览器中url不变的一个效果。这里就可以通过rewrite中的P这个flag来实现了,P的含义是,rewrite时,将服务器转进proxy模式,来代理客户拿到资源,再通过本机返回给用户。这就是为什么需要同时引入rewrite和proxy_http两个包的原因。

# 主配置中,加载rewrite和proxy相关的模块
LoadModule rewrite_module modules/mod_rewrite.so
LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_http_module modules/mod_proxy_http.so
LoadModule proxy_http2_module modules/mod_proxy_http2.so

# 虚拟主机配置中,使用"P"Flag来将rewrite通过proxy的方式请求回来
  RewriteEngine On
  RewriteCond %{REQUEST_URI} ^/path/to/some.js$
  RewriteRule "^(.*)$" "http://www.bbb.com/path/to/some.js" [P]

详情可以参照apache httpd rewrite flag P docs