Firebase 教學 - Firestore Rules 安全規則

Rules ( 安全規則 ) 可以讓 firestore 具有多一層的安全保護,firestore 的規則除了可以指定哪些資料可以讀取或寫入,也能更進一步的區分讀取與寫入的細部規則 ( 單一檔案、全部檔案、創建新檔案、更新檔案或刪除檔案...等 ),透過安全規則的設置,資料庫的使用也更靈活更安全。

延伸閱讀:

基本寫法

所有 Firestore 的安全規則都包含了matchallow的語法,match負責識別哪些文件與資料夾,allow則負責相對應的權限,又分為read( 讀取 ) 和write( 寫入 ) 兩種,read可再細分為getlistwrtie也可再細分為createupdatedelete,如果單純指定readwrtie的權限,則細分的規則就會全部按照父層的定義進行,透過這些規則的交互搭配,就能提高資料庫的彈性與安全性。

類型 說明
(read) get 知道文件路徑就可讀取
(read) list 讀取所有文件時就可被讀取
(write) create 建立文件
(write) update 更新文件
(wrtie) delete 刪除文件

基本的寫法如下,最外層的兩行不需要更動,因它代表這整份資料庫的路徑,,內層透過match篩選指定的路徑,並由 if 條件判斷 true 或 false 來決定讀取或寫入的權限。

service cloud.firestore {                  // 不需更動
  match /databases/{database}/documents {  // 不需更動
    match /路徑 {
      allow read, write: if 條件判斷;
    }
  }
}

當我們建立了一個 firestore 的資料庫,預設的規則會像下面這個樣子,內層的match透過「萬用字元」指定資料庫下「所有的文件」都可以寫入和讀取,也就等同於只要知道資料庫路徑的所有人都可以操作這個資料庫。

service cloud.firestore {                  // 不需更動
  match /databases/{database}/documents {  // 不需更動
    match /{document=**} {
      allow read, write;
    }
  }
}

定義規則

以下規則將使用「Firebase 教學 - Firestore 安裝、寫入和讀取」裡的專案作為範例,專案結構如下圖為一個包含五種水果名字文件的 fruit 集合。

如果我們把規則寫成match /fruit/{document},因為只有單一個集合,那麼它的效果幾乎等同於match /{document=**},使用模擬工具測試可以看到,針對 fruit 裡的 orange 文件,可以允許寫入資料。

service cloud.firestore {
  match /databases/{database}/documents {
    match /fruit/{document} {
      allow read, write;
    }
  }
}

如果 allow 後方不加入 if 條件判斷,預設就會 true,如果不寫 allow,預設則是 false,舉例來說,下面這兩段規則的結果相同,只能讀取資料庫,而不能寫入資料庫。

service cloud.firestore {
  match /databases/{database}/documents {
    match /fruit/{document} {
      allow read;
    }
  }
}

service cloud.firestore {
  match /databases/{database}/documents {
    match /fruit/{document} {
      allow read;
      allow write: if false;
    }
  }
}

規則的分層與交集

撰寫規則不限有幾個match,但若匹配到同一個文件,只要任何一個allow的判斷回傳 true,就擁有權限可以允許讀取或寫入,以下方的規則來說,雖然我們定義了 /fruit/apple 不可以寫入,但因為 /fruit/{document} 回傳了 true,所以 apple 文件仍然保有寫入的權限。

service cloud.firestore {
  match /databases/{database}/documents {
    match /fruit/apple {
      allow read;
      allow write: if false;
    }
    match /fruit/{document} {
      allow read, write;
    }
  }
}

延續上面所提到的「萬用字元」( 被{}包起來的變數 ),規則裡的{document}表示「該層」所有的文件,如果指令更深層的文件規則,不同層的規則無法互相覆蓋,舉例來說,下方的規則雖然把 /fruit/{document} 定義為可以寫入,但因更深層 /fruit/{document}/{col}/{doc} 的規則定義為不能寫入,因此就無法存入資料。

service cloud.firestore {
  match /databases/{database}/documents {
    match /fruit/{document} {
      allow read, write;
    }
    match /fruit/{document}/{col}/{doc} {
      allow read;
      allow write: if false;
    }
  }
}

第一層 apple 可以寫入資料。

apple 的下一層則無法寫入資料。

不過如果我們把萬用字元改成{document=**},表示該層以下所有的文件,也因此後方的判斷就會無效,變成可以寫入資料了。

簡單實現資料的保護

最基本保護資料的方法有兩種,一種是不能修改已有的檔案,一種就是透過簡單的使用者認證來保護,首先看到第一種「不能修改已有的檔案」,在 firestore 裡要實作還滿簡單的,只要把 read 和 write 再細分規範,就能實現對應的保護,下面的規則會限制不能更新與刪除已存在的檔案,但仍然可以新建檔案

service cloud.firestore {
  match /databases/{database}/documents {
    match /fruit/{document=**} {
      allow read, create;
      allow delete: if false;
      allow update: if false;
    }
  }
}

至於下面這段規則,表示「如果知道該文件的路徑」就能讀取該文件,但若要讀取整份文件則會被規則拒絕

service cloud.firestore {
  match /databases/{database}/documents {
    match /fruit/{document=**} {
      allow get;
      allow list: if false;
      allow create;
      allow delete: if false;
      allow update: if false;
    }
  }
}

而我們也可以單純透過資料本身的屬性來定義,下方的規則定義了「如果資料有 visibility 為 public 才可以讀取」。

service cloud.firestore {
  match /databases/{database}/documents {
    match /fruit/{document=**} {
      allow read: if resource.data.visibility == 'public';
    }
  }
}

規則也可以強制使用者登入後才能讀取或寫入,下方的規則使用了request.auth.uid的方法,取得註冊用戶的 uid,如果沒有 uid 則禁止讀取或寫入

註冊使用者的方法和 RealTime Database 一模一樣,只需把資料庫的部分換成 Firestore 即可,請參考:Firebase 教學 - 簡單的使用者註冊功能

service cloud.firestore {
  match /databases/{database}/documents {
    match /fruit/{document=**} {
      allow read, write: if request.auth.uid != null;
    }
  }
}

當然我們也可以把規則寫得更完整,下方的規則可以指定使用者僅能讀取或寫入屬於自己的文件

service cloud.firestore {
  match /databases/{database}/documents {
    match /fruit/{document=**} {
      allow read, update, delete: if request.auth.uid == resource.data.author_id;
      allow create: if request.auth.uid != null;
    }
  }
}

小結

整體來說,我覺得 Firestore 的規則比 RealTime Database 的規則還要有彈性,實作起來在理解上也差不多,本篇文章所介紹的規則應該可以滿足大多數的使用情境,不過如果有更複雜的流程,就得在規則上多加琢磨了。

參考:Cloud Firestore Rules

有興趣瞧瞧其他新文章嗎?