大家好,歡迎來到第二十七天!在 Day 26,我們成功地將「省錢拍拍」打包成了可以發布的 Android AAB 檔案。我們的 App 離正式上架只差一步之遙。但在我們將它交給全世界的使用者之前,必須先處理一個極度重要的安全問題。
回想在 Day 11,為了開發方便,我們將 Firestore 資料庫設定為「測試模式」。這個模式的規則如下:
allow read, write: if request.time < timestamp.date(2025, 10, 25);
這意味著,在一個月的寬限期內,任何人,無論是否登入,只要知道我們專案的 ID,就可以對我們的資料庫進行任意的讀、寫、刪除!這就像開了一家銀行,卻沒有鎖上金庫的大門,是絕對不能接受的。
今天,我們將扮演「資安工程師」的角色,學習 Firestore 安全規則的基本語法,並為我們的資料庫部署一條最核心、最重要的安全規則:「使用者只能讀寫屬於自己的資料」。
Firestore 安全規則不是寫在 Flutter 程式碼裡的,而是直接部署在 Firebase 雲端上。它就像一個站在你資料庫門口的警衛,每一筆來自 App 的讀寫請求,都必須先經過它的盤問和批准。
...add(...)
或 ...get()
時,就會發送一個包含使用者身份 (request.auth
) 和目標路徑 (request.path
) 等資訊的請求。true
,請求通過;如果回傳 false
或沒有任何規則匹配,請求將被拒絕。rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// 匹配 users 集合下的所有文件
match /users/{userId} {
// 允許使用者讀取或更新自己的個人資料文件 (例如 email, 暱稱)
// 條件:請求者的 UID 必須等於路徑上的 userId
allow read, update: if request.auth != null && request.auth.uid == userId;
// 允許使用者建立自己的個人資料文件
// 條件:請求者的 UID 必須等於文件 ID,且文件尚不存在
allow create: if request.auth != null && request.auth.uid == userId;
// 匹配 users/{userId} 底下的 transactions 子集合
match /transactions/{transactionId} {
// 允許對 transactions 子集合進行所有操作 (CRUD)
// 條件:請求者的 UID 必須等於這個子集合所屬的 user 的 ID
allow write: if request.auth != null && request.auth.uid == userId;
}
}
}
}
users
集合下任一文件的路徑。{userId}
是一個萬用字元,它會捕捉路徑中的那段字串(也就是使用者的 UID)。allow read, update
: 賦予「讀取」和「更新」的權限。if ...
: 這是條件判斷式。request.auth != null
: 檢查使用者是否已登入。request.auth.uid
: 這是發出請求的使用者的 UID,由 Firebase Authentication 自動提供。== userId
: 將請求者的 UID 與他試圖存取的路徑中的 userId 萬用字元進行比較。User A
無法讀取 User B
的資料。users/{userId}
文件底下的 transactions
子集合中的任一文件。{userId}
萬用字元。write
是 create、update、delete
的統稱,代表允許完整的寫入權限。/users/USER_A_UID/transactions/SOME_ID
時,規則會檢查發出請求的人,其 request.auth.uid
是否等於外層路徑中的 USER_A_UID
。如果是,就允許所有操作。今天我們為雲端金庫裝上了一把堅固、可靠的大鎖。我們學會了:
match
、萬用字元 {}
和 request.auth.uid
來定義權限。完成了這一步,我們才能放心地將 App 交給使用者。資料隱私與安全是任何一個負責任的 App 開發者都必須嚴肅對待的課題。
我們的 App 現在不僅功能完整,安全性也提升到了新的層級。但程式碼中還有一個小小的安全隱憂——我們的 Gemini API 金鑰。
明天,我們將處理最後一個安全議題:使用環境變數來管理我們的 API 金鑰,徹底告別將密鑰寫死在程式碼中的壞習慣。