이 페이지에서는 보안 규칙 구조화 및 보안 규칙 조건 작성의 개념을 바탕으로 Cloud Firestore Security Rules에서 쿼리를 활용하는 방법을 보여줍니다. 작성할 수 있는 쿼리에 보안 규칙이 어떤 영향을 미치는지 자세히 살펴보고 쿼리에서 보안 규칙과 동일한 제약조건을 사용하게 하는 방법을 설명합니다. 또한 limit
및 orderBy
와 같은 쿼리 속성을 기준으로 쿼리를 허용 또는 거부하도록 보안 규칙을 작성하는 방법도 설명합니다.
규칙과 필터의 차이
쿼리를 작성하여 문서를 가져올 때 보안 규칙은 필터가 아니므로 전부 쿼리하거나 전혀 쿼리하지 않는다는 점에 유의해야 합니다. 시간과 리소스를 절약하기 위해 Cloud Firestore에서 모든 문서의 실제 필드 값 대신 잠재적인 결과 세트로 쿼리를 평가합니다. 쿼리가 클라이언트에 읽기 권한이 없는 문서를 반환할 수도 있는 경우에 전체 요청이 실패합니다.
쿼리 및 보안 규칙
아래 예시에서 볼 수 있듯이 보안 규칙의 제약조건에 맞게 쿼리를 작성해야 합니다.
auth.uid
를 기반으로 문서 보호 및 쿼리
다음 예시는 쿼리를 작성하여 보안 규칙으로 보호하는 문서를 가져오는 방법을 보여줍니다. story
문서의 컬렉션이 포함된 데이터베이스를 예로 들어보겠습니다.
/stories/{storyid}
{
title: "A Great Story",
content: "Once upon a time...",
author: "some_auth_id",
published: false
}
title
및 content
필드 외에도 각 문서가 액세스 제어에 사용할 author
및 published
필드를 저장합니다. 이 예시에서는 앱이 Firebase 인증을 사용하여 author
필드를 문서 생성자 UID로 설정한다고 가정합니다. 또한 Firebase 인증이 보안 규칙의 request.auth
변수를 채웁니다.
다음 보안 규칙은 request.auth
및 resource.data
변수를 사용하여 각 story
의 읽기 및 쓰기 액세스를 해당 작성자로 제한합니다.
service cloud.firestore {
match /databases/{database}/documents {
match /stories/{storyid} {
// Only the authenticated user who authored the document can read or write
allow read, write: if request.auth != null && request.auth.uid == resource.data.author;
}
}
}
앱에 자신이 작성한 story
문서의 목록을 사용자에게 표시하는 페이지가 있다고 가정해 보겠습니다. 다음 쿼리를 사용하여 이 페이지를 채울 수 있을 것이라 생각할 수도 있지만 이 쿼리에 보안 규칙과 동일한 제약조건이 없으므로 쿼리가 실패합니다.
무효: 쿼리 제약조건이 보안 규칙 제약조건과 일치하지 않습니다.
// This query will fail
db.collection("stories").get()
현재 사용자가 실제로 모든 story
문서의 작성자인 경우에도 쿼리가 실패합니다. 이러한 동작이 발생하는 이유는 Cloud Firestore가 보안 규칙을 적용할 때 데이터베이스에 있는 문서의 실제 속성이 아니라 잠재적인 결과 세트로 쿼리를 평가하기 때문입니다. 쿼리에 보안 규칙을 위반하는 문서가 포함될 수도 있는 경우에 쿼리가 실패합니다.
반면에 다음 쿼리는 성공하는데, 그 이유는 보안 규칙과 동일한 제약조건을 author
필드에 포함하기 때문입니다.
유효: 쿼리 제약조건이 보안 규칙 제약조건과 일치합니다.
var user = firebase.auth().currentUser;
db.collection("stories").where("author", "==", user.uid).get()
필드를 기준으로 문서 보안 적용 및 쿼리
쿼리와 규칙의 상호작용을 더 자세히 보여주기 위해 아래 보안 규칙은 모든 사용자가 published
필드가 true
로 설정된 story
문서를 읽을 수 있도록 stories
컬렉션의 읽기 액세스 권한을 확장합니다.
service cloud.firestore {
match /databases/{database}/documents {
match /stories/{storyid} {
// Anyone can read a published story; only story authors can read unpublished stories
allow read: if resource.data.published == true || (request.auth != null && request.auth.uid == resource.data.author);
// Only story authors can write
allow write: if request.auth != null && request.auth.uid == resource.data.author;
}
}
}
게시된 페이지의 쿼리에는 보안 규칙과 동일한 제약조건을 포함해야 합니다.
db.collection("stories").where("published", "==", true).get()
쿼리 제약조건 .where("published", "==", true)
는 resource.data.published
가 모든 결과에 대해 true
라고 보장합니다. 따라서 이 쿼리는 보안 규칙을 충족하며 데이터를 읽을 수 있습니다.
쿼리 OR
회
규칙 집합에 대해 논리적 OR
쿼리(or
, in
, array-contains-any
)를 평가할 때 Cloud Firestore는 각 비교 값을 개별적으로 평가합니다. 각 비교 값은 보안 규칙 제약조건을 충족해야 합니다. 다음 규칙의 경우를 예로 들어보겠습니다.
match /mydocuments/{doc} {
allow read: if resource.data.x > 5;
}
무효: 쿼리가 모든 잠재적 문서에 대해 x > 5
의 조건을 보장하지 않습니다.
// These queries will fail
query(db.collection("mydocuments"),
or(where("x", "==", 1),
where("x", "==", 6)
)
)
query(db.collection("mydocuments"),
where("x", "in", [1, 3, 6, 42, 99])
)
유효: 쿼리가 모든 잠재적 문서에 대해 x > 5
의 조건을 보장합니다.
query(db.collection("mydocuments"),
or(where("x", "==", 6),
where("x", "==", 42)
)
)
query(db.collection("mydocuments"),
where("x", "in", [6, 42, 99, 105, 200])
)
쿼리의 제약조건 평가
또한 보안 규칙은 제약조건을 기준으로 쿼리를 허용하거나 거부할 수 있습니다.
request.query
변수에는 쿼리의 limit
, offset
, orderBy
속성이 포함됩니다. 예를 들어 보안 규칙은 가져오는 최대 문서 수를 특정 범위로 제한하지 않는 쿼리를 거부할 수 있습니다.
allow list: if request.query.limit <= 10;
다음 규칙 세트는 쿼리에 적용된 제약조건을 평가하는 보안 규칙을 작성하는 방법을 보여줍니다. 이 예시는 다음 변경사항을 포함하여 이전 stories
규칙 세트를 확장합니다.
- 규칙 세트가 읽기 규칙을
get
및list
규칙으로 분리합니다. get
규칙은 문서 검색을 공개 문서나 사용자가 작성한 문서로 제한합니다.list
규칙은get
과 동일한 제한사항을 쿼리에 적용합니다. 또한 쿼리 한도를 확인한 다음 한도가 없거나 10을 초과하는 쿼리를 거부합니다.- 규칙 세트에서 코드 중복을 피하기 위해
authorOrPublished()
함수를 정의합니다.
service cloud.firestore {
match /databases/{database}/documents {
match /stories/{storyid} {
// Returns `true` if the requested story is 'published'
// or the user authored the story
function authorOrPublished() {
return resource.data.published == true || request.auth.uid == resource.data.author;
}
// Deny any query not limited to 10 or fewer documents
// Anyone can query published stories
// Authors can query their unpublished stories
allow list: if request.query.limit <= 10 &&
authorOrPublished();
// Anyone can retrieve a published story
// Only a story's author can retrieve an unpublished story
allow get: if authorOrPublished();
// Only a story's author can write to a story
allow write: if request.auth.uid == resource.data.author;
}
}
}
컬렉션 그룹 쿼리 및 보안 규칙
기본적으로 쿼리는 단일 컬렉션으로 범위가 지정되며 해당 컬렉션에서만 결과를 검색합니다. 컬렉션 그룹 쿼리를 사용하면 ID가 동일한 모든 컬렉션으로 구성된 컬렉션 그룹에서 결과를 검색할 수 있습니다. 이 섹션에서는 보안 규칙을 사용하여 컬렉션 그룹 쿼리를 보호하는 방법을 설명합니다.
컬렉션 그룹을 기반으로 문서 보호 및 쿼리
보안 규칙에서 컬렉션 그룹에 대한 규칙을 작성하여 컬렉션 그룹 쿼리를 명시적으로 허용해야 합니다.
- 규칙 세트의 첫 번째 줄에
rules_version = '2';
가 있는지 확인합니다. 컬렉션 그룹 쿼리에는 보안 규칙 버전 2의 새로운 재귀 와일드 카드{name=**}
동작이 필요합니다. match /{path=**}/[COLLECTION_ID]/{doc}
를 사용하여 컬렉션 그룹의 규칙을 작성합니다.
예를 들어 posts
하위 컬렉션을 포함하는 forum
문서로 구성된 포럼을 살펴보겠습니다.
/forums/{forumid}/posts/{postid}
{
author: "some_auth_id",
authorname: "some_username",
content: "I just read a great story.",
}
이 애플리케이션에서는 소유자가 글을 편집하고 인증된 사용자가 글을 읽을 수 있도록 합니다.
service cloud.firestore {
match /databases/{database}/documents {
match /forums/{forumid}/posts/{post} {
// Only authenticated users can read
allow read: if request.auth != null;
// Only the post author can write
allow write: if request.auth != null && request.auth.uid == resource.data.author;
}
}
}
모든 인증된 사용자는 단일 포럼의 글을 검색할 수 있습니다.
db.collection("forums/technology/posts").get()
그런데 현재 사용자에게 모든 포럼에 있는 자신의 글을 표시하려면 어떻게 해야 할까요?
컬렉션 그룹 쿼리를 사용하여 모든 posts
컬렉션에서 결과를 검색할 수 있습니다.
var user = firebase.auth().currentUser;
db.collectionGroup("posts").where("author", "==", user.uid).get()
보안 규칙에서 posts
컬렉션 그룹에 대한 read 또는 list 규칙을 작성하여 이 쿼리를 허용해야 합니다.
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// Authenticated users can query the posts collection group
// Applies to collection queries, collection group queries, and
// single document retrievals
match /{path=**}/posts/{post} {
allow read: if request.auth != null;
}
match /forums/{forumid}/posts/{postid} {
// Only a post's author can write to a post
allow write: if request.auth != null && request.auth.uid == resource.data.author;
}
}
}
그러나 이러한 규칙은 계층 구조와 상관없이 ID가 posts
인 모든 컬렉션에 적용됩니다. 예를 들어 이러한 규칙은 다음과 같은 posts
컬렉션 모두에 적용됩니다.
/posts/{postid}
/forums/{forumid}/posts/{postid}
/forums/{forumid}/subforum/{subforumid}/posts/{postid}
필드를 기반으로 컬렉션 그룹 쿼리 보호
단일 컬렉션 쿼리와 마찬가지로 컬렉션 그룹 쿼리도 보안 규칙에 의해 설정된 제약조건을 충족해야 합니다. 예를 들어 위의 stories
예시에서 했던 것처럼 published
필드를 각 포럼 글에 추가할 수 있습니다.
/forums/{forumid}/posts/{postid}
{
author: "some_auth_id",
authorname: "some_username",
content: "I just read a great story.",
published: false
}
그런 다음 published
상태와 글 author
를 기반으로 posts
컬렉션 그룹에 대한 규칙을 작성할 수 있습니다.
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// Returns `true` if the requested post is 'published'
// or the user authored the post
function authorOrPublished() {
return resource.data.published == true || request.auth.uid == resource.data.author;
}
match /{path=**}/posts/{post} {
// Anyone can query published posts
// Authors can query their unpublished posts
allow list: if authorOrPublished();
// Anyone can retrieve a published post
// Authors can retrieve an unpublished post
allow get: if authorOrPublished();
}
match /forums/{forumid}/posts/{postid} {
// Only a post's author can write to a post
allow write: if request.auth.uid == resource.data.author;
}
}
}
이러한 규칙을 사용하면 웹, Apple, Android 클라이언트에서 다음 쿼리를 수행할 수 있습니다.
누구나 포럼에서 게시된 글을 검색할 수 있습니다.
db.collection("forums/technology/posts").where('published', '==', true).get()
누구나 모든 포럼에서 저자의 게시된 글을 검색할 수 있습니다.
db.collectionGroup("posts").where("author", "==", "some_auth_id").where('published', '==', true).get()
저자는 모든 포럼에서 게시된 글과 게시되지 않은 글을 모두 검색할 수 있습니다.
var user = firebase.auth().currentUser; db.collectionGroup("posts").where("author", "==", user.uid).get()
컬렉션 그룹 및 문서 경로를 기반으로 문서 보호 및 쿼리
경우에 따라 문서 경로를 기반으로 컬렉션 그룹 쿼리를 제한할 수 있습니다. 이러한 제한사항을 만들려면 필드를 기반으로 문서를 보호하고 쿼리할 때 동일한 기법을 사용하면 됩니다.
여러 주식 및 암호화폐 교환 중에 각 사용자의 트랜잭션을 추적하는 애플리케이션을 살펴보겠습니다.
/users/{userid}/exchange/{exchangeid}/transactions/{transaction}
{
amount: 100,
exchange: 'some_exchange_name',
timestamp: April 1, 2019 at 12:00:00 PM UTC-7,
user: "some_auth_id",
}
user
필드를 확인합니다. 문서의 경로를 보면 어떤 사용자가 transaction
문서를 소유하는지 알 수 있지만 다음과 같은 두 가지 작업을 수행할 수 있도록 각 transaction
문서에 이 정보가 복제됩니다.
문서 경로에 특정
/users/{userid}
가 포함된 문서로 제한된 컬렉션 그룹 쿼리를 작성합니다. 예를 들면 다음과 같습니다.var user = firebase.auth().currentUser; // Return current user's last five transactions across all exchanges db.collectionGroup("transactions").where("user", "==", user).orderBy('timestamp').limit(5)
한 사용자가 다른 사용자의
transaction
문서를 검색할 수 없도록transactions
컬렉션 그룹의 모든 쿼리에 이 제한사항을 적용합니다.
이러한 제한사항을 보안 규칙에 적용하고 user
필드에 대한 데이터 유효성 검사를 포함합니다.
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{path=**}/transactions/{transaction} {
// Authenticated users can retrieve only their own transactions
allow read: if resource.data.user == request.auth.uid;
}
match /users/{userid}/exchange/{exchangeid}/transactions/{transaction} {
// Authenticated users can write to their own transactions subcollections
// Writes must populate the user field with the correct auth id
allow write: if userid == request.auth.uid && request.data.user == request.auth.uid
}
}
}
다음 단계
- 역할 기반 액세스 제어의 자세한 예는 사용자 및 그룹에 대한 데이터 액세스 보안을 참조하세요.
- 보안 규칙 참조 읽어보기