临时接到一个需求,有一个巨大的资源池,传到阿里云的OSS上了,坑爹的是资源池内的文件全是加密的.临上线了才发现这个坑.为了安全也不能把未加密的文件对外传输.所以就得想办法实时解密.
首先想到的就是通过一个反向代理来转发,代理收到内容后,解密然后再回包.
所以首选Nginx了, 虽然我们一直用Nginx作为网关,但是我对其还处于完全不了解的状态,趁机恶补一下知识, 花了2个小时看了淘宝团队写的Nginx开发从入门到精通,最后发现,添加一个module竟然要重新编译nginx?? What the fuck!这不科学…
于是寻寻觅觅,发现了lua-nginx-module,进而发现了已经打包好的openresty.
之前接触过云风大神写的skynet游戏服务端框架,也是使用lua作为开发语言,使用lua的协程特性可以很方便的把异步代码”逻辑同步化”,不过我这次需要实现的解密功能,是属于典型的CPU密集型的应用,和同步异步没啥关系…
如果用脚本语言实现加解密算法,性能可想而知,不过还好,发现了一个叫lua-resty-nettle的加解密库,加解密算法都是native实现的,lua只是作为胶水.所以性能基本上放心了.
再加上找京东的同事考证了一下,他们内部确实也使用了这个东西,所以稳定性应该是没有问题.
技术方案定了,接下来就是写代码了.
看了一下官方文档,lua-nginx-module提供了很多钩子,可以让你通过lua代码去处理,找出我现在最需要的几个钩子:
- body_filter_by_lua
把反向代理获取到的body传入自己编写的lua代码中进行处理,需要注意的是这里传入的是一个一个”chunk”,载入一个页面,可能会分为好几次调用过来,所以我们编写的lua代码需要处理这种情况 - rewrite_by_lua_block
可以使用lua代码对url进行重写,需要用这个的原因是因为资源池以前是在windows上,之前大小写不敏感,导致很多地方的url引用不区分大小写也可以正常使用, 所以现在迁移到OSS后,大小写敏感了.为了兼容这种情况,只能把所有的文件名全部转换为小写,然后把所有转发请求的url都rewrite为小写 - header_filter_by_lua_block
可以使用lua代码重写http头.因为回包经过解密后length变了,所以需要把content_length设为空,这样浏览器就可以识别body是chunk的方式传输的,还需要把content_encoding设为nil,否则oss会根据大部分浏览器的请求,返回一个gzip压缩过的数据,为了处理方便,这里就强行把gzip给关了当然,通过这个钩子只能处理被代理方回包的http头,具体如何强行关闭gzip,后面再说.
具体配置如下:
|
|
注意这一行:
|
|
使用 proxy_set_header 功能,强行屏蔽掉浏览器gzip压缩格式的请求.所以到被代理方,也就是资源池的请求,永远是不压缩的.
因为lua代码比较长,所以为了方便把它放到一个文件里面了, 然后通过body_filter_by_lua_file 指定文件,效果和body_filter_by_lua是一样的
lua代码如下:
|
|
ngx.arg[1]表示当前数据 ngx.arg[2]表示当前数据是否是最后一个chunk.
对ngx.arg[1]赋值表示要传给最终用户(浏览器)的数据,所以处理完数据后别忘了赋值给ngx.arg[1]
因为数据不是一次性发过来的,所以需要进行拼包,如果发现某个文件过大,就不解密直接raw过去
lua代码中用到了ngx.ctx.*, 这个是lua-nginx-module提供的一个global的table,可以用来暂存任意数据.当一个完整的文件没有传送完成时,之前传送的部分就存在这里.
为了解密des,用到了一个第三方lua库,openresty提供了包管理器,可以使用以下命令安装:
|
|
还有一个需要注意的是,des解密后出来的文件总是对不上,后面会莫名其妙多出几个byte,查阅资料后发现,原来PKCS5Padding会填充长度为8的倍数,处理这个问题也很简单,把最后一个byte拿出来作为冗余数据的长度,从解密数据中删除尾部的这个长度的数据即可.
花了一天从确定技术方案到开发完成,保证了项目上线时间.还是蛮有成就感的.