用NginScript做个带权限的文件分发服务

CDN的带权限分发对控制成本、防止盗链非常有用,那在Nginx上怎么实现相同的带权限分发呢?

之前有个带权限验证的CDN服务,没有同步开通海外CDN,对海外一直用一台海外的服务器提供文件服务。为了实现和CDN一样的权限算法,是用Node做的服务器。JS做权限验证逻辑当然是非常轻松的,但是Node做文件服务就有点力不从心了,读文件流,写http流,零拷贝不知道怎么实现,似乎还有定位不出来的内存泄漏之类的问题,服务过几天就莫名其妙的会死一下,还要写个监控把它拉起来。

其实一直知道Nginx+Lua可能是最佳解决方案了,不过想想专门去学习一个在其他场合好像也没啥用处的语言……还是让Node服务去重启吧。

直到发现了NginScript(NJS),这就很适合用javascript写着这种无状态的计算服务嵌入到文件分发流程中了。

安装最新的Nginx+NJS,服务器是CentOS 8.2,选择了相应的rpm包:

1
2
rpm -Uvh https://nginx.org/packages/centos/8/x86_64/RPMS/nginx-1.20.1-1.el8.ngx.x86_64.rpm
rpm -Uvh https://nginx.org/packages/centos/8/x86_64/RPMS/nginx-module-njs-1.20.1%2B0.7.0-1.el8.ngx.x86_64.rpm

文件一直是在COS里面管理,通过COS分发到CDN的,最简单的让Nginx分发COS的方式是用COSFS把bucket挂载到/mnt目录下。不过8.0+的CentOS系统用yum安装COSFS的时候不兼容,要下载编译:

1
2
3
4
5
6
7
8
 yum install automake gcc-c++ git libcurl-devel libxml2-devel fuse-devel make openssl-devel fuse
git clone https://github.com.cnpmjs.org/tencentyun/cosfs /usr/cosfs
cd /usr/cosfs
./autogen.sh
./configure
make
sudo make install
cosfs --version

github访问经常连不上,换了加速站点 github.com.cnpmjs.org 才顺利下载到。

因为要对文件访问做权限,所以挂载了COS的目录somewhere不能直接在Nginx里对外开放,要对内开放,只允许经过验证的请求用内部重定向的方式下载:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
load_module modules/ngx_http_js_module.so;
......
http {
......
js_import http.js;
......

location /somewhere{
internal;
root /mnt/somewhere/;
}

location / {
js_content http.redirect;
}

这样外部来的http请求直接到http.js里面去鉴权通过了才能去访问到somewhere:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
function redirect(r) {
for(var i in headers){
r.headersOut[i]=headers[i];
}

if(r.method == "OPTIONS"){
r.return(200, "ok");
}else{
var pathname = r.uri;
if(/\.((jpe?g)|(png)|(gif)|(ico)|(html?))$/.test(r.uri)){
r.internalRedirect('/somewhere'+r.uri);
}else if(r.args && r.args.data){
var checkResult = checkAuth(decodeURIComponent(r.args.data));
if(checkResult){
r.internalRedirect('/somewhere'+checkResult);
}else{
r.internalRedirect('/somewhere/404.html');
}
}else{
r.internalRedirect('/somewhere/404.html');
}
}
}

export default {redirect};

鉴权算法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function checkAuth(fullpath){
const crypto = require('crypto'),
rFullPath = /https:\/\/www.myDomainName.com([^\.]*\.mp3)\?sign=([\d]+)\-([0-9a-z]+)\-([0-9]+)\-([0-9a-f]+)/ ;
cdnkey = "my-cdn-key...................";
if(!rFullPath.test(fullpath)){
return false
} ;
var t=fullpath.match(rFullPath);
var path=t[1],timestamp=t[2],rand=t[3],uid=t[4],md5=t[5]
var timeDiff = Date.now()-timestamp;
if(timeDiff>3600000){
return false
}
var data = path+"-"+timestamp+"-"+rand+"-"+uid+"-"+cdnkey;
if (md5 == crypto.createHash('md5').update(data).digest("hex")){
return decodeURI(path)
};
return false;
}

天下JS是一家,代码逻辑大部分从Node服务程序里面抠出来就直接可以用了。

就这样用NginScript简单复刻了腾讯云CDN的带校验静态文件分发。