最近公司在進行網站的SEO優化,將所有主要頁面的URL統一更改為新的格式,其中重要的一項改變是將所有URL的標識符統一為ID,例如過去我們的一個用戶的公共頁面URL是這樣的
https://www.example.com/user/[:username]
而更新后的格式則變成
https://www.example.com/user/[:user_id]
在看到設計文檔(Design Doc)的同時,我本能就對這個這樣的URL形式產生了一絲疑慮,但又說不上為什么。
我們的用戶信息是存在MySQL數據庫的表單中,user_id是一個整數類型的主鍵(Primary Key),同時也是自增(Auto Increment)字段。
從安全角度來說,使用user_id來作為標識符的URL設計並沒有什么問題,但是這樣會有別的問題,什么樣的問題呢?
暴露商業數據!
暴露的數據1:總體用戶數量
有心人如果想要知道公司現在的總體用戶數量,那么很簡單,只需要注冊一個新用戶,然后查看自己的公共主頁,這樣新用戶的user_id就會暴露出來,而這個user_id就約等於網站現在的總體用戶數量(有一部分被刪除的用戶除外)
暴露的數據2:用戶增速
如果說暴露總量還不是最危險的,用戶增速則是更加重大的信息暴露。有心人知道URL中的user_id規律之后,可以通過做很簡單的實驗
1. 創建一個新用戶,查看URL中的用戶ID。 https://www.example.com/user/1000000
2. 隔一段時間之后(一周/月),再創建一個新用戶,查看URL中的用戶ID。https://www.example.com/user/1005000
二者相減(1005000-1000000)就可以輕易得到這段時間內的用戶增長量。甚至可以在外部用很小的代價獲得非常精確的用戶增長曲線。
而這樣的數據,對於大部分公司來說,都是非常核心的數據,尤其在公司未上市之前,應該盡量避免此類數據外流。
這樣的情況十分普遍,因為在學習Web開發的初級教程中,幾乎都會將數據庫主鍵ID用來作為URL標識符,這樣從URl解析主鍵到后台服務使用主鍵查詢數據庫,可以非常簡潔直觀。但是在實際的生產環境中,這樣的做法往往並不可取。
那么有什么樣的方法可以避免使用數據庫ID呢?
1. 使用UUID —— UUID是亂序的字符串,所以無法用來預測數據,我們可以用多種方法來設計UUID。如果你使用了MongoDB等NoSQL的存儲,那么恭喜你獲得了天然的UUID,每一個ObjectID都是你可以使用的UUID。如果你的數據依然離不開關系數據庫,那么可以考慮在數據庫中擴增字段來生成UUID並存儲,同時提供基於UUID的查詢接口。
2. 加密自增ID —— 在系統已經到處使用自增ID作為內部查詢的關鍵詞的情況下,往往遷移到UUID會帶來很大的工程開銷,並且為了支持已有的URL訪問,還需要維護兩套不同的邏輯。在這種情況下一種相對簡便的方法是在服務器端在生成URL的時候對於ID進行加密,這樣在客戶端看到的便是加密后的ID。當客戶端進行請求的時候,服務器端再進行解密。這樣的方法看似簡單,其實其中也多需要注意的地方,比如加密的秘鑰一旦泄露,便失去了意義。並且有在網頁端動態生成URL的需求,也有不小的挑戰。