本頁面以「建構安全性規則」和「編寫安全性規則條件」中的概念為基礎,說明如何使用 Cloud Firestore Security Rules 建立規則,允許用戶端對文件中的某些欄位執行作業,但不能對其他欄位執行作業。
有時您可能想在欄位層級 (而非文件層級) 控制文件的變更。
舉例來說,您可能想允許用戶端建立或變更文件,但不允許他們編輯文件中的特定欄位。或者,您可能希望強制用戶端一律建立包含特定一組欄位的文件。本指南說明如何使用 Cloud Firestore Security Rules 完成其中一些工作。
AI 輔助功能的 AI 提示
如需 AI 輔助,請將下方提示詞複製到 AI 程式設計代理 (例如 Android Studio 內建 Gemini、Antigravity、Claude Code、Codex 和 Cursor)。您可以自訂提示,並與代理反覆調整。如需詳細安裝資訊,請參閱 Firebase 代理技能。
Help me use Firebase in my app.
1. Make sure that **Firebase agent skills** are installed and updated. For Claude Code, Codex, or Cursor agents, help me install the Firebase plugin. For other agents, run `npx skills add firebase/agent-skills`. Always try to use **Firebase agent skills** for Firebase-related tasks.
2. Help me write, validate, and deploy Firebase Security Rules to protect my app's Firestore data.
僅允許特定欄位的讀取權限
規則中的讀取作業是在文件層級執行。您只能擷取完整文件,無法擷取部分內容。無法擷取部分文件。單靠安全性規則,無法防止使用者讀取文件中的特定欄位。
如果想對某些使用者隱藏文件中的特定欄位,最好的做法是將這些欄位放在另一份文件中。例如,您可以考慮在 private 子集合中建立文件,如下所示:
/employees/{emp_id}
name: "Alice Hamilton",
department: 461,
start_date: <timestamp>
/employees/{emp_id}/private/finances
salary: 80000,
bonus_mult: 1.25,
perf_review: 4.2
接著,您可以新增安全性規則,為這兩個集合設定不同的存取層級。在本例中,我們使用自訂驗證宣告,規定只有自訂驗證宣告 role 等於 Finance 的使用者,才能查看員工的財務資訊。
service cloud.firestore {
match /databases/{database}/documents {
// Allow any logged in user to view the public employee data
match /employees/{emp_id} {
allow read: if request.resource.auth != null
// Allow only users with the custom auth claim of "Finance" to view
// the employee's financial data
match /private/finances {
allow read: if request.resource.auth &&
request.resource.auth.token.role == 'Finance'
}
}
}
}
限制建立文件時的欄位
規則不含結構定義,也就是說,資料庫層級不會限制文件包含的欄位。雖然這種彈性可讓開發作業更輕鬆,但有時您會希望確保用戶端只能建立包含特定欄位或不含其他欄位的文件。
您可以檢查 request.resource.data 物件的 keys 方法,建立這些規則。這份清單列出用戶端嘗試在這份新文件中寫入的所有欄位。將這組欄位與 hasOnly() 或 hasAny() 等函式結合,即可加入邏輯,限制使用者可新增至規則的文件類型。
要求新文件必須包含特定欄位
假設您想確保在 restaurant 集合中建立的所有文件,都至少包含 name、location 和 city 欄位。方法是在新文件的鍵清單中呼叫 hasAll()。
service cloud.firestore {
match /databases/{database}/documents {
// Allow the user to create a document only if that document contains a name
// location, and city field
match /restaurant/{restId} {
allow create: if request.resource.data.keys().hasAll(['name', 'location', 'city']);
}
}
}
這樣一來,餐廳也可以使用其他欄位建立,但可確保用戶端建立的所有文件至少包含這三個欄位。
禁止在新文件中使用特定欄位
同樣地,您也可以使用 hasAny() 檢查禁止欄位清單,防止用戶端建立含有特定欄位的文件。如果文件包含任何這類欄位,這個方法會評估為 true,因此您可能需要否定結果,才能禁止特定欄位。
舉例來說,在以下範例中,用戶端不得建立包含 average_score 或 rating_count 欄位的文件,因為這些欄位稍後會由伺服器呼叫新增。
service cloud.firestore {
match /databases/{database}/documents {
// Allow the user to create a document only if that document does *not*
// contain an average_score or rating_count field.
match /restaurant/{restId} {
allow create: if (!request.resource.data.keys().hasAny(
['average_score', 'rating_count']));
}
}
}
為新文件建立欄位許可清單
您可能不想禁止新文件中的特定欄位,而是想建立一份清單,只列出新文件中明確允許的欄位。接著,您可以使用 hasOnly() 函式,確保建立的新文件只包含這些欄位 (或這些欄位的子集),不含其他欄位。
service cloud.firestore {
match /databases/{database}/documents {
// Allow the user to create a document only if that document doesn't contain
// any fields besides the ones listed below.
match /restaurant/{restId} {
allow create: if (request.resource.data.keys().hasOnly(
['name', 'location', 'city', 'address', 'hours', 'cuisine']));
}
}
}
結合必填和選填欄位
您可以在安全性規則中結合 hasAll 和 hasOnly 作業,要求某些欄位並允許其他欄位。舉例來說,這個範例要求所有新文件都必須包含 name、location 和 city 欄位,並可選擇是否允許 address、hours 和 cuisine 欄位。
service cloud.firestore {
match /databases/{database}/documents {
// Allow the user to create a document only if that document has a name,
// location, and city field, and optionally address, hours, or cuisine field
match /restaurant/{restId} {
allow create: if (request.resource.data.keys().hasAll(['name', 'location', 'city'])) &&
(request.resource.data.keys().hasOnly(
['name', 'location', 'city', 'address', 'hours', 'cuisine']));
}
}
}
在實際情境中,您可能需要將這個邏輯移至輔助函式,避免重複程式碼,並更輕鬆地將選填和必填欄位合併至單一清單,如下所示:
service cloud.firestore {
match /databases/{database}/documents {
function verifyFields(required, optional) {
let allAllowedFields = required.concat(optional);
return request.resource.data.keys().hasAll(required) &&
request.resource.data.keys().hasOnly(allAllowedFields);
}
match /restaurant/{restId} {
allow create: if verifyFields(['name', 'location', 'city'],
['address', 'hours', 'cuisine']);
}
}
}
更新時限制欄位
常見的安全做法是只允許用戶端編輯部分欄位,而不允許編輯其他欄位。您無法單靠查看上一節所述的 request.resource.data.keys() 清單來完成這項操作,因為這份清單代表更新後的完整文件,因此會包含用戶端未變更的欄位。
不過,如果您使用 diff() 函式,就可以將 request.resource.data 與 resource.data 物件進行比較,後者代表更新前資料庫中的文件。這會建立 mapDiff 物件,其中包含兩個不同地圖之間的所有變更。
對這個 mapDiff 呼叫 affectedKeys() 方法,即可找出在編輯中變更的一組欄位。接著,您可以使用 hasOnly() 或 hasAny() 等函式,確保這組項目包含 (或不包含) 特定項目。
防止變更某些欄位
在 affectedKeys() 產生的集合上使用 hasAny() 方法,然後否定結果,即可拒絕任何嘗試變更您不希望變更的欄位的用戶端要求。
舉例來說,您可能想允許用戶端更新餐廳資訊,但不允許變更平均分數或評論數量。
service cloud.firestore {
match /databases/{database}/documents {
match /restaurant/{restId} {
// Allow the client to update a document only if that document doesn't
// change the average_score or rating_count fields
allow update: if (!request.resource.data.diff(resource.data).affectedKeys()
.hasAny(['average_score', 'rating_count']));
}
}
}
僅允許變更特定欄位
除了指定不想變更的欄位,您也可以使用 hasOnly() 函式,指定要變更的欄位清單。一般來說,這種做法較為安全,因為根據預設,除非您在安全性規則中明確允許,否則系統會禁止寫入任何新的文件欄位。
舉例來說,您可以建立安全性規則,只允許用戶端變更 name、location、city、address、hours 和 cuisine 欄位,而不是禁止 average_score 和 rating_count 欄位。
service cloud.firestore {
match /databases/{database}/documents {
match /restaurant/{restId} {
// Allow a client to update only these 6 fields in a document
allow update: if (request.resource.data.diff(resource.data).affectedKeys()
.hasOnly(['name', 'location', 'city', 'address', 'hours', 'cuisine']));
}
}
}
這表示如果未來在應用程式的某個疊代中,餐廳文件包含 telephone 欄位,則嘗試編輯該欄位會失敗,直到您返回並將該欄位新增至安全性規則中的 hasOnly() 清單。
強制執行欄位類型
規則無結構定義的另一個影響是,資料庫層級不會強制規定特定欄位可儲存的資料類型。不過,您可以使用 is 運算子,在安全性規則中強制執行這項操作。
舉例來說,下列安全性規則會強制規定評論的 score 欄位必須是整數,headline、content 和 author_name 欄位是字串,而 review_date 是時間戳記。
service cloud.firestore {
match /databases/{database}/documents {
match /restaurant/{restId} {
// Restaurant rules go here...
match /review/{reviewId} {
allow create: if (request.resource.data.score is int &&
request.resource.data.headline is string &&
request.resource.data.content is string &&
request.resource.data.author_name is string &&
request.resource.data.review_date is timestamp
);
}
}
}
}
is 運算子的有效資料類型為 bool、bytes、float、int、list、latlng、number、path、map、string 和 timestamp。is 運算子也支援 constraint、duration、set 和 map_diff 資料類型,但由於這些類型是由安全性規則語言本身產生,而非由用戶端產生,因此在大多數實際應用中很少使用。
list 和 map 資料類型不支援泛型或類型引數。換句話說,您可以使用安全性規則強制要求特定欄位包含清單或對應,但無法強制要求欄位包含所有整數或所有字串的清單。
同樣地,您可以使用安全性規則,強制執行清單或對應中特定項目的類型值 (分別使用括號標記法或鍵名稱),但無法一次強制執行對應或清單中所有成員的資料類型。
舉例來說,下列規則可確保文件中的 tags 欄位包含清單,且第一個項目是字串。此外,這項規則也會確保 product 欄位包含對應,而對應中包含字串形式的產品名稱和整數形式的數量。
service cloud.firestore {
match /databases/{database}/documents {
match /orders/{orderId} {
allow create: if request.resource.data.tags is list &&
request.resource.data.tags[0] is string &&
request.resource.data.product is map &&
request.resource.data.product.name is string &&
request.resource.data.product.quantity is int
}
}
}
}
建立及更新文件時,都必須強制執行欄位類型。因此,建議您建立輔助函式,以便在安全性規則的建立和更新部分呼叫。
service cloud.firestore {
match /databases/{database}/documents {
function reviewFieldsAreValidTypes(docData) {
return docData.score is int &&
docData.headline is string &&
docData.content is string &&
docData.author_name is string &&
docData.review_date is timestamp;
}
match /restaurant/{restId} {
// Restaurant rules go here...
match /review/{reviewId} {
allow create: if reviewFieldsAreValidTypes(request.resource.data) &&
// Other rules may go here
allow update: if reviewFieldsAreValidTypes(request.resource.data) &&
// Other rules may go here
}
}
}
}
強制執行選填欄位的類型
請務必記住,在不存在 foo 的文件上呼叫 request.resource.data.foo 會導致錯誤,因此任何發出該呼叫的安全規則都會拒絕要求。如要處理這種情況,請在 request.resource.data 上使用 get 方法。get 方法可讓您為從地圖擷取的欄位提供預設引數 (如果該欄位不存在)。
舉例來說,如果評論文件也包含選用的 photo_url 欄位和選用的 tags 欄位,而您想驗證這些欄位分別是字串和清單,可以將 reviewFieldsAreValidTypes 函式改寫成類似下列內容:
function reviewFieldsAreValidTypes(docData) {
return docData.score is int &&
docData.headline is string &&
docData.content is string &&
docData.author_name is string &&
docData.review_date is timestamp &&
docData.get('photo_url', '') is string &&
docData.get('tags', []) is list;
}
這會拒絕存在 tags 但不是清單的文件,同時仍允許不含 tags (或 photo_url) 欄位的文件。
不允許部分寫入
關於 Cloud Firestore Security Rules 的最後一點是,它們會允許用戶端變更文件,或拒絕整個編輯。您無法建立安全性規則,在同一作業中接受寫入文件中的某些欄位,同時拒絕其他欄位。