May the #! be with you

用nginx lua模块做m3u8防盗链

| Stephen Zhang | Comments

刚写了一篇文章提到了nginx的lua模块,顺手再写一篇介绍一下我们的一个具体的应用。

简单的说一下什么是防盗链,大家可能常见一些网站对图片防盗链,比如我如果在这篇 文章里嵌一个来自百度的图片,很有可能是显示不出来的,防盗链的目的主要有两点吧, 一是版权,有些图片版权方可能要求图片不能在其他网站上显示,另外就是服务器带宽资源, 如果不做防盗链,那就成了一个免费图床了。我们公司提供m3u8格式的直播源,m3u8 是通过纯http传输的,不支持权限验证。有许多网站(访问量还挺大)直接在他们站内 嵌入了播放器,播放地址写的我们的m3u8地址,这给我们造成了很大的带宽损失,但 有没有给我们带来收益(视频上没有我们的logo等,因此用户并不知道这些视频的来源), 因此我们需要对m3u8做防盗链设置。

m3u8是一个纯http的协议,因此防盗链很难做。网上许多图片防盗链都是通过http_referer 来做的,要求http_referer必须来自某些指定的url。然而m3u8不同,因为是播放器请求, 所以不一定会带有http_referer。于是我们采用了另外一种方式,其流程如下:

  • 用户请求播放页,播放页中含有m3u8地址,我们给出的地址为: http://our.cdn.domain/path/to/playlist.m3u8?key={somekey}
  • 播放器请求上述链接,我们的服务器验证这个somekey是否合法,如果合法则返回m3u8内容, 否则返回403。

这里的somekey可以看做跟软件序列号一样,可以通过一个算法批量产生,并且能够自验证有效性。 每个用户来请求播放地址时,我们会发放一个不同的、有效的key,而在cdn服务器上的验证逻辑 是这样的:

  • 当一个用户请求时,将该用户的{ip-ua}对作为其标示(这不完全可靠,但不影响后面的判断),记为uid吧;
  • 使用key验证算法验证key的有效性,如果key无效,返回验证失败;
  • _uid = memc:get(key)
    • 如果_uid为空,则表示该用户第一次来访,将这个key记录到memcached中:memc:set(key, uid, 3600),并返回验证成功
    • 如果_uid不为空,且_uid == uid,则表示该key上一次也是这个用户使用的,返回验证成功
    • 如果_uid不为空,且_uid != uid,则表示该key一小时内上一次是别人使用的,那就表示这个人盗链了,验证失败

通过上述这个算法,即可很好的满足我们的防盗链需求。其他网站的站长无法直接将某一个播放url 嵌入他们的网站,必须嵌入我们的播放页才行。当然不排除他们的站长很牛逼,每次从后台请求我们的播放页, 分析出播放地址,再展示给他们的用户,不过这样我们也能很容易的识别出来自该网站后台的请求从而封禁。

对于上述这个需求,我们在CDN上就需要有一段代码来实现上述验证逻辑。 如果没有lua的话,恐怕很难实现,需要写一个nginx的模块,这开发成本就有点高了,且nginx模块 使用C开发的,我们没有长期用C开发的同事,因此也很难的短期内写出相对bug free的代码。 有了lua模块,这份工作就很简单了,可以用我上一篇博客中的方法,写一个小的HTTP服务器来做这件事, 然后用用lua去访问一下这个HTTP服务,询问验证结果。不过上面这段逻辑其实很简单,用不着单独写一个HTTP服务, 其部署成本还是很高的,因此我们直接用lua实现了上述逻辑,100行左右的代码就搞定了,具体代码就不贴出来了。

文章写完了发现,呵呵,好像跟lua没有啥关系啊,只不过使用lua实现的而已…… 嗯,当初在网上搜了一圈m3u8防盗链的技术,基本上没有找到什么(被分享出来的)方案,因此我们自己设计了这个方案, 文章写出来也算是给大家分享一下,抛砖引玉,提个思路吧。另外这也算是lua模块的一个应用实例吧。

Comments