关于http反向代理的一些简单研究

在大型项目的部署中,经常会用到反向代理。

由于后续站点的后端部署就会涉及到反向代理,就研究了一下。

本文所提及的Web服务器软件只有Apache httpd(下文简写为Apache)、Nginx,因为我也只用过这两个。

以及,这是去年同期挖的一个坑,一直搁置了一年左右才想起来。

以及原本个人意向里这篇文章只会包含Apache的相关配置。

为何要使用反向代理

这一段的内容为我的个人见解。

  1. 相比于后端程序,Apach、Nginx这类Web服务器软件通常对http有着更好的支持和优化。
  2. 对于需要使用免费SSL证书的服务器,Apache、Nginx等服务器可以更好的处理免费SSL证书续期相关的配置。
  3. 对于单台服务器,若要同时部署大量后端程序,通过使用反向代理可让所有后端程序走80/443的标准http/https端口。
  4. 对于企业,通常会部署大量服务器,此时可以将几台服务器设置为网关服务器,暴露在公网,控制外网对内部服务器的访问。
  5. 如果部署了多台服务器,可以通过反向代理实现负载均衡,将流量平摊到各个服务器上。

Apache的反向代理配置

Apache如需支持反向代理,需要启用模块mod_proxy,以及你需要的代理模块。

推荐启用的代理模块
mod_proxy_html, mod_proxy_http: 这两个是用来处理http和html相关的反向代理的模块。
mod_proxy_balancer: 负载均衡相关,等之后深入了解了会有用处。

假设我们将Spring Boot后端运行在8080端口上,我们想用api.local.azurezeng.com:443这个地址将后端程序暴露到外网。

接下来在Apache配置文件中启用需要的模块,然后添加下面的配置。

这里使用虚拟主机配置,以保证Apache服务器可以使用多个域名提供服务。

<VirtualHost *:443>
    # ...
    ServerName api.local.azurezeng.com
    # 不要将服务器的/.well-known路径进行反代,方便处理SSL证书自动续期问题
    ProxyPass /.well-known !
    # 将其他所有请求反代到127.0.0.1:8080,也就是我们的后端端口
    ProxyPass / http://127.0.0.1:8080/
    # 保证部分传出外网的重定向请求,地址是正确的
    ProxyPassReverse / http://127.0.0.1:8080/
    # 将Host请求头发送给被反代的程序
    ProxyPreserveHost On
    # ...
</VirtualHost>

配置完成后重新加载Apache,来看看效果。我们的请求被全部重定向到了我们的后端服务上中。

Nginx的反向代理配置

本来说呢,这一段写不了的,因为当年开这个坑的时候本站服务器还在使用Apache作为Web服务器。今年暑假摸了一下Nginx,所以这一段也可以写了。

为了让php有更好的支持,我把php和nginx都安装到了Linux虚拟机下。

根据个人配置,相对于虚拟机而言,我们主机上运行的后端服务器的地址就变为 了10.1.10.1:8080。我们现在想使用api.vm.azurezeng.com:443(此域名已提前指向虚拟机)将后端程序暴露到公网中。

需要注意,下面的配置需要放到配置文件的http设置节点中。

同样的,这个配置中我们也使用了虚拟主机功能来保证nginx能处理多个域名下的请求。

http {
    #...
    server {
        listen 443 ssl http2;
        listen [::]:443 ssl http2;
        server_name api.vm.azurezeng.com;
        index index.html index.htm default.html default.htm;
        root  /home/wwwroot/public_html/api;  # 自行更改
        #...
        # 排除/.well-known文件夹,以方便SSL证书自动续期
        location ^~ /.well-known/ {
            allow all;
        }

        location / {
            proxy_pass http://10.1.10.1:8080/;
        }

        #...
    }
    #...
}

输入完配置后重新加载nginx,此时就可以用这个地址访问到我们的后端服务了。

如何让后端程序正确的识别到用户IP地址

相关原理和配置方案

通过后端程序获取请求来源的IP地址原理可知,使用上面的配置文件配置好反代之后,后端程序已经无法正常的识别到请求来源的IP地址。

此时需要了解一些东西,来保证后端程序能正确的在反向代理的环境下工作。

X-Forwarded系列http系列请求头
一般情况下,反向代理服务器会使用X-Forwarded系列http请求头,向被代理的服务器传递请求来源信息。这里只提4个可能用得上的。
X-Forwarded-For: 表示请求来源IP地址,以及经过的一系列代理服务器的IP地址。
X-Forwarded-Port: 用的较少,用于指示请求来源连接代理服务器,所使用的代理服务器端口。
X-Forwarded-Host: 表示请求来源向代理服务器发起本次请求所使用的域名,常用于使用一个服务器处理多个域名下的请求的场景。
X-Forwarded-Proto: 表示请求来源与代理服务器之间的连接所使用的协议(如http或https)。
其中最重要的请求头就是X-Forwarded-For,我们需要用它来获取请求来源的IP地址。

Apache的请求头设置

Apache在向后端程序发起请求前,已经自动设置好了相关请求头。当然,除非你手动关闭掉了这个功能(ProxyAddHeaders Off)。

写一个接口来看看发送过来的请求头是什么情况:

Nginx的请求头配置

而对于Nginx,我们需要做一些额外设置。

location / {
    proxy_pass http://10.1.10.1:8080/;
    proxy_redirect http://10.1.10.1:8080/ /;
    proxy_set_header Host $host;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection keep-alive;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Port $server_port;
    proxy_set_header X-Forwarded-Host $host;
    proxy_set_header X-Forwarded-Proto $scheme;
}

$proxy_add_x_forwarded_for这里需要提醒的一点是,如果传过来的请求已经存在X-Forwarded-For,则会将代理服务器的IP地址追加到发往后端服务器的X-Forwarded-For请求头,否则就等同于请求来源的IP地址。在需要多层反代的情况下$proxy_add_x_forwarded_for会非常有用,但是也存在一些问题(下面会提)。

设置完成之后重新加载nginx,就可以正常的检测到X-Forwarded-For相关的请求头了。

此时后端程序就可以通过X-Forwarded-For正常的获取请求来源的IP地址了。

对后端程序进行设置调整

相比于直接从请求头直接读取请求的IP地址,若能直接用后端提供的API来读取请求的IP地址会好很多。大部分后端框架和后端程序对X-Forwarded-For提供了较好的支持。

下面就提几个我用过的后端框架和服务器程序的相关配置,其他的框架和软件请自行阅读相关文档。

在做好相关设置的前提下,框架的访问来源IP地址相关API,或者程序的访问来源IP地址识别就不会发生问题了。

Spring Boot

前往application.properties或application.yml,加入下面的配置(下面使用YAML配置)。

server:
  # 这里正常情况下,用framework也可以,反正不能是默认的none
  forward-headers-strategy: native

Spring Cloud Gateway

这玩意本身就是个网关服务器,将请求发往下级后端程序。所以默认设置已经支持X-Forwarded-For转发。

需要详细设置请参阅相关的配置文档。

当然,通常情况不建议将Spring Cloud Gateway网关程序直接暴露到公网,而是套一层Apache或nginx等这类Web程序,用反代访问Spring Cloud Gateway网关程序。

ASP.NET Core/Kestrel

此处使用ASP.NET Core 6.0举例。

找到程序代码的Main函数(或者别的地方,如果你把这段初始化代码扔到别的地方去了),加入下面的代码。

var builder = WebApplication.CreateBuilder(args);
//..
var app = builder.Build();
//...
app.UseForwardedHeaders(new ForwardedHeadersOptions {
    // 此处可灵活调整
    // 你也可以仅使用X-Forwarded-For和X-Forwarded-Proto
    // 如果后端程序和Web程序不在一个服务器上,
    // 可能需要调整KnownNetworks和KnownProxies
    ForwardedHeaders = ForwardedHeaders.All,
    KnownNetworks = { new IPNetwork(IPAddress.Parse("10.255.255.255"), 8) }
});

Tomcat

找到conf/server.xml文件,依次按照Server->Service->Engine->Host选项,然后加入下面的内容。

<!-- RemoteIp Value -->
<Valve className="org.apache.catalina.valves.RemoteIpValve" />

IP伪造问题和解决办法

我们现在已经知道,后端程序在使用反向代理之后需要依赖X-Forwarded系列请求头来获取请求来源的IP地址。目前我们通过配置Apache/Nginx,做到了让Apache/Nginx在将请求发往后端程序时自动设置好相关的请求头。

但是,目前的配置存在安全隐患。如果请求发送过来的时候就带有X-Forwarded相关请求头,里面有伪造的请求来源IP地址,Apache/Nginx会误认为发送过来的请求已经经过了一次反向代理,此时后端程序收到的X-Forwarded请求头信息就是被伪造过的。换句话说,我们如果知道有这个漏洞,就可以随意的伪造IP地址。

虽然部分后端有“信任的反代服务器”相关的设置,能一定程度的规避此问题,但这只是治标不治本。

对于暴露在外网的服务器(如果是内网服务器继续进行反代,请直接使用上面的配置,否则可能出现请求头出问题),建议在将请求传递到下一级内网服务器或后端服务时,清除请求的X-Forwarded相关请求头,再由Web服务器设置X-Forwarded相关请求头内容。

不要信任用户的任何输入。——前端设计名言

对于Apache,我们可以修改一下配置,来修复此问题。(需要使用模块mod_headers)

RequestHeader指令与Apache的反向代理自动处理X-Forwarded请求头选项(ProxyAddHeaders)存在矛盾,因此必须关闭ProxyAddHeaders并自行处理请求头内容。

<VirtualHost *:443>
    #...
    ServerName api.local.azurezeng.com
    #...
    ProxyPass /.well-known !
    ProxyPass / http://127.0.0.1:8080/
    ProxyPassReverse / http://127.0.0.1:8080/

    # 永远不要相信用户的输入,删除请求中的X-Forwarded相关信息
    RequestHeader unset X-Forwarded-*
    ProxyAddHeaders Off
    # 手动设置
    RequestHeader set X-Forwarded-For "expr=%{REMOTE_ADDR}"
    RequestHeader set X-Forwarded-Port "expr=%{SERVER_PORT}"
    RequestHeader set X-Forwarded-Host "expr=%{SERVER_NAME}"
    RequestHeader set X-Forwarded-Proto "expr=%{REQUEST_SCHEME}"

    ProxyPreserveHost On
    #...
</VirtualHost>

对于Nginx,则是将上面提到的$proxy_add_x_forwarded_for替换为$remote_addr

当请求来源没有携带X-Forwarded-For时,$proxy_add_x_forwarded_for的值等效于$remote_addr(请求来源的IP地址)。做此修改之后,反向代理服务器就不会再读取来源的X-Forwarded-For请求头,而是将请求来源的IP地址作为X-Forwarded-For的内容,发往下级服务器。

做完上述修改,并重新加载服务器配置之后,向服务器发送的X-Forwarded相关内容就会被服务器直接丢弃,也就不存在X-Forwarded伪造问题了。

总结

至此,我们就设置好了基础的Web服务器的反向代理,已经可以用反向代理处理一些比较常见的情景了。

我个人觉得,本文只做抛砖引玉作用,向各位读者指明一些问题的解决方向,以及踩到的一些坑。

如果还需要做一些更高级的配置,还需要读者自己自行研究相关配置,来做到符合自己需求。

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇