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

Cloud Firestore 보안 규칙 테스트

앱을 빌드할 때 Cloud Firestore 데이터베이스에 대한 액세스를 잠그고 싶을 수 있습니다. 그러나 시작하기 전에 더 미묘한 Cloud Firestore 보안 규칙이 필요합니다. Cloud Firestore 에뮬레이터를 사용하면 앱의 일반적인 기능과 동작 을 프로토타이핑하고 테스트하는 것 외에도 Cloud Firestore 보안 규칙의 동작을 확인하는 단위 테스트를 작성할 수 있습니다.

빠른 시작

간단한 규칙이 있는 몇 가지 기본 테스트 사례의 경우 빠른 시작 샘플 을 사용해 보세요.

Cloud Firestore 보안 규칙 이해

모바일 및 웹 클라이언트 라이브러리를 사용할 때 서버리스 인증, 승인, 데이터 유효성 검사를 위한 Firebase 인증Cloud Firestore 보안 규칙 을 구현합니다.

Cloud Firestore 보안 규칙에는 다음 두 부분이 포함됩니다.

  1. 데이터베이스의 문서를 식별하는 match 문.
  2. 해당 문서에 대한 액세스를 제어하는 allow 표현식입니다.

Firebase 인증은 사용자의 자격 증명을 확인하고 사용자 기반 및 역할 기반 액세스 시스템의 기반을 제공합니다.

Cloud Firestore 모바일/웹 클라이언트 라이브러리의 모든 데이터베이스 요청은 데이터를 읽거나 쓰기 전에 보안 규칙에 따라 평가됩니다. 규칙이 지정된 문서 경로에 대한 액세스를 거부하면 전체 요청이 실패합니다.

Cloud Firestore 보안 규칙 시작하기 에서 Cloud Firestore 보안 규칙에 대해 자세히 알아보세요.

에뮬레이터 설치

Cloud Firestore 에뮬레이터를 설치하려면 Firebase CLI 를 사용하고 아래 명령어를 실행하세요.

firebase setup:emulators:firestore

에뮬레이터 실행

작업 디렉토리에서 Firebase 프로젝트를 초기화하여 시작합니다. 이것은 Firebase CLI를 사용할 때 일반적인 첫 번째 단계입니다.

firebase init

다음 명령을 사용하여 에뮬레이터를 시작합니다. 에뮬레이터는 프로세스를 종료할 때까지 실행됩니다.

firebase emulators:start --only firestore

많은 경우 에뮬레이터를 시작하고 테스트 제품군을 실행한 다음 테스트가 실행된 후 에뮬레이터를 종료하려고 합니다. emulators:exec 명령을 사용하여 이 작업을 쉽게 수행할 수 있습니다.

firebase emulators:exec --only firestore "./my-test-script.sh"

시작되면 에뮬레이터는 기본 포트(8080)에서 실행을 시도합니다. firebase.json 파일의 "emulators" 섹션을 수정하여 에뮬레이터 포트를 변경할 수 있습니다.

{
  // ...
  "emulators": {
    "firestore": {
      "port": "YOUR_PORT"
    }
  }
}

에뮬레이터를 실행하기 전에

에뮬레이터 사용을 시작하기 전에 다음 사항에 유의하십시오.

  • 에뮬레이터는 처음에 firebase.json 파일의 firestore.rules 필드에 지정된 규칙을 로드합니다. Cloud Firestore 보안 규칙이 포함된 로컬 파일의 이름을 예상하고 해당 규칙을 모든 프로젝트에 적용합니다. 로컬 파일 경로를 제공하지 않거나 아래 설명된 대로 loadFirestoreRules 메서드를 사용하는 경우 에뮬레이터는 모든 프로젝트를 열린 규칙이 있는 것으로 처리합니다.
  • 대부분의 Firebase SDK 는 에뮬레이터와 직접 작동하지만 @firebase/rules-unit-testing 라이브러리만 보안 규칙에서 모의 auth 을 지원하므로 단위 테스트가 훨씬 쉬워집니다. 또한 라이브러리는 아래 나열된 모든 데이터 지우기와 같은 몇 가지 에뮬레이터 관련 기능을 지원합니다.
  • 에뮬레이터는 또한 클라이언트 SDK를 통해 제공되는 프로덕션 Firebase 인증 토큰을 수락하고 그에 따라 규칙을 평가하므로 통합 및 수동 테스트에서 애플리케이션을 에뮬레이터에 직접 연결할 수 있습니다.

로컬 단위 테스트 실행

v9 JavaScript SDK로 로컬 단위 테스트 실행

Firebase는 버전 9 JavaScript SDK와 버전 8 SDK 모두와 함께 보안 규칙 단위 테스트 라이브러리를 배포합니다. 라이브러리 API는 상당히 다릅니다. 보다 간소화되고 에뮬레이터에 연결하는 데 필요한 설정이 적어서 프로덕션 리소스의 우발적인 사용을 안전하게 방지할 수 있는 v9 테스트 라이브러리를 권장합니다. 이전 버전과의 호환성을 위해 v8 테스트 라이브러리를 계속 사용할 수 있습니다 .

@firebase/rules-unit-testing 모듈을 사용하여 로컬에서 실행되는 에뮬레이터와 상호작용합니다. 시간 초과 또는 ECONNREFUSED 오류가 발생하면 에뮬레이터가 실제로 실행 중인지 다시 확인하십시오.

async/await 표기법을 사용할 수 있도록 최신 버전의 Node.js를 사용하는 것이 좋습니다. 테스트하려는 거의 모든 동작에는 비동기 함수가 포함되며 테스트 모듈은 Promise 기반 코드와 함께 작동하도록 설계되었습니다.

v9 Rules Unit Testing 라이브러리는 항상 에뮬레이터를 인식하고 프로덕션 리소스를 건드리지 않습니다.

v9 모듈식 import 문을 사용하여 라이브러리를 가져옵니다. 예를 들어:

import {
  assertFails,
  assertSucceeds,
  initializeTestEnvironment,
  RulesTestEnvironment,
} from "@firebase/rules-unit-testing"

// Use `const { … } = require("@firebase/rules-unit-testing")` if imports are not supported
// Or we suggest `const testing = require("@firebase/rules-unit-testing")` if necessary.

가져온 후 단위 테스트 구현에는 다음이 포함됩니다.

  • initializeTestEnvironment 호출로 RulesTestEnvironment 생성 및 구성
  • 규칙을 일시적으로 우회할 수 있는 편리한 방법 RulesTestEnvironment.withSecurityRulesDisabled 를 사용하여 규칙을 트리거하지 않고 테스트 데이터를 설정합니다.
  • RulesTestEnvironment.cleanup() 또는 RulesTestEnvironment.clearFirestore() ) 와 같은 테스트 데이터 및 환경을 정리하기 위한 호출로 테스트 스위트 및 테스트별 후크 설정.
  • RulesTestEnvironment.authenticatedContextRulesTestEnvironment.unauthenticatedContext 를 사용하여 인증 상태를 모방하는 테스트 사례를 구현합니다.

일반적인 방법 및 유틸리티 기능

v9 SDK의 에뮬레이터별 테스트 방법 도 참조하세요.

initializeTestEnvironment() => RulesTestEnvironment

이 함수는 규칙 단위 테스트를 위한 테스트 환경을 초기화합니다. 테스트 설정을 위해 이 함수를 먼저 호출하십시오. 성공적으로 실행하려면 에뮬레이터가 실행 중이어야 합니다.

이 함수는 프로젝트 ID 및 에뮬레이터 구성 설정으로 구성될 수 있는 TestEnvironmentConfig 를 정의하는 선택적 개체를 허용합니다.

let testEnv = await initializeTestEnvironment({
  projectId: "demo-project-1234",
  firestore: {
    rules: fs.readFileSync("firestore.rules", "utf8"),
  },
});

RulesTestEnvironment.authenticatedContext({ user_id: string, tokenOptions?: TokenOptions }) => RulesTestContext

이 메소드는 인증된 인증 사용자처럼 동작하는 RulesTestContext 를 생성합니다. 반환된 컨텍스트를 통해 생성된 요청에는 모의 인증 토큰이 첨부됩니다. 선택적으로 인증 토큰 페이로드에 대한 사용자 지정 클레임 또는 재정의를 정의하는 개체를 전달합니다.

테스트에서 반환된 테스트 컨텍스트 개체를 사용하여 initializeTestEnvironment 로 구성된 인스턴스를 포함하여 구성된 모든 에뮬레이터 인스턴스에 액세스합니다.

// Assuming a Firestore app and the Firestore emulator for this example
import { setDoc } from "firebase/firestore";

const alice = testEnv.authenticatedContext("alice", { … });
// Use the Firestore instance associated with this context
await assertSucceeds(setDoc(alice.firestore(), '/users/alice'), { ... });

RulesTestEnvironment.unauthenticatedContext() => RulesTestContext

이 메소드는 인증을 통해 로그인하지 않은 클라이언트처럼 동작하는 RulesTestContext 를 생성합니다. 반환된 컨텍스트를 통해 생성된 요청에는 Firebase 인증 토큰이 연결되지 않습니다.

테스트에서 반환된 테스트 컨텍스트 개체를 사용하여 initializeTestEnvironment 로 구성된 인스턴스를 포함하여 구성된 모든 에뮬레이터 인스턴스에 액세스합니다.

// Assuming a Cloud Storage app and the Storage emulator for this example
import { getStorage, ref, deleteObject } from "firebase/storage";

const alice = testEnv.unauthenticatedContext();

// Use the Cloud Storage instance associated with this context
const desertRef = ref(alice.storage(), 'images/desert.jpg');
await assertSucceeds(deleteObject(desertRef));

RulesTestEnvironment.withSecurityRulesDisabled()

보안 규칙이 비활성화된 것처럼 동작하는 컨텍스트로 테스트 설정 기능을 실행합니다.

이 메서드는 Security-Rules-bypassing 컨텍스트를 사용하고 약속을 반환하는 콜백 함수를 사용합니다. 컨텍스트는 약속이 해결/거부되면 소멸됩니다.

RulesTestEnvironment.cleanup()

이 메서드는 테스트 환경에서 생성된 모든 RulesTestContexts 를 파괴하고 기본 리소스를 정리하여 깨끗한 종료를 허용합니다.

이 방법은 어떤 식으로든 에뮬레이터의 상태를 변경하지 않습니다. 테스트 간에 데이터를 재설정하려면 애플리케이션 에뮬레이터별 데이터 지우기 방법을 사용하십시오.

assertSucceeds(pr: Promise<any>)) => Promise<any>

이것은 테스트 케이스 유틸리티 함수입니다.

이 함수는 에뮬레이터 작업을 래핑하는 제공된 약속이 보안 규칙 위반 없이 해결될 것이라고 주장합니다.

await assertSucceeds(setDoc(alice.firestore(), '/users/alice'), { ... });

assertFails(pr: Promise<any>)) => Promise<any>

이것은 테스트 케이스 유틸리티 함수입니다.

이 함수는 에뮬레이터 작업을 래핑하는 제공된 약속이 보안 규칙 위반으로 거부될 것이라고 주장합니다.

await assertFails(setDoc(alice.firestore(), '/users/bob'), { ... });

에뮬레이터별 메서드

또한 v9 SDK의 일반적인 테스트 방법 및 유틸리티 기능을 참조하십시오.

RulesTestEnvironment.clearFirestore() => Promise<void>

이 방법은 Firestore 에뮬레이터에 대해 구성된 projectId 에 속한 Firestore 데이터베이스의 데이터를 지웁니다.

RulesTestContext.firestore(settings?: Firestore.FirestoreSettings) => Firestore;

이 메소드는 이 테스트 컨텍스트에 대한 Firestore 인스턴스를 가져옵니다. 반환된 Firebase JS 클라이언트 SDK 인스턴스는 클라이언트 SDK API(v9 모듈식 또는 v9 호환)와 함께 사용할 수 있습니다.

규칙 평가 시각화

Cloud Firestore 에뮬레이터를 사용하면 Firebase 보안 규칙에 대한 평가 추적을 포함하여 Emulator Suite UI에서 클라이언트 요청을 시각화할 수 있습니다.

Firestore > 요청 탭을 열어 각 요청에 대한 자세한 평가 순서를 확인합니다.

보안 규칙 평가를 보여주는 Firestore 에뮬레이터 요청 모니터

테스트 보고서 생성

일련의 테스트를 실행한 후 각 보안 규칙이 평가된 방식을 보여주는 테스트 적용 범위 보고서에 액세스할 수 있습니다.

보고서를 가져오려면 실행 중인 에뮬레이터에서 노출된 끝점을 쿼리합니다. 브라우저 친화적인 버전의 경우 다음 URL을 사용하십시오.

http://localhost:8080/emulator/v1/projects/<project_id>:ruleCoverage.html

이렇게 하면 평가 수 및 반환된 값을 포함하여 자세한 내용을 보려면 마우스를 올려 놓을 수 있는 표현식과 하위 표현식으로 규칙이 나뉩니다. 이 데이터의 원시 JSON 버전의 경우 쿼리에 다음 URL을 포함합니다.

http://localhost:8080/emulator/v1/projects/<project_id>:ruleCoverage

에뮬레이터와 프로덕션의 차이점

  1. Cloud Firestore 프로젝트를 명시적으로 만들 필요는 없습니다. 에뮬레이터는 액세스되는 모든 인스턴스를 자동으로 생성합니다.
  2. Cloud Firestore 에뮬레이터는 일반 Firebase 인증 흐름에서 작동하지 않습니다. 대신 Firebase 테스트 SDK에서 auth 필드를 사용하는 rules-unit-testing 라이브러리에 initializeTestApp() 메서드를 제공했습니다. 이 메서드를 사용하여 만든 Firebase 핸들은 사용자가 제공한 엔터티로 성공적으로 인증된 것처럼 작동합니다. null 을 전달하면 인증되지 않은 사용자로 동작합니다(예: auth != null 규칙은 실패함).

알려진 문제 해결

Cloud Firestore 에뮬레이터를 사용할 때 다음과 같은 알려진 문제가 발생할 수 있습니다. 아래 지침에 따라 경험하고 있는 불규칙한 동작을 해결하세요. 이러한 메모는 보안 규칙 단위 테스트 라이브러리를 염두에 두고 작성되었지만 일반적인 접근 방식은 모든 Firebase SDK에 적용할 수 있습니다.

테스트 동작이 일관되지 않음

테스트 자체에 변경 사항이 없더라도 테스트가 때때로 통과하고 실패하는 경우 올바르게 순서가 지정되었는지 확인해야 할 수 있습니다. 에뮬레이터와의 대부분의 상호 작용은 비동기식이므로 모든 비동기식 코드의 순서가 올바르게 지정되었는지 다시 확인하세요. 약속을 연결하거나 await 표기법을 자유롭게 사용하여 시퀀싱을 수정할 수 있습니다.

특히 다음 비동기 작업을 검토하세요.

  • 예를 들어 initializeTestEnvironment 를 사용하여 보안 규칙을 설정합니다.
  • 예를 들어 db.collection("users").doc("alice").get() 을 사용하여 데이터 읽기 및 쓰기
  • assertSucceedsassertFails 를 포함한 운영 어설션.

테스트는 에뮬레이터를 처음 로드할 때만 통과합니다.

에뮬레이터는 상태를 저장합니다. 기록된 모든 데이터를 메모리에 저장하므로 에뮬레이터가 종료될 때마다 모든 데이터가 손실됩니다. 동일한 프로젝트 ID에 대해 여러 테스트를 실행하는 경우 각 테스트는 후속 테스트에 영향을 줄 수 있는 데이터를 생성할 수 있습니다. 다음 방법 중 하나를 사용하여 이 동작을 우회할 수 있습니다.

  • 각 테스트에 고유한 프로젝트 ID를 사용하십시오. 이 작업을 선택하면 각 테스트의 일부로 initializeTestEnvironment 를 호출해야 합니다. 규칙은 기본 프로젝트 ID에 대해서만 자동으로 로드됩니다.
  • 이전에 작성된 데이터와 상호 작용하지 않도록 테스트를 재구성합니다(예: 테스트마다 다른 컬렉션 사용).
  • 테스트 중에 작성된 모든 데이터를 삭제하십시오.

테스트 설정이 매우 복잡합니다.

테스트를 설정할 때 Cloud Firestore 보안 규칙에서 실제로 허용하지 않는 방식으로 데이터를 수정할 수 있습니다. 규칙이 테스트 설정을 복잡하게 만드는 경우 설정 단계에서 RulesTestEnvironment.withSecurityRulesDisabled 를 사용해 보십시오. 그러면 읽기 및 쓰기가 PERMISSION_DENIED 오류를 트리거하지 않습니다.

그 후에 테스트는 각각 RulesTestEnvironment.authenticatedContextunauthenticatedContext 를 사용하여 인증된 사용자 또는 인증되지 않은 사용자로 작업을 수행할 수 있습니다. 이를 통해 Cloud Firestore 보안 규칙이 다양한 사례를 올바르게 허용/거부하는지 확인할 수 있습니다.