剛寫了一篇文章提到了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模塊的一個應用實例吧。
