用 openresty 来做 app api 接口的验真

app 服务器端的开发中,有一个百年不变的小东西:api 访问的验真问题。因为 http 协议是无状态的,每次 app 发起请求,我都需要验证这次请求的确是由真实的、授权的 app 发起,以此来阻止不诚实用户发起的攻击、数据爬取,确保产品的安全,和用户在产品上的信息安全。

JWT(JSON Web Token)是一个 http 之上的不错的 api 验真协议,结合 https,几乎可以安全地传输各种数据。最关键是,它原理简单,没用到啥黑科技,新瓶装老酒,但实用。

openresty 是春哥做的一款神器,在 nginx 上挂了个 lua 引擎,可以用 lua 脚本指挥 nginx 做很多事情。了解 lua 的童靴都知道,lua 的执行性能堪比 C++/Java 这些编译型语言,大家在一个量级。能做到这步,已经相当感人。用 openresty 来实现 jwt 协议,做 api 访问的验真,可以降低后台系统的复杂程度,同时还能提高系统鲁棒性,防止被恶意攻击时系统雪崩。

现在已经有 openresty 的 jwt 模块,叫 lua-resty-jwt 的,还有基于它做了二次封装的 openresty-nginx-jwt

有了这个 jwt 的 openresty 模块,可以在 http server 这一层对 api 访问做验真,业务系统接收到的请求理论上都是真实的,避免调用庞大的业务系统做验真。这在收到 api 攻击的时候效果最明显,那时每次调用庞大的业务系统,加载若干组件,仅仅做了一次验真,然后就释放资源,销毁请求。简直是罪过。

话分两头说,直接上这个 jwt 模块存在一些风险。要在生产环境使用 jwt,还有一些工作必须做。

  1. secret 的策略。jwt 协议中,并没有规定 secret 怎么来的,实际操作中,不同的 secret 策略会有不同的效果。
    • 整个系统使用一个固定的 secret。不诚实用户可以注册账户,暴力穷举或内存搜索,得到 secret 明文。存在系统性风险。风险高。
    • 一台服务器使用一个 secret,secret 写到 openresty 的配置中。仍然有上述问题,还增加了做负载均衡的难度。风险高。
    • 一个用户一个 secret,openresty 根据用户信息取 secret 编解码。风险低,复杂度高。
  2. api 访问日志需要保存。特别对验真失败的访问,要能有手段及时处理日志,分析风险,对恶意用户可以屏蔽 IP 等手段规避。
  3. 经过验真的 api 访问日志结合静态资源访问日志,可以模拟真实用户的访问场景。结合 lazy load 等前端技术,甚至可以判断出用户究竟看了几页,资源加载速度快还是慢。这么好的资源,不能浪费。

接上面。第一点好解决,一个 subrequest,或者单独给 lua-resty-jwt 一个 redis 存用户的 secret,就行了。第二点和第三点说的其实是一件事情,记日志,而且是把验真失败和验真成功的日志分开记。验真失败的日志需要参与在线准实时的计算,验真成功的日志打包保存,用来做离线的大数据分析,统计用户访问指标,优化产品。

一个小组件,挺好玩儿的一件事。