Nginx通过二级目录(路径)映射不同的反向代理,规避IP+端口访问
这是我上一家公司的案例总结,发现躺在草稿箱好几个月了,今天得空就整理发布一下。
先说一下开发那边提来的 2 个 case:
①、同一个域名需要反向代理到前台和后台(不同机器和端口); ②、需要采用 IP+端口的模式,嵌入到 APP 作为 DNS 污染后的备选方案。
对于第①个问题,很好解决:通过区分二级目录来反代不同的节点即可,所以代码类似如下:
server {
listen 80;
server_name demo.domain.com;
#通过访问service二级目录来访问后台
location /service/ {
#DemoBackend1后面的斜杠是一个关键,没有斜杠的话就会传递service到后端节点导致404
proxy_pass http://DemoBackend1/;
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
#其他路径默认访问前台网站
location / {
proxy_pass http://DemoBackend2;
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
#简单的负载均衡节点配置
upstream DemoBackend1 {
server 192.168.1.1;
server 192.168.1.2;
ip_hash;
}
upstream DemoBackend2 {
server 192.168.2.1;
server 192.168.2.2;
ip_hash;
}
如上配置即可实现通过一个域名来反代不同的后端节点,用到的思路就是匹配二级目录来反代。
对于第②个问题,可能粗略一看,还没理解是个啥意思吧!
其实就是现在业界流行的一种防 DNS 污染的解决方案之一:手机 APP 里面除了通过域名来获取数据,还会额外嵌入一些备用的 IP。APP 在获取数据时,会先通过域名向服务器发起一个简单的校验请求,如果得到的不是预期数据,说明该网络环境下的 DNS 已被污染,比如被运营商劫持,请求 A 内容却给你展示 B 内容!这时候,APP 将会启动备用预案,通过 IP 的方式来请求数据!很明显,这个做法可以有效避免恶心的 DNS 劫持了(看完这段是不是有所收获呢?)。
做法很简单,就是在 APP 中集成多个 IP 和端口作为备用的访问途径。
当开发 GG 找到我,提出的需求是:
需要实现公网 IP+端口来访问,比如邮件 API 使用 http://192.168.1.10:125 Ps:公网服务器是多线的,那么就有多个 IP,本文假设电信是 192.168.1.10,联通是 192.168.2.10,移动是 192.168.3.10 等
说白了就是要用端口来区分不同的 API,此时如果我不深究,顺手可能会写出如下配置:
#API1
server {
listen 125;
server_name 192.168.1.10 192.168.2.10 192.168.3.10;
location / {
proxy_pass http://DemoBackend;
proxy_redirect off;
proxy_set_header Host api1.domain.com;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
#API2
server {
listen 126;
server_name 192.168.1.1 192.168.2.1 192.168.3.1;
location / {
proxy_pass http://DemoBackend;
proxy_redirect off;
proxy_set_header Host api2.domain.com;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
##API n等等....
粗略一看,确实是可以实现开发 GG 的要求啊!再仔细一想,你会发现如此做法会开放越来越多的端口!运维成本以及辨识度低还只是其次,咱说好的安全第一呢?
经过思考和测试,我写出的最终配置如下:
#新增的IP映射配置
server {
listen 80;
server_name 192.168.1.10 192.168.2.10 192.168.3.10;
location /mail_api/ {
proxy_pass http://DemoBackend/; #后面的斜杠不能少,作用是不往后端传递/mail-api 这个路径
proxy_redirect off;
proxy_set_header Host mailapi.domain.com; #传递不同的host给后方节点,实现IP和域名均可以访问
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
location /other_api1/ {
proxy_pass http://DemoBackend/;
proxy_redirect off;
proxy_set_header Host otherapi1.domain.com;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
#还可以添加更多映射,通过不同的路径来映射不同的API,最后对于直接访问IP则返回403,防网络上的扫码探测
location / {
return 403;
}
}
#原有的域名映射
server {
listen 80;
server_name mailapi.domain.com;
location / {
proxy_pass http://DemoBackend;
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
server {
listen 80;
server_name otherapi1.domain.com;
location / {
proxy_pass http://DemoBackend;
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
#简单的节点配置(当这些API都用到同一个Backend时,上述代码中的proxy_set_header传递的host就起到了关键性作用!)
upstream DemoBackend {
server 192.168.10.1;
server 192.168.10.2;
ip_hash;
}
最终实现的效果就是:你要通过 IP 请求邮件 API,只要请求 http://192.168.1.1/mail_api/ 即可,而不需要开放多余端口。而且,后续要新增更多 API,只需要定义不同的二级路径即可,这些二级路径的辨识度可比端口要好得多!
Ps:正如代码中的注释,示例代码只用了一个 DemoBackend 节点配置,为的是分享另一个小技巧:当后端节点承载了多个站点而且都是监听 80 端口时(比如某些小公司同一个 IIS 服务器部署了 N 个站点),反向代理中的 proxy_set_header 参数,可以自定义传递一个 host 域名给后端节点,从而正确响应预期内容!
这段解释有点无力,还是拿实际例子举例吧!
我之前供职的公司节点用的是 IIS 服务器,前端用 Nginx 反向代理,IIS 服务器上有多个站点,站点之间部分会通过 rewrite 规则联系起来。 打个比方:比如 A 网站有个专题内容(www.a.com/zt/)是通过 IIS 伪静态映射到了 B 网站(content.b.com)。也就是访问到 http://www.a.com/zt/,其实最后是通过 A 网站映射到了 B 网站上面。 后来发现 IIS 有个伪静态 BUG,会经常奔溃,就要我用前端的 Nginx 来实现直接映射,而不再走 IIS 的 A 网站中转。 那么这个需求就正好用到了 proxy_set_header 技巧,一看就懂:
server {
listen 80;
server_name www.a.com;
location /zt/ {
proxy_pass http://ABackend; #都是相同的节点,此示例代码我就不写upstream了
proxy_redirect off;
proxy_set_header Host www.b.com; #这里就是关键性作用,传递b域名给后端IIS
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
#upstream略..
很明显,通过传递自定义域名,就可以实现通过 A 网站访问 Nginx,返回 B 网站内容,和反向代理谷歌的原理是一致的。
当然,上文为了实现 IP 和域名都可以访问,这个 proxy_set_header 设置也是必须的。说白了就是在反代过程中,对后端服务器伪装(传递)了一个自定域名,让后端响应该域名预期内容。
当然,在之前张戈博客分享的《分享几个 WordPress 本地缓存 gravatar 评论头像的方案》一文中也用到了这个技巧,感兴趣的朋友可以前往查看。
本文分享的经验,其实比较简单,主要就是通过不同路径来反代不同的目标。估计很多大拿早就用烂了吧!不过值得注意的是,通过自定义路径反代,需要注意 proxy_pass 参数后面是否需要斜杠,避免将自定义的路径传递到后端节点,导致访问 404!
- 数据结构C#版笔记--顺序表(SeqList)
- 数据结构C#版笔记--单链表(LinkList)
- 操作系统 页式存储 页与块之间的关系详解
- 数据结构C#版笔记--双向链表(DbLinkList)
- 斐波那契数列与IE9
- DateTime.ToString()输出"年/月/日 时:分:秒"的格式
- Flash在线拍摄用户头象
- win7 64位下如何折腾Tubro C 3.0
- TweenLite的又一应用:图片的拼图加载效果
- mysql创建数据表时如何判断是否已经存在?
- 温故知新:接口的隐式实现与显式实现
- 也谈枚举ToString()性能的改进
- silverlight:利用telerik中的zip类对字符串进行压缩、解压
- 索引,视图,存储过程和触发器文档
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 一次绕过waf进行xss的经历
- opencv+python制作硬核七夕礼物
- 身份验证器是如何验证我们的身份?
- 谷歌开源NLP模型可视化工具LIT,模型训练不再「黑箱」
- MongoDB 案例:Document failed validation 错误
- 利用GoogleAppsScript自动回复短信实现保号
- 用php来查询graphql
- 利用树莓派的摄像模块实现“扫码枪”
- n ../../node_modules/@storybook/channels/dist/index.d.ts:25:9 - error TS1086: An accessor cannot ...
- 要不来重新认识Spring事务?三歪又学到了
- 读者问:学完SSM,该学什么呢?
- go-zero 微服务框架介绍
- redis-cli 未找到命令的一个解决方式
- 【每日一题】42. Trapping Rain Water
- iframe跨域安全