在探討這個問題之前,先要確認一點的是,作為一名互聯網Coder,無論你是前端或者后端你都要對http請求要有一定的了解,知道http特性,要清楚的了解http里面的Request與Response是什么,知道為什么網站會存在cookie,session,驗證碼的意義和必要性。因為探討APP接口的安全性就是在探討HTTP請求的安全性;
我一般把APP接口分為三類,普通接口,表單接口,會員接口;本文重點討論會員接口
普通接口
一般為GET請求,比如獲取新聞列表 GET http://Example.com/index.php?module=news&action=list,為了防止采集或者暴力查詢,我們PC端一般做如下處理:
- 防止本站被它站file_get_contents,所以要識別user_agent,如果不是通過瀏覽器來訪問的話直接不給看。
- 如果別人通過偽造user_agent來訪問的話,就通過單位時間ip的訪問量來控制抓取方,可以寫一套算法,如果再一個ip在前后一分鍾多於多少次訪問量來處理。但是,會有一種情況,即某個小區或公司內都是使用某一個IP的外網的話,這樣搞就會自尋死路,所以還要配合瀏覽器中的cookie來處理
總結: 請求頭可以偽造,IP地址可以變更,cookie可以清空,基本上PC端是很難防這個問題的,比如淘寶,點評等大站的數據我也是經常去采的。
那APP端如何處理這個問題呢?我們可以抓點評APP的http請求包來看一下:
GET http://114.80.165.113/mapi/ugcuserfeeds.bin?filtertype=5&userid=129059048&token=73114c7e9a4485319542039cdff854d989f61e5821d306b3abf0fc9904eb51ff&start=0 HTTP/1.1
Host: 114.80.165.113
Accept: */*
pragma-appid: 351091731
pragma-newtoken: c2032338f6abf96c8e2984db1655f2bac73b88f799e49aab4a426d414f994b5f
pragma-token: 73114c7e9a4485319542039cdff854d989f61e5821d306b3abf0fc9904eb51ff
pragma-dpid: 9214560561001942797
pragma-device: 566fe5aeb75a827967fbad8356608134ba98a4a6
Proxy-Connection: keep-alive
pragma-os: MApi 1.1 (dpscope 7.5.0 appstore; iPhone 8.3 iPhone7,1; a0d0)
Accept-Language: zh-cn
network-type: wifi
User-Agent: MApi 1.1 (dpscope 7.5.0 appstore; iPhone 8.3 iPhone7,1; a0d0) Paros/3.2.13
當你直接訪問http://114.80.165.113/mapi/ugcuserfeeds.bin?filtertype=5&userid=129059048&token=73114c7e9a4485319542039cdff854d989f61e5821d306b3abf0fc9904eb51ff&start=0的時候,直接從服務器端給擋住,並返回450錯誤;

PHP的服務器一般為Apache或Nignx,我們也可以在配置項中根據與客戶端開發人員約定的一些自定義的Request頭信息,比如上面的parama-*,在服務器配置項中可以獲取到這些自定義的Request頭信息,然后根據是否為約定好的Request信息,如果不是就rewrite到450;
但是,我們通過抓包既可獲得全部請求頭信息,這時可以完全模擬請求頭信息來獲取數據;

很多APP最多到此步既可獲得該API接口的數據,而且是非常便於處理的json格式,而點評APP到此處直接返回的是一堆看上去是經過壓縮的亂碼數據:

這有點類似於PC端gzip,服務器端返回的是gzip的壓縮數據,而瀏覽器來解壓這個gzip來獲取真正的數據,然后再顯示出來;
我不知道點評的這個亂碼數據是否也是這個原理,如果是的話,不得不說真的是"棒棒噠",因為解壓的算法是發生在自己的APP端,這不僅保證了數據的安全性,而且還節省帶寬流量,加快的數據傳輸速度。具體是怎么樣做的,暫時還不得而知;
表單接口
即類似html中的from表單,主要是往服務器提交數據的。一般是post方式的http請求,主要的危險是來自於強刷HTTP請求,撐爆數據庫;在PC端我們一般通過驗證碼來解決這個問題,而在APP端,我能想到的也只有通過驗證碼的方式,只不過PC端的是把驗證碼存進session,而APP端是存進cache里面;但如果加上驗證碼的話,在用戶體驗上肯定會大打折扣,關於這一點肯定有更好的方式解決,具體怎么解決,暫時還不得而知;
會員接口
所謂會員接口,就是類似http://Example.com/index.php?module=users&action=info&user_id=333的請求,然后服務器端直接根據user_id來做相應的會員操作,這是及其危險的接口處理,等於把當前的會員系統全暴露出來了,只要對方改一下user_id既可操作所有會員對應的接口。
一般在PC端,我們是通過加密的cookie來做會員的辨識和維持會話的;但是cookie是屬於瀏覽器的本地存儲功能。APP端不能用,所以我們得通過token參數來辨識會員;而這個token該如何處理呢?
首先,先說說在做該接口加密前,我一共經歷的四個方案:
方案一
與APP端開發人員約定特定的md5組合算法,然后兩端比對一下,如果相同就allow,不相同就deny;
但是,這也是不安全的,如果APP程序被反編譯,這些約定的算法就會暴露,特別是在安卓APP中,有了算法,完全就可以模擬接口請求通過驗證;
方案二
數據庫會員表的password是帶上了隨機密竄並經過雙重加密的md5值;在用戶登錄的時候,我返回會員相應的uid和password,password雖然是明文的,別人知道也不能登錄,畢竟是經過加密的,然后每次請求接口的時候user_id=333&token=aa37e10c7137ac849eab8a2d5020568f,通過主鍵uid可以很快的找到當前uid對應的token,然后再來比對;
但是這樣想法是too yang too simple的,抓包的人雖然不能通過密文密碼來登錄該會員,然而一旦知道了這個token,除非用戶更改密碼,否則也可以一直通過這個token來操作該會員的相關接口;
方案三
通過對稱加密算法,該加密算法對uid+網站公鑰進行時效加密,在一定時效內可用。在會員登錄成功時,服務器端對該ID加密后返回給客戶端,客戶端每次請求接口的時候帶上該參數,服務器端通過解密認證;
但是這樣做,也是不安全的。因為,防外不防內,聽說這次的攜程宕機就是因為內部離職人員的惡意操作。內部不懷好意的人員如果知道相應的算法規則后,就算沒有數據庫權限,也可以通過接口來操作相關會員;
方案四
會員登錄的時候請求登錄接口,然后服務器端返回給客戶端一個token,該token生成的規則是 網站公鑰 + 當前uid + 當前時間戳 + 一段隨機數雙重加密,根據需求決定是把該token放進cache等一段時間自動失效,還是放進數據庫(如果要放進數據庫的話,單獨拎出一張表來,順便記錄用戶的登錄,登出時間),在用戶登出登錄的時候改變一下,確保該token只能在用戶人為登出登錄之間有用。
為保安全,應保證讓用戶在一段時間內自動退出;此方案配合Linux和數據庫的權限管理可以防外又防內;
其他接口開發的注意事項
- 數據格式最好使用JSON格式數據,因為JSON有較好的跨平台性。在生成JSON的時候,要注意json的兩種格式:對象(字典) 與 數組;mobile端開發語言中沒有類似PHP中的foreach不能遍歷對象,只能遍歷數組,他們對對象的操作一般都是通過鍵名去取鍵值。
- 不管是成功,還是失敗。接口必須提供明確的數據狀態信息,並且不能返回NULL,如果返回NULL的話,在IOS端會崩掉。
