Firebase雲端資料庫 - 初探 Realtime Database 安全性規則
前一篇提到 Firebase 的資料是以 JSON 格式儲存的 NoSQL 資料庫,如果你有接觸過 MongoDB 的話,那你對這樣的資料格式一定很熟悉,同樣都是 NoSQL的資料庫,以JSON格式儲存,至於如何才能設計出好的資料庫,這個應該是需要經驗的累積,而資料的安全性也十分重要,它不需要經驗累積,只要熟悉該產品的安全性規則就可以限制了,由於前一篇建立的資料庫預設是公開的,所以接下來看怎麼去限制權限來保護我們的資料吧。
前置準備
Rule Types
Realtime Database Rules1 具有類似 JavaScript 的語法,定義了下列四種屬性
屬性 | 型別 | 說明 |
---|---|---|
.read |
boolean | 是否允許讀取資料, true 可以讀取;false 拒絕讀取。 |
.write |
boolean | 是否允許寫入資料, true 可以寫入;false 拒絕寫入。 |
.validate |
string | 定義正確格式化後的值是什麼樣子,是否有子屬性、資料的類型等。 |
.indexOn |
string | 指定索引來支持排序和查詢,在資料量大的時候才會感覺效能的差異。 |
授權方式
以筆者接觸資料庫多年的經驗,資料的安全性是以最小化權限授權為原則,所以這裡也是一樣,授權給不同的用戶擁有不同的資料權限,同時資料也可以讓多人擁有相同的權限,前一篇我們建立以測試模式啟動的資料庫,其安全性是公開的,安全性規則的 JSON 如下:
{
"rules": {
".read": true,
".write": true
}
}
它的 .read
和 .write
都是 true
,因此只要知道此資料庫位置的任何人都可以讀取與修改資料,這風險還真大啊! Firebase 也很貼心的警示這樣的設計很危險,如下圖。
我們可以點擊規則模擬工具來模擬看看。
- 模擬類型: read
- 已驗證:關閉
- 點擊執行
- 得到此結果已允許模擬「read」,點擊詳細資料可以看到驗證的值是 null,讀取成功,因為
.read
的屬性值是true
。同理,把屬性值都設定為false
時,所有人都無法讀取與修改了。
那安全性規則要怎麼設計才能做到保護的作用?這個就取決於需求了,下面我們舉幾個例子吧!
只授權有認證過的使用者
首先先來設計資料如下圖
接下來到規則設計 JSON 為「只要有認證過的使用者都可以讀取與修改資料」,這個的權限其實開很大,陌生的人登入就可以進行讀取與修改,意思是只要註冊為用戶就可以,安全性規則的 JSON 如下:
{
"rules": {
".read": "auth != null",
".write": "auth != null"
}
}
當你確認發佈後, Firebase 一樣很貼心的警示你這樣的設計不夠完善,如下圖。
我們可以點擊規則模擬工具來模擬看看。
- 模擬類型: read
- 已驗證:關閉
- 點擊執行
- 得到此結果已拒絕模擬「read」,點擊詳細資料可以看到驗證的值是 null,已拒絕讀取,因為
.read
的屬性值是auth != null
,意思就是要登入後才可以讀取。
接下來測試已驗證的結果,這次來測試 set 修改資料。
- 模擬類型: set
- 位置: /Demo ,就是前面我們設計的資料,各位會發現在 Demo 應該有上一層的 aiotfarm,但規則設定的是最上層,整個資料庫!所以可以了解規則是含蓋了更深層的路徑。
- 資料: 將 Demo 節點下的
status
屬性設定為true
,這裡只要屬性值有值即可,只是測試可不可以寫入。{ "status": "true" }
- 已驗證:開啟。
- 供應商:都可以,因為驗證的條件不含這個。
- UID:只要有值就可以,但必須先登入進來有認證過才可。
- 點擊執行
- 得到此結果已允許模擬「set」,點擊詳細資料可以看到驗證的值是有值,結果是「寫入成功」,因為
.write
的屬性值是auth != null
,意思就是要登入後才可以寫入。
不過這種的設計就如警示一樣「不夠完善」,任何通過驗證的使用者都能竊取、修改或刪除資料庫中的資料。
特定授權
即然 auth != null
的設定方式不夠完善,那要怎麼設定才完善?筆者還是認為專案需求決定最小化權限的設定,那現在我們就來設計相對安全的安性性規則,先來看看它的 JSON 如何設計。
{
"rules": {
".read": false,
".write": false,
"aiotfarm": {
"Demo":{
"$uid": {
".read": "$uid === auth.uid",
".write": "$uid === auth.uid"
}
}
}
}
}
這個不難理解,就是把所有的使用者預設都是不能讀取與寫入,但授權 /aiotfarm/Demo/
這個路徑允許認證的使用者讀取與寫入該路徑的資料, $uid
是系統的變數,當通過 Firebase 身份驗證後就會得到的用戶 ID 。
在測試之前,我們先看一下這次的資料設計,如下圖。
在 aiotfarm
主節點下新增了 Demo
與 devices
這二個子節點,接下來的測試是登入的使用者有 Demo
路徑的權限,但沒有 devices
的權限,接下來測試讀取已驗證的結果。
- 模擬類型: read
- 位置: /aiotfarm/devices/$uid ,當通過 Firebase 身份驗證後就會得到的用戶 ID ,筆者這裡遮蔽了自己的 ID ,各位請帶上自己的吧,可以從 #5 得知。
- 已驗證:開啟。
- 供應商:預設帶入的 Google 就可以。
- UID:當通過 Firebase 身份驗證後就會得到的用戶 ID ,把這組 ID 加入到位置的路徑。
- 點擊執行
- 得到此結果已拒絕模擬「read」,點擊詳細資料可以看到驗證的值是有 uid,結果是「已拒絕讀取」,因為預設根目錄
.read
的屬性值是false
,且並沒有指定該位置的路徑是可以讀取。
那剛才的安全性規則有設定了 /aiotfarm/Demo/
這個路徑是可以讀寫,接著測試看看吧!
- 模擬類型: read
- 位置: /aiotfarm/Demo/$uid ,這裡請特別留意大小寫是有差異的。
- 已驗證:開啟。
- 供應商:預設帶入的 Google 就可以。
- UID:當通過 Firebase 身份驗證後就會得到的用戶 ID ,把這組 ID 加入到位置的路徑。
- 點擊執行
- 得到此結果已允許模擬「read」,點擊詳細資料可以看到驗證的值是有 uid,結果是「讀取成功」,雖然預設根目錄
.read
的屬性值是false
,但下方有指定該位置的路徑是可以讀取。
定義規則順序
Firebase 的規則採用「淺層判斷至深層」2的做法,只要是存取資料,規則的 true 與 false 一律由淺層 ( root 根節點 ) 開始判斷起,以剛才的例子來說明,調整安全性規則的 JSON 語法如下:
{
"rules": {
"aiotfarm": {
"Demo":{
"$uid": {
".read": "$uid === auth.uid",
".write": false
}
}
},
".read": true,
".write": true
}
}
可以看到在根目錄就已設定為可以讀取跟寫入了,這樣另外在子節點再設定拒絕就變得多此一舉,我們來看看實際測試的結果吧。
從上圖可以看到,根本沒有走到子節點判斷,如果要針對節點設定權限,可以把根目錄的這個移除,這樣如果不寫讀寫的規則,會變成預設值 false
, false
不會影響深層,安全性規則的 JSON 語法如下:
{
"rules": {
"aiotfarm": {
"Demo":{
"$uid": {
".read": "$uid === auth.uid",
".write": "$uid === auth.uid"
}
}
}
}
}
資料驗證
不過淺層判斷至深層只適用於 .read
與 .write
, 另一個屬性 .validate
則不受這個規則的限制,只會影響自己的層級,例如上述案例的資料中,在 /aiotfarm/Demo/
下有個 status
屬性,資料的型別為布林值 (Boolean),只接受 true
或 false
,因此我們可以用 .validate
來限制,安全性規則的 JSON 語法如下:。
{
"rules": {
"aiotfarm": {
"Demo":{
"$uid": {
".read": "$uid === auth.uid",
".write": "$uid === auth.uid",
".validate": "newData.child('status').isBoolean()"
}
}
}
}
}
接著來測試寫入的結果吧。
- 模擬類型: set
- 位置: /aiotfarm/Demo/$uid ,這裡請特別留意大小寫是有差異的。
- 資料:注意屬性值只有
true
或false
。{ "status": false }
- 已驗證:開啟。
- 供應商:預設帶入的 Google 就可以。
- UID:當通過 Firebase 身份驗證後就會得到的用戶 ID ,把這組 ID 加入到位置的路徑。
- 點擊執行
- 得到此結果已允許模擬「set」,點擊詳細資料可以看到驗證的值是有 uid,結果是「寫入成功」,可以看到
.validate
驗證是成功的。
那麼來測試失敗,把 status
屬性值改為字串的 "false"
,結果如下。
定義資料庫索引
待寫。
留言