콘솔로 이동

안전하게 데이터 쿼리

이 페이지에서는 보안 규칙 구조화보안 규칙 조건 작성의 개념을 바탕으로 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 인증을 사용하여 문서를 만든 사용자의 UID로 author 필드를 설정한다고 가정합니다. 또한 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.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.uid == resource.data.author;
      // Only story authors can write
      allow write: if request.auth.uid == resource.data.author;
    }
  }
}

게시된 페이지의 쿼리에는 보안 규칙과 동일한 제약조건을 포함해야 합니다.

db.collection("stories").where("published", "==", true).get()

쿼리 제약조건 .where("published", "==", true)resource.data.published가 모든 결과에 대해 true라고 보장합니다. 따라서 이 쿼리는 보안 규칙을 충족하며 데이터를 읽을 수 있습니다.

쿼리의 제약조건 평가

또한 보안 규칙은 제약조건을 기준으로 쿼리를 허용하거나 거부할 수 있습니다. request.query 변수에는 쿼리의 limit, offset, orderBy 속성이 포함됩니다. 예를 들어 보안 규칙은 가져오는 최대 문서 수를 특정 범위로 제한하지 않는 쿼리를 거부할 수 있습니다.

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 authors can write to a story
      allow write: if request.auth.uid == resource.data.author;
    }

  }
}

다음 단계