Firebase(Android)/Firestore

[Firestore] 6편 - 보안 규칙

잘할수있을거야 2021. 11. 26. 19:16

Firebase 데이터베이스 공통 보안 규칙 가이드의 Firestore 스닙셋 가이드와

Firestore 보안 규칙 가이드 부분으로 나뉘어져 있어 내용이 뒤죽박죽이다.

 

Firebase 보안 규칙 이해하기

https://firebase.google.com/docs/rules/rules-language?authuser=0 

 

보안 규칙 언어  |  Firebase Documentation

Catch up on everthing we announced at this year's Firebase Summit. Learn more 의견 보내기 보안 규칙 언어 Firebase 보안 규칙은 다양한 복잡성과 세분화 범위를 지원하는 유연하고 강력한 커스텀 언어를 활용합니다

firebase.google.com

 

Firestore 보안 규칙

https://firebase.google.com/docs/firestore/security/rules-structure

 

Cloud Firestore 보안 규칙 구성  |  Firebase Documentation

Catch up on everthing we announced at this year's Firebase Summit. Learn more 의견 보내기 Cloud Firestore 보안 규칙 구성 Cloud Firestore 보안 규칙을 통해 데이터베이스의 문서 및 컬렉션 액세스를 제어할 수 있습니다.

firebase.google.com

 

Cloud Firestore Rules Reference( 페이지의 왼쪽 목록에서 확인가능 )

https://firebase.google.com/docs/reference/rules/rules.firestore.Request

 

인터페이스 : 요청  |  Firebase

Test SDK for Cloud Functions for Firebase

firebase.google.com

 

참고 블로그

https://gist.github.com/Dohyunwoo/b8370f208619c7f44a2a13fb390e1514

 

firebase security-rules 정리

firebase security-rules 정리. GitHub Gist: instantly share code, notes, and snippets.

gist.github.com


보안 규칙 syntax

rules_version = '2';
service <<name>> {
  // Match the resource path.
  match <<path>> {
    // Allow the request if the following conditions are true.
    allow <<methods>> : if <<condition>>
  }
}

 

rules_version 선언하지 않으면 규칙이 v1 엔진(버전1)을 사용하여 평가

 

  • service <<name>> { }

보안 규칙을 적용시킬 Firebase 제품 또는 서비스.

서비스,제품이름을 지정하여 다른 서비스,제품의 보안 규칙과 충돌을 방지함

service cloud.firestore { ... } //Firestore 보안 규칙

 

  • match <<path>> { }

문서 경로를 지정하는 match 블록

 

<<path>> - service가 파이어스토어인 경우에는 보안 규칙들이 적용될 Firestore 문서 경로

컬렉션이 아닌 문서를 가리켜야함

match 컬렉션/문서 { ... }

 

  • allow <<methods>> : if <<condition>>

match블록의 path에 해당하는 Firestore 문서에 적용될 규칙(allow 표현식)들

match블록은 하나이상의 allow문이 포함되어야함

 

<<methods>> - get, list, create, update, delete 와 간편 메서드 read, write

read- get, list

write- create, update, delete 

 

<<condition>> - allow표현식의 조건. 

match 컬렉션/문서 {
    allow 메서드 : if 조건;
}

조건이 true면 메서드에 해당하는 작업이 문서에 허용됨

 

  • function 

보안 규칙이 복잡, 재사용성 높으면 함수 선언하여 활용


1. <<service>>

allow 표현식이 포함된 match블록이 하나 이상 존재해야함

 

2. match 블록

하나 이상의 중첩된 match블록

또는 하나 이상의 allow표현식

또는 하나 이상의 function 선언이 필요

중첩된 내부 match 블록의 경로는 상위 match 블록의 경로를 기준으로 한 상대경로가 됨

 

<<path>>

특정 문서를 가리킬 수도 있고, 와일드카드 { } 를 사용하여 컬렉션에 포함된 모든 문서를 나타낼 수 있음

 

 

 

<<condition>>

request, resource 변수를 사용할 수 있음

클라이언트에서 CollectionReference#getDocument("/컬렉션1/문서1") 메서드 호출(요청) 가정

-> request.path 는 /컬렉션1/문서1


보안 규칙 동작 원리

 

클라이언트에서 Firestore api를 통해 요청하면

-> match블록들과 요청에 해당하는 문서의 매치작업 시작

-> 완전 매치에 해당하는 allow만 평가됨 

 

  • match

 

1. 완전 매치

요청경로와 match블록의 경로가 완전히 일치 -> 해당 match문의 내부 allow들이 평가됨

완전 매치가 되었다고 해서 해당 match블록의 allow들만 평가되고 끝나는것이 아니라, 보안 규칙의 마지막까지 완전 match를 찾아 동일한 과정을 반복함

 

2. 부분 매치

요청경로와 match블록의 경로가 부분일치

->  match블록 내부로 들어감. 완전일치가 아니기 때문에 allow표현식은 평가되지 않고, 완전 매치 match블록만 찾음

 


  • <<path>>의 와일드 카드 변수  -> { 변수이름지정 }

 

match 컬렉션/{ var } { 내부 }

변수 var은 match블록 내부에서 사용가능

 

Single-segment wildcard

{ 이름 } -  하나의 / 경로를 대응시킬 수 있는 와일드카드 변수.

보안 규칙 api의 String으로 접근가능

 

Recursive wildcard

{ 이름=** } - 여러개의 / 경로를 대응시킬 수 있는 와일드카드 변수.

=**으로 선언.

보안 규칙 api의 path object로 접근가능

 

// Given request.path == /example/hello/nested/path the following
// declarations indicate whether they are a partial or complete match and
// the value of any variables visible within the scope.
service firebase.storage {
  // Partial match.
  match /example/{singleSegment} {   // `singleSegment` == 'hello'
    allow write;                     // Write rule not evaluated.
    // Complete match.
    match /nested/path {             // `singleSegment` visible in scope.
      allow read;                    // Read rule is evaluated.
    }
  }
  // Complete match.
  match /example/{multiSegment=**} { // `multiSegment` == /hello/nested/path
    allow read;                      // Read rule is evaluated.
  }
}

 

 

 

첫 match블록에서 부분 매치 -> singleSegment의 값이 hello로 설정됨-> 내부로 들어가 완전 매치를 찾음

-> 완전 매치가 아니라 allow문 평가는 하지 않은채로 완전 매치 match블록만 찾음

-> 내부 match블록이 완전 매치 -> 내부의 allow표현식들 순서대로 평가

-> 보안 규칙의 끝이 아니므로 끝까지 완전 매치 match블록을 찾음

-> 마지막 match블록 또한 완전 매치 -> 내부의 allow 표현식들 평가


 

Recursive wildcard  버전 1, 버전 2 차이

https://firebase.google.com/docs/rules/rules-behavior?authuser=0#version-1 

 

버전1

재귀 와일드카드가 하나 이상의 경로 항목과 일치해야함. (빈 경로와 일치할 수 없음)

match 문당 최대 1개의 재귀 와일드 카드를 사용가능

재귀 와일드 카드는 match 블록의 마지막에만 가능.

 

버전2

재귀 와일드 카드가 0개 이상의 경로 항목과 일치. (빈 경로와 일치가능)

match 문의 어디에나 재귀 와일드 카드를 배치할 수 있습니다


  • allow

match 블록은 하나 이상의 allow 문을 포함.

하나의 allow문에 여러메서드 적용가능

메서드에 해당하는 접근작업이 허용되려면 조건식이 true를 만족해야함

조건식을 생략하고 allow문 작성가능

 

service firebase.storage {
  // Allow the requestor to read or delete any resource on a path under the
  // user directory.
  match /users/{userId}/{anyUserFile=**} {
    allow read, delete: if request.auth != null && request.auth.uid == userId;
  }

  // Allow the requestor to create or update their own images.
  // When 'request.method' == 'delete' this rule and the one matching
  // any path under the user directory would both match and the `delete`
  // would be permitted.

  match /users/{userId}/images/{imageId} {
    // Whether to permit the request depends on the logical OR of all
    // matched rules. This means that even if this rule did not explicitly
    // allow the 'delete' the earlier rule would have.
    allow write: if request.auth != null && request.auth.uid == userId && imageId.matches('*.png');
  }
}

Cloud Storage규칙 예제라서 match문의 path가 Firestore와 조금 다르다( Firestore의 문서는 항상 짝수 경로 )

 

  • 같은 match문(외부,내부)에서 읽기 메서드가 중복되면 안된다.
  • 같은 path의 쓰기 메서드가 중복되면 안된다.

규칙 실패 예제

service bad.example {
  match /rules/with/overlapping/methods {
    //Firebase인증된 사용자만 read가능
    allow read: if request.auth != null;

    match another/subpath {
      //외부 match의 read, 내부 match의 get의 읽기 메서드 중첩 //에러
      allow get: if request.auth != null && request.auth.uid == "me";
      //동일한 path의 쓰기 메서드 write,create가 중첩 //에러
      allow write: if request.auth != null;
      allow create: if request.auth != null && request.auth.uid == "me";
    }
  }
}

 

 

  • function

1. return문 하나만 포함 가능. 루프,로직 포함 불가

2. 다른 함수 호출 가능. 재귀호출 불가. 호출 스택 깊이 최대 20

3. 함수가 선언된 위치에서 사용할 수 있는 변수는 모두 사용가능

4. 규칙 버전 v2에서 함수는 let 키워드를 사용하여 변수를 정의할 수 있습니다. 함수는 최대 10개의 let 바인딩을 가질 수 있지만 반환 구문으로 끝나야 합니다.

 

service cloud.firestore {
  match /databases/{database}/documents {
    // True if the user is signed in or the requested data is 'public'
    function signedInOrPublic() {
      return request.auth.uid != null || resource.data.visibility == 'public';
    }

    match /cities/{city} {
      allow read, write: if signedInOrPublic();
    }

    match /users/{user} {
      allow read, write: if signedInOrPublic();
    }
  }
}

해당 문서 리소스의 data프로퍼티를 통해 문서의 데이터를 Map으로 리턴 후

문서 데이터의 visibility key(미리 정의된 필드)에 해당하는 value값과 'public' 비교

 

  • 조건에 사용할 수 있는 resource, request 변수

 rules.firestore의 프로퍼티는 service cloud.firestore 스코프 내부에서 사용가능

-> rules.firestore의 request, resource프로퍼티를 조건에서 request, resource로 사용가능

데이터를 write할 때 write할 데이터와 기존 문서에 존재하는 데이터를 비교하여 update할지 말지 정해야 하는 경우가 있다.

 

rules.firestore.Request의 resource 프로퍼티 - write요청시만 존재, write작업후의 문서 상태 변수

 

( write작업후 population 필드의 값 > 0) and ( write작업후 name 필드의 값 == write작업전 name 필드의 값) 이면 업데이트, 만족안하면 철회

service cloud.firestore {
  match /databases/{database}/documents {
    // Make sure all cities have a positive population and
    // the name is not changed
    match /cities/{city} {
      allow update: if request.resource.data.population > 0
                    && request.resource.data.name == resource.data.name;
    }
  }
}

문서수정시 name 필드의 값을 모르고 바꾸는 경우 update가 되는 것을 방지할 수 있다.


보안 규칙에서 get()  exists() 함수를 사용하여 수신된 요청을 데이터베이스의 다른 문서와 비교할 수 있습니다

 

rules.firestore의 get, exists메서드

get(rules.Path) return rules.Resource - 경로에 해당하는 문서 리턴

exists(rules.Path) return rules.Boolean - 경로에 문서 존재 유무 리턴

getAfter(rules.Path) return rules.Resource - 현재 요청을 반영한 상태의 문서를 리턴. 

 

 

rules.Path 의 경로에 $(변수)사용가능 //${변수}아님 주의

service cloud.firestore {
  match /databases/{database}/documents {
    match /cities/{city} {
      // Make sure a 'users' document exists for the requesting user before
      // allowing any writes to the 'cities' collection
      allow create: if request.auth != null && exists(/databases/$(database)/documents/users/$(request.auth.uid))

      // Allow the user to delete cities if their user document has the
      // 'admin' field set to 'true'
      allow delete: if request.auth != null && get(/databases/$(database)/documents/users/$(request.auth.uid)).data.admin == true
    }
  }
}

 


추가 function 내용 

 

a function defined within the service cloud.firestore scope has access to the resource variable and built-in functions such as get() and exists().

 

A function is defined with the function keyword and takes zero or more arguments.