標籤:

LeanCloud 層層加固雲端數據安全性之剖析

程序代碼中的一個邏輯 bug 可能會引發數據錯誤、界面顯示錯亂,甚至是程序崩潰。作為開發者,誰攤上這事都恨不能趕緊修掉 bug,萬不可拖到使用者們義憤填膺地來砸招牌。

可如果是一個數據安全性的問題呢?使用者還是能正常使用程序,沒人知道這個問題的存在或隱患,包括開發者自己。直到某天使用者們突然收到很多騷擾郵件,他們在應用中的數據被人惡意篡改,甚至所綁定的信用卡信息也被竊取……此刻,開發者才幡然悔悟——數據安全性問題可不像之前提到的那類 bug 那樣容易搞定,即使堵上了漏洞,使用者的信息也還是泄露了出去,被篡改的信息沒那麼容易恢復,經濟上的損失更是難以估算。

LeanCloud 作為一個將各項服務的 API 直接開放給客戶端,並擁有數以萬計開發者和海量應用的 BaaS 服務,勢必要提供針對數據安全的各種保障機制。但更重要的是,只有開發者們了解並掌握了這些數據安全機制的用法,才能根據實際需求來有效地為應用數據加上防護措施。

不要依賴 AppKey 作為數據安全的保障

訪問 LeanCloud 的 API 需要提供應用的 AppID 和 AppKey,或是 AppID 和 MasterKey。

當客戶端需要直接訪問 LeanCloud 提供的 API 時,開發者必須將 AppKey 編碼到客戶端的代碼中。這樣用戶只要通過反編譯客戶端代碼或者截獲客戶端發往伺服器的請求,即可輕易獲取 AppKey。

因此千萬不要認為:

AppKey 是對數據的一種保護措施,只要不主動泄漏,就沒人能獲得 AppKey。

而是要明白:

AppKey 是公開的,需要結合 LeanCloud 提供的其他安全措施來保障應用數據的安全性!

使用 MasterKey 來訪問 LeanCloud API 會跳過所設置的任何安全措施,方便對數據進行強制性的修改。因此請盡量保證 MasterKey 不會被泄漏,也不要將 MasterKey 放在任何客戶端代碼中,它只可出現在你的後端伺服器或者雲引擎的代碼中。

表、行、列級別的許可權設置

首先我們要理解要用到的專有名詞:Class 對應傳統資料庫中的表(Table),Object 對應表中的行記錄(Row/Record),field 對應為列或欄位。LeanCloud 可以分別給每個 Class、每個 Object 和每個 field 設置不同的訪問許可權,保證每位用戶只能訪問到被授權的數據。

Class 許可權

Class 許可權只可以在控制台中進行設置,顧名思義,對應的是整個 Class 的讀寫許可權。下圖為 Class 的許可權設置頁面:

可以控制的許可權有五種:

  • add_fields: 保存 Object 時,如果對應的欄位不存在,是否允許自動創建新的欄位。如果已經在控制台創建好 Class 的所有欄位,最好對任意用戶都關閉此許可權,防止被寫入一些臟數據,佔用存儲空間,同時拖慢數據傳輸速度。另外在控制台里也可以關閉整個應用的「創建 Class」的許可權,作用類似。
  • create: 是否允許創建新的 Object。對於需要登錄用戶或者擁有指定授權的用戶才能創建內容的場景,可以考慮根據情況設置此許可權。
  • delete: 是否允許刪除 Object。
  • find: 是否允許根據條件對 Object 進行查詢。對於一些不想讓其他用戶可以批量獲取的 Class,可以通過此許可權進行限制,這樣用戶只能通過 objectId 進行獲取。因為 objectId 默認不是自增的形式,因此如果沒有此許可權的話,很難通過遍歷 objectId 的方法進行批量獲取。
  • get: 是否允許直接通過 objectId 直接獲取 Object。可以配合 find 許可權使用。
  • update: 是否允許更新 Object 內容。

可以針對以下 4 種維度的用戶分類來指定許可權:

  • 所有用戶:所有使用 AppKey 來訪問 API 的請求,都可以進行此項許可權對應的操作。
  • 登錄用戶: 使用了當前應用下一個合法的 _User 表中的 Object 所對應的 sessionToken 進行訪問的用戶,才可以進行此項許可權對應的操作。此限制適合在只有登錄之後才允許發布和閱讀內容的場景下使用。
  • 指定用戶:必須使用某個指定的 _User 表對應的 sessionToken 進行訪問的用戶,才能進行此項許可權對應的操作。靈活性較大,可以適應各個場景。
  • 指定角色:必須使用某個指定的 _Role 表中(一系列 _User 的合集)對應的 sessionToken 進行訪問的用戶,才能進行此項許可權對應的操作。例如在論壇中需要給各個版塊的版主特殊許可權,並且版主可能會經常變更,這樣使用角色來管理許可權會比較方便。

除了以上許可權之外,每一個 field 也有特殊的許可權可以設置:

  • 只讀: 只允許客戶端讀取數據,不允許寫入。
  • 客戶端不可見:有時一個 Object 上需要保存一些關聯的數據,但是不想要將此數據展示給用戶,一種方案是通過創建一個新的 Class 來保存這一數據,然後限制客戶端讀取這個 Class 來解決,另一種方案就是直接使用此選項來解決。

Object 許可權

除了上面提到的 Class 許可權,往往還需要針對具體的 Object 來設置不同的許可權。

在 LeanCloud 的數據存儲服務中,每個 Object 都會有一列特殊的屬性 ACL (Access Control List) 來決定一個 Object 的實際讀寫許可權。如果需要針對每個 Object 設置不同的讀寫許可權,比如只允許作者修改自己發布過的文章,可以直接通過設置對應的 ACL 來實現。

可以設置在創建一個 Class 時新增加的 Object 的默認 ACL 值:

ACL 的默認值只覆蓋了 4 個常用場景,如果有特殊需求,在創建好 Class 之後直接修改其中 ACL 欄位的默認值即可。這與為一個普通的 field 設置默認值的方法相同:

ACL 欄位的值是一個 JSON 對象,最外層的 key 的值可能有如下情況:

  • * 所有人。
  • _User 用戶表中的某個 objectId 一個指定用戶。
  • role:{{roleName}} _Role 表中的 name 與 {{roleName}} 相匹配的角色所對應的所有用戶,如 role:authors
  • _owner 這是個特殊的值,在新對象創建後,它會自動變成發出創建此對象請求的用戶的 objectId。

也可以在每個 Object 上設置具體的 ACL:

當然,在控制台里給每個 Object 分別設置許可權並不現實,最好的方式是通過上面提到的設置 ACL 欄位的默認值,來確保所有新創建的 Object 都設置了合適的 ACL 值。

如果有通過默認 ACL 值依然不能滿足的情況,則可以考慮在插入數據時同時設置 ACL 許可權:

$ curl -X POST n -H "X-LC-Id: XEComQ0KWPD7r3RDQYrSCaPV-gzGzoHsz" n -H "X-LC-Key: WFzTQJdI39iR6JJO9ytR290i" n -H "Content-Type: application/json" n -d {"content": "Hello, world!","ACL": {"*": {"read": true, "write": false}, "58ce7493ac502e00589e3105": {"write": true}}} n https://api.leancloud.cn/1.1/classes/Postn

除了直接使用 REST API 創建數據之外,其他各個語言的 SDK 也都提供了相應的方法來在創建對象時設置 ACL。

另外需要注意,如果一條數據的讀寫操作對應的 Object 與 Class 都設置了對應的許可權,這時需要兩個條件都滿足,才能成功進行這次操作。而對於同一個請求,可能會有多個 ACL 設置,這時只要滿足其中一項規則就可以執行這次操作了。比如不允許任何人(*)寫入操作,但是允許特定的用戶寫入,這時這個用戶是可以執行寫入操作的。

結合雲引擎,進行高級許可權管理

上面提到的一些安全措施已經能夠覆蓋到大部分場景了,但是依然會有一些場景不能簡單地通過設置許可權來解決。典型的場景有:需要對非用戶的維度進行許可權或數據合法性校驗。比如論壇,不允許在已經關閉的帖子下添加新的回復,似乎使用上面提到過的機制很難做到這個限制。

這個時候就需要引入雲引擎來完成需求了。雲引擎是 LeanCloud 提供的另一項服務,在之上除了像傳統提供虛擬機的雲服務一樣,搭建自己的基於 HTTP 的網站服務之外,還可以結合數據存儲服務,在讀寫數據時進行校驗。

雲引擎在進行數據讀寫時默認會使用 MasterKey 來進行鑒權,因此會擁有最高許可權。不過此時因為代碼運行在伺服器上,不會被第三者篡改,因此只要合理使用,開發者不必擔心安全性問題。

回到上面的問題,如何限制用戶在已經關閉的帖子下,創建新的回復呢?可以考慮使用雲引擎的 beforeSave 這個 hook 來進行檢查:

@engine.before_save(Comment)ndef before_comment_save(comment):n post = comment.get(post)n post.fetch() # post 是個 Pointer,因此需要先調用 `fetch()` 來獲取數據n if post.get(isClosed):n raise LeanEngineError(post is closed.)n # 除此之外,還可以對 Comment 做一些其他檢查,比如是否有禁止出現的辭彙n

將這段代碼部署在雲引擎中,這樣所有請求在已經關閉的帖子下創建回復的請求時都會被拒絕掉。

除了 beforeSave hook 之外,還有 beforeUpdate、beforeDelete 等等其他種類的 hook 來在對應的時機下執行,具體可參考 文檔 。上述代碼是使用 Python 來實現的,LeanCloud 還支持使用 Node.js、PHP、Java 來編寫雲函數。

再設想一種場景,假設我們在 Post 表中使用一個叫做 commentsCount 的欄位來保存帖子的回複數量,用於客戶端展示。當然這個欄位里的數據屬於冗餘,是可以直接通過查詢 Comment 表中對應的數量來獲取的,但是這就意味著在客戶端中要展示 100 個帖子的列表時,需要再進行 100 次 API 調用來查詢回複數量,並且底層資料庫也會有響應的查詢耗時,因此為了提升應用響應性能,一般情況下開發者都會設置這個冗餘欄位。

但是這時候問題來了,我們如何確保這個欄位的值是正確的呢?當然可以讓客戶端每次發送完創建評論的請求後,再次發送一次請求去更新這個 commentsCount 欄位。但是對於直接繞過了客戶端、直接通過 curl 來發帖的用戶,他們可能不會遵守「發完貼要更新回複數量」這個義務,甚至有人會直接設置一個錯誤的值進去,破壞應用數據的完整性,或帶來其他影響,這時依然可以通過雲引擎的 afterSave hook 來完成任務:

@engine.after_save(Comment)ndef after_comment_save(comment):n post = comment.get(post)n post.increment(commentsCount, 1)n post.save()n

除了將此段代碼部署在雲引擎,還需要結合上面的介紹,將 Post 表中的 commentsCount 設置為「只讀」,這樣就不用擔心有人會忘記更新評論數,以及故意破壞數據完整性的問題了。

此外,有時我們還需要做更加複雜的許可權設置,比如在用戶查詢數據時要過濾掉某些條件(只允許查詢最近一個月創建的帖子),或者對相同 IP 發送的請求做頻率限制,或者讓某個沒有許可權的用戶臨時寫入或查詢某些數據等等。

這時可以通過雲函數進行這種操作:

@engine.define(godApi)ndef god_api(**params):n do_what_you_want_with_input_data(params) n return what_you_want()n

事實上,將對應的數據許可權全部關閉,只通過雲函數來操作數據,使用 LeanCloud 進行應用開發,與自己搭建伺服器來編寫介面具有同樣的數據安全性保障。

上面介紹了基於 LeanCloud 開發應用、保障數據安全的一系列措施,靈活性從弱到強。實際開發過程中,開發者可以根據具體情況,選擇相應的機制。

比如只對單一數據條目進行增刪改查,並且只針對用戶進行許可權校驗,完全可以利用 Class / Object 許可權設置來保障數據的安全性與統一性。除此之外的情況,則可以考慮將對應 Class 的讀或寫許可權關閉,利用雲引擎來進行安全性檢查和數據校驗。


推薦閱讀:

如何優雅地修改前同事的混亂代碼?
從 HTTP 0.9 到 QUIC
線程之間傳遞 ThreadLocal 對象
QPS 和並發:如何衡量伺服器端性能
BaaS 服務的興起減少了後端的工作量,這意味著未來大批後台程序員要失業么?

TAG:LeanCloud |