Firebase Summit에서 발표된 모든 내용을 살펴보고 Firebase로 앱을 빠르게 개발하고 안심하고 앱을 실행하는 방법을 알아보세요. 자세히 알아보기

데이터를 안전하게 쿼리

이 페이지는 Cloud Firestore 보안 규칙이 쿼리와 상호 작용하는 방식을 설명하기 위해 보안 규칙 구조화 및 보안 규칙에 대한 조건 작성 의 개념을 기반으로 합니다. 보안 규칙이 작성할 수 있는 쿼리에 어떤 영향을 미치는지 자세히 살펴보고 쿼리가 보안 규칙과 동일한 제약 조건을 사용하도록 하는 방법을 설명합니다. 이 페이지에서는 limitorderBy 같은 쿼리 속성을 기반으로 쿼리를 허용하거나 거부하는 보안 규칙을 작성하는 방법도 설명합니다.

규칙은 필터가 아닙니다.

문서를 검색하기 위한 쿼리를 작성할 때 보안 규칙은 필터가 아니라는 점을 명심하십시오. 쿼리는 전부 아니면 전무입니다. 시간과 리소스를 절약하기 위해 Cloud Firestore는 모든 문서의 실제 필드 값 대신 잠재적인 결과 집합에 대해 쿼리를 평가합니다. 쿼리가 클라이언트에게 읽기 권한이 없는 문서를 잠재적으로 반환할 수 있는 경우 전체 요청이 실패합니다.

쿼리 및 보안 규칙

아래 예에서 알 수 있듯이 보안 규칙의 제약 조건에 맞게 쿼리를 작성해야 합니다.

auth.uid 를 기반으로 문서 보안 및 쿼리

다음 예는 보안 규칙으로 보호되는 문서를 검색하는 쿼리를 작성하는 방법을 보여줍니다. story 문서 모음을 포함하는 데이터베이스를 고려하십시오.

/stories/{storyid}

{
  title: "A Great Story",
  content: "Once upon a time...",
  author: "some_auth_id",
  published: false
}

titlecontent 필드 외에도 각 문서는 액세스 제어에 사용할 authorpublished 필드를 저장합니다. 이 예에서는 앱이 Firebase 인증 을 사용하여 author 필드를 문서를 만든 사용자의 UID로 설정한다고 가정합니다. Firebase 인증은 보안 규칙의 request.auth 변수도 채웁니다.

다음 보안 규칙은 request.authresource.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()

필드를 기반으로 문서 보안 및 쿼리

쿼리와 규칙 간의 상호 작용을 추가로 보여주기 위해 아래 보안 규칙은 stories 컬렉션에 대한 읽기 액세스를 확장하여 모든 사용자가 published 필드가 true 로 설정된 story 문서를 읽을 수 있도록 합니다.

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.publishedtrue 임을 보장합니다. 따라서 이 쿼리는 보안 규칙을 만족하며 데이터를 읽을 수 있습니다.

inarray-contains-any 쿼리

규칙 세트에 대해 in 또는 array-contains-any 쿼리 절을 평가할 때 Cloud Firestore는 각 비교 값을 개별적으로 평가합니다. 각 비교 값은 보안 규칙 제약 조건을 충족해야 합니다. 예를 들어 다음 규칙의 경우:

match /mydocuments/{doc} {
  allow read: if resource.data.x > 5;
}

유효 하지 않음: 쿼리는 모든 잠재적 문서에 대해 x > 5 임을 보장하지 않습니다.

// This query will fail
db.collection("mydocuments").where("x", "in", [1, 3, 6, 42, 99]).get()

유효 : 쿼리는 모든 잠재적 문서에 대해 x > 5 를 보장합니다.

db.collection("mydocuments").where("x", "in", [6, 42, 99, 105, 200]).get()

쿼리에 대한 제약 조건 평가

보안 규칙은 제약 조건에 따라 쿼리를 수락하거나 거부할 수도 있습니다. request.query 변수에는 쿼리의 limit , offsetorderBy 속성이 포함됩니다. 예를 들어 보안 규칙은 검색되는 문서의 최대 수를 특정 범위로 제한하지 않는 쿼리를 거부할 수 있습니다.

allow list: if request.query.limit <= 10;

다음 규칙 집합은 쿼리에 적용된 제약 조건을 평가하는 보안 규칙을 작성하는 방법을 보여줍니다. 이 예에서는 다음 변경 사항으로 이전 stories 규칙 세트를 확장합니다.

  • 규칙 세트는 읽기 규칙을 getlist 규칙으로 분리합니다.
  • 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를 가진 모든 컬렉션으로 구성된 컬렉션 그룹에서 결과를 검색할 수 있습니다. 이 섹션에서는 보안 규칙을 사용하여 컬렉션 그룹 쿼리를 보호하는 방법을 설명합니다.

컬렉션 그룹을 기반으로 문서 보안 및 쿼리

보안 규칙에서 컬렉션 그룹에 대한 규칙을 작성하여 컬렉션 그룹 쿼리를 명시적으로 허용해야 합니다.

  1. rules_version = '2'; 규칙 집합의 첫 번째 줄입니다. 컬렉션 그룹 쿼리에는 보안 규칙 버전 2의 새로운 재귀 와일드카드 {name=**} 동작이 필요합니다.
  2. 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 컬렉션 그룹에 대한 읽기 또는 나열 규칙을 작성하여 이 쿼리를 허용해야 합니다.

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;

    }
  }
}

그러나 이러한 규칙은 계층 구조에 관계없이 posts ID가 있는 모든 컬렉션에 적용됩니다. 예를 들어 이러한 규칙은 다음 모든 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
    }
  }
}

다음 단계