这周在项目中遇到一个问题:由于我们前端打包的时候把静态文件的.map文件也上传到了生产环境中,导致这些.map文件可以被访问下载,因此被定性为"有源码泄露的安全风险问题"。因此,需要禁用这些.map文件的访问,于是决定用Nginx添加配置来禁用,但是设置过程中发现怎么都不生效,最后经过了我的各种查找和提问,终于搞清楚了Nginx的配置中location规则的生效规律,最终也解决了问题。
问题背景
项目需求
基于上面提到的问题,这里还有一个限制条件:我们的项目中有个很多Nginx配置,我不能修改默认的配置,只能在默认配置的一个拓展配置中添加新配置,来解决.map文件可访问问题。
我们的这个默认配置关键内容如下:
nginx
server {
...
include conf.d/console*.conf.custom;
location ^~ /itsc-mobile/ {
proxy_pass http://logic.itsc_mobile/;
}
...
}
其他内容就省略了,其实就是说我不能修改这个默认文件,但是可以增加一个console*.conf.custom
文件来添加额外的配置。
我要做的就是让用户无法访问/itsc-mobile/static/js/main.45d35777.js.map
这种地址,也就是以.js.map
或者.css.map
结尾的文件,但是不能影响正常的js和css的访问,比如/itsc-mobile/static/js/main.45d35777.js
需要正常访问。
我的做法
我想到的方案是添加一个正则的规则,匹配.js.map
或者.css.map
结尾的文件就返回403,这样就可以阻止用户访问这种文件了。
于是我在额外配置文件console-ext.conf.custom中添加了如下规则:
nginx
location ~* \.(css|js)\.map$ {
return 403;
}
但是经过验证,发现这个规则根本就不生效,期初以为是我配置的位置不对,后面经过一番验证发现跟配置的位置没关系,就是正则不生效。
于是带着这个问题,我经过了一番搜索和求证,终于搞清楚了Nginx配置里面location的规则的生效规律。
location的语法
这里我根据网上搜到的一些文章,还有官方文档的介绍,自己理解后对location的的语法进行了总结。
这里只讨论下面这种由4个部分组成的语法, 这4个组成部分分别是: location关键字 + 匹配方式符号(可省略)+匹配规则+如何处理, 这个最复杂也是最常用, 我们只讨论这个。
格式大概是这样的:
nginx
location [ = | ^~|~ | ~* | ] uri { ... }
这里的匹配符号有5种:
- 无符号:也就是匹配符号为空,算是一种前缀匹配,如
location / {}
=
:表示精确匹配,如location = /static/abc.png {}
^~
: 表示优先前缀匹配,如location ^~ /static/js/ {}
~
: 表示区分大小写的正则匹配,如location ~ /static/js/ {}
~*
: 表示不区分大小写的正则匹配,如location ~* /static/js/ {}
location语法的优先级
我这里把这5种规则分成三类,分别是精确匹配、前缀匹配(无符号和优先前缀匹配),正则匹配。
精确匹配
格式如下:
nginx
location = /static/abc.png {
return 403;
}
精确匹配是要求请求地址跟匹配项完全一致才算匹配成功,所以这种匹配是优先级最高的,只要匹配成功,就不会再进行其他规则的判断,直接返回。
前缀匹配
首先是无符号的前缀匹配:
nginx
location /static/js/css/ {
return 405;
}
location /static/js/ {
return 405;
}
然后是有符号的前缀匹配,这种我称之为优先前缀匹配
nginx
location ^~ /static/js/ {
return 404;
}
这两个的优先级规律是:命中任何一个前缀匹配的话,还会继续往下去进行规则匹配,并且会从这两个规则中选择匹配到的最长的前缀作为结果,而如果长度相同,那么优先级前缀优先,此时直接返回优先前缀匹配,如果是无符号匹配的更长,则继续去匹配正则规则。
一个例子:/static/js/css/abc
这个地址同时被上面的两个前缀规则匹配,但是很明显无符号的规则可以匹配到/static/js/css/
比有符号的匹配前缀/static/js/
长,所以会选择/static/js/css/
最为临时返回规则,进一步去找正则规则。
而/static/js/abc
这个地址也同时被上面的规则匹配,但是两个都是匹配到/static/js/
,长度一样,此时有符号的优先级就更高,并且不会继续找正则匹配,直接返回。
正则匹配
格式如下:
nginx
location ~* \.(css|js)\.map$ {
return 402;
}
正则匹配的优先级相对比较低,从上面的前缀匹配也能看到,当无符号的前缀匹配到的情况下才会进行正则匹配,并且如果此时正则匹配到了,那么会取正则的结果,否则取无符号前缀匹配的结果
总结
我经过咨询和学习,对location这里的规则进行了一个总结,并且画了一个流程图来体现:
有如下结论:
- 首先,不同规则在文件中的顺序对规则的匹配不受影响,只有同类型规则的顺序至上到下进行匹配时是先匹配先生效的
- 精确匹配优先级最高,只要匹配到就立即返回,不会进行下一步匹配
- 前缀匹配中会取匹配到的最长前缀作为预选,当无符号的匹配最长时,还需要进行正则匹配,当优先前缀最长时,直接返回
- "正则匹配的优先级很低"这个说法其实是不准确的,因为当匹配能到达正则匹配这里的时候,正则匹配的优先级就是最高的,只要匹配就返回,所以真正的问题是匹配会不会到正则匹配这里
一个典型案例
有如下配置文件:
nginx
server {
listen 12080;
server_name abc.com;
access_log /var/log/nginx/test.access.log;
error_log /var/log/nginx/test.error.log;
location ~* \.png$ {
return 402;
}
location / {
return 400;
}
location /static/js/css/ {
return 405;
}
location ^~ /static/ {
return 401;
}
location ^~ /static/js/ {
return 404;
}
location = /static/abc.png {
return 403;
}
}
请写出下面的地址的返回码(可以在这个网站上面进行验证 nginx.viraptor.info/):
/static/js/css/4.png
这个地址会返回402,因为它同时被无符号前缀和有符号前缀匹配到,但是无符号前缀/static/js/css/
更长,此时就继续进行正则匹配,发现可以被正则匹配到,所以返回了正则匹配的状态码402
匹配过程如下:
/static/js/4.png
这个地址仅被两个优先前缀匹配,所以直接返回优先前缀匹配最长的/static/js/
的404
/static/js/css6/4.png
这个地址同时被两个优先级前缀匹配,所以选择最长的返回404
匹配过程如下:
这3个地址的匹配我最开始非常不理解,特别是第1个和第3个的对比,并且把这个疑惑发到了V站进行讨论,也正是这个讨论,让我理解了这里的规律。
学以致用
经过上面的学习,我知道了正则的规则生效的条件,所以我想到了解决项目上问题的方案,之前我配置的正则不生效是因为地址被优先前缀匹配到了,所以直接返回了,根本不会到正则判断这一步,所以为了使正则生效,我需要额外添加一个无符号前缀匹配才行,我的解决方案是这样的:
nginx
location ~* \.(css|js)\.map$ {
return 403;
}
location /itsc-mobile/static/js/ {
proxy_pass http://logic.itsc_mobile/;
}
我在拓展文件中添加了两个规则,第一个规则自然是正则匹配了,第二个规则是一个无符号前缀匹配规则,这个规则需要比原本的优先级前缀匹配规则长,这样才能走到这里,进而走向正则匹配。
总结
其实在项目里面经常会遇到需要配置Nginx规则的事情,而我们项目里面配置文件又多,规则根本就理不清楚,所以之前配置的时候经常也是不停试探。经过了这次的学习,我总算是理清楚了location的规则,也经过了验证是理解正确的,后续再遇到Nginx配置规则不生效或者产出冲突,就可以按照这个思路去定位解决了。