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

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

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

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

java

不要依賴 AppKey 做爲數據安全的保障

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

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

所以千萬不要認爲:json

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

而是要明白:
AppKey 是公開的,須要結合 LeanCloud 提供的其餘安全措施來保障應用數據的安全性!api

使用 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 \
 -H "X-LC-Id: XEComQ0KWPD7r3RDQYrSCaPV-gzGzoHsz" \
 -H "X-LC-Key: WFzTQJdI39iR6JJO9ytR290i" \
 -H "Content-Type: application/json" \
 -d '{"content": "Hello, world!","ACL": {"*": {"read": true, "write": false}, "58ce7493ac502e00589e3105": {"write": true}}}' \
 https://api.leancloud.cn/1.1/classes/Post複製代碼

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

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

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

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

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

雲引擎在進行數據讀寫時默認會使用 MasterKey 來進行鑑權,所以會擁有最高權限。不過此時由於代碼運行在服務器上,不會被第三者篡改,所以只要合理使用,開發者沒必要擔憂安全性問題。

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

@engine.before_save('Comment')
def before_comment_save(comment):
    post = comment.get('post')
    post.fetch()    # post 是個 Pointer,所以須要先調用 `fetch()` 來獲取數據
    if post.get('isClosed'):
        raise LeanEngineError('post is closed.')
    # 除此以外,還能夠對 Comment 作一些其餘檢查,好比是否有禁止出現的詞彙複製代碼

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

除了 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')
def after_comment_save(comment):
    post = comment.get('post')
    post.increment('commentsCount', 1)
    post.save()複製代碼

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

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

這時能夠經過雲函數進行這種操做:

@engine.define('godApi')
def god_api(**params):
    do_what_you_want_with_input_data(params) 
    return what_you_want()複製代碼

事實上,將對應的數據權限所有關閉,只經過雲函數來操做數據,使用 LeanCloud 進行應用開發,與本身搭建服務器來編寫接口具備一樣的數據安全性保障。

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

好比只對單一數據條目進行增刪改查,而且只針對用戶進行權限校驗,徹底能夠利用 Class / Object 權限設置來保障數據的安全性與統一性。除此以外的狀況,則能夠考慮將對應 Class 的讀或寫權限關閉,利用雲引擎來進行安全性檢查和數據校驗。

相關文章
相關標籤/搜索