실시간 데이터베이스 보안 규칙 언어의 핵심 구문 알아보기

Firebase 실시간 데이터베이스 보안 규칙을 사용하면 데이터베이스에 저장된 데이터에 대한 액세스를 제어할 수 있습니다. 유연한 규칙 구문을 사용하면 데이터베이스에 대한 모든 쓰기에서 개별 노드에 대한 작업에 이르기까지 모든 것과 일치하는 규칙을 생성할 수 있습니다.

실시간 데이터베이스 보안 규칙은 데이터베이스에 대한 선언적 구성입니다. 이는 규칙이 제품 논리와 별도로 정의됨을 의미합니다. 여기에는 여러 가지 장점이 있습니다. 클라이언트는 보안을 적용할 책임이 없고 버그가 있는 구현은 데이터를 손상시키지 않으며 아마도 가장 중요한 것은 서버와 같은 중간 심판이 세상으로부터 데이터를 보호할 필요가 없다는 것입니다.

이 항목에서는 전체 규칙 집합을 만드는 데 사용되는 실시간 데이터베이스 보안 규칙의 기본 구문과 구조에 대해 설명합니다.

보안 규칙 구조화

실시간 데이터베이스 보안 규칙은 JSON 문서에 포함된 JavaScript와 유사한 표현식으로 구성됩니다. 규칙의 구조는 데이터베이스에 저장한 데이터의 구조를 따라야 합니다.

기본 규칙은 보안할 노드 집합 , 관련된 액세스 방법 (예: 읽기, 쓰기) 및 액세스가 허용되거나 거부되는 조건 을 식별합니다. 다음 예에서 조건 은 단순한 truefalse 진술이지만 다음 주제에서는 조건을 표현하는 보다 동적인 방법을 다룰 것입니다.

따라서 예를 들어 child_node 아래의 parent_node 를 보호하려는 경우 따라야 할 일반 구문은 다음과 같습니다.

{
  "rules": {
    "parent_node": {
      "child_node": {
        ".read": <condition>,
        ".write": <condition>,
        ".validate": <condition>,
      }
    }
  }
}

이 패턴을 적용해보자. 예를 들어 메시지 목록을 추적하고 다음과 같은 데이터가 있다고 가정해 보겠습니다.

{
  "messages": {
    "message0": {
      "content": "Hello",
      "timestamp": 1405704370369
    },
    "message1": {
      "content": "Goodbye",
      "timestamp": 1405704395231
    },
    ...
  }
}

규칙도 비슷한 방식으로 구성되어야 합니다. 다음은 이 데이터 구조에 적합할 수 있는 읽기 전용 보안에 대한 일련의 규칙입니다. 이 예는 규칙이 적용되는 데이터베이스 노드와 해당 노드에서 규칙을 평가하기 위한 조건을 지정하는 방법을 보여줍니다.

{
  "rules": {
    // For requests to access the 'messages' node...
    "messages": {
      // ...and the individual wildcarded 'message' nodes beneath
      // (we'll cover wildcarding variables more a bit later)....
      "$message": {

        // For each message, allow a read operation if <condition>. In this
        // case, we specify our condition as "true", so read access is always granted.
        ".read": "true",

        // For read-only behavior, we specify that for write operations, our
        // condition is false.
        ".write": "false"
      }
    }
  }
}

기본 규칙 작업

데이터에 대해 수행되는 작업 유형에 따라 보안을 적용하는 규칙에는 .write , .read.validate 의 세 가지 유형이 있습니다. 다음은 목적을 간략하게 요약한 것입니다.

규칙 유형
.읽다 사용자가 데이터를 읽을 수 있는지 여부와 시기를 설명합니다.
.쓰다 데이터 쓰기가 허용되는지 여부와 시기를 설명합니다.
.확인 올바른 형식의 값이 어떻게 생겼는지, 자식 속성이 있는지 여부와 데이터 유형을 정의합니다.

와일드카드 캡처 변수

모든 규칙 문은 노드를 가리킵니다. 명령문은 특정 노드를 가리키거나 $ 와일드카드 캡처 변수 를 사용하여 계층 구조 수준의 노드 집합을 가리킬 수 있습니다. 이러한 캡처 변수를 사용하여 후속 규칙 문 내에서 사용할 노드 키 값을 저장합니다. 이 기술을 사용하면 더 복잡한 규칙 조건 을 작성할 수 있습니다. 이에 대해서는 다음 주제에서 더 자세히 다룰 것입니다.

{
  "rules": {
    "rooms": {
      // this rule applies to any child of /rooms/, the key for each room id
      // is stored inside $room_id variable for reference
      "$room_id": {
        "topic": {
          // the room's topic can be changed if the room id has "public" in it
          ".write": "$room_id.contains('public')"
        }
      }
    }
  }
}

동적 $ 변수는 상수 경로 이름과 병렬로 사용할 수도 있습니다. 이 예제에서는 $other 변수를 사용하여 widgettitlecolor 이외의 자식이 없도록 하는 .validate 규칙을 선언합니다. 추가 자식이 생성되는 쓰기 작업은 실패합니다.

{
  "rules": {
    "widget": {
      // a widget can have a title or color attribute
      "title": { ".validate": true },
      "color": { ".validate": true },

      // but no other child paths are allowed
      // in this case, $other means any key excluding "title" and "color"
      "$other": { ".validate": false }
    }
  }
}

읽기 및 쓰기 규칙 캐스케이드

.read.write 규칙은 하향식으로 작동하며 얕은 규칙이 더 깊은 규칙보다 우선합니다. 규칙이 특정 경로에서 읽기 또는 쓰기 권한을 부여하는 경우 해당 규칙 아래의 모든 하위 노드에도 액세스 권한을 부여합니다. 다음 구조를 고려하십시오.

{
  "rules": {
     "foo": {
        // allows read to /foo/*
        ".read": "data.child('baz').val() === true",
        "bar": {
          /* ignored, since read was allowed already */
          ".read": false
        }
     }
  }
}

이 보안 구조를 사용하면 /foo/ 에 값이 true 인 자식 baz 가 포함될 때마다 /bar/ 를 읽을 수 있습니다. ".read": false /foo/bar/ 아래의 false 규칙은 자식 경로에 의해 액세스가 취소될 수 없기 때문에 여기에서는 영향을 미치지 않습니다.

직관적이지 않은 것처럼 보일 수도 있지만 이것은 규칙 언어의 강력한 부분이며 최소한의 노력으로 매우 복잡한 액세스 권한을 구현할 수 있습니다. 이것은 이 가이드의 뒷부분에서 사용자 기반 보안에 대해 설명할 때 설명합니다.

.validate 규칙은 캐스케이드되지 않습니다. 쓰기가 허용되려면 모든 유효성 검사 규칙이 계층 구조의 모든 수준에서 충족되어야 합니다.

규칙은 필터가 아닙니다.

규칙은 원자적 방식으로 적용됩니다. 즉, 해당 위치나 상위 위치에 액세스 권한을 부여하는 규칙이 없으면 읽기 또는 쓰기 작업이 즉시 실패합니다. 영향을 받는 모든 하위 경로에 액세스할 수 있는 경우에도 상위 위치에서 읽기가 완전히 실패합니다. 다음 구조를 고려하십시오.

{
  "rules": {
    "records": {
      "rec1": {
        ".read": true
      },
      "rec2": {
        ".read": false
      }
    }
  }
}

규칙이 원자적으로 평가된다는 것을 이해하지 못하면 /records/ 경로를 가져오면 rec1 은 반환하지만 rec2 는 반환하지 않는 것처럼 보일 수 있습니다. 그러나 실제 결과는 오류입니다.

자바스크립트
var db = firebase.database();
db.ref("records").once("value", function(snap) {
  // success method is not called
}, function(err) {
  // error callback triggered with PERMISSION_DENIED
});
오브젝티브-C
참고: 이 Firebase 제품은 App Clip 대상에서 사용할 수 없습니다.
FIRDatabaseReference *ref = [[FIRDatabase database] reference];
[[_ref child:@"records"] observeSingleEventOfType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) {
  // success block is not called
} withCancelBlock:^(NSError * _Nonnull error) {
  // cancel block triggered with PERMISSION_DENIED
}];
빠른
참고: 이 Firebase 제품은 App Clip 대상에서 사용할 수 없습니다.
var ref = FIRDatabase.database().reference()
ref.child("records").observeSingleEventOfType(.Value, withBlock: { snapshot in
    // success block is not called
}, withCancelBlock: { error in
    // cancel block triggered with PERMISSION_DENIED
})
자바
FirebaseDatabase database = FirebaseDatabase.getInstance();
DatabaseReference ref = database.getReference("records");
ref.addListenerForSingleValueEvent(new ValueEventListener() {
  @Override
  public void onDataChange(DataSnapshot snapshot) {
    // success method is not called
  }

  @Override
  public void onCancelled(FirebaseError firebaseError) {
    // error callback triggered with PERMISSION_DENIED
  });
});
쉬다
curl https://docs-examples.firebaseio.com/rest/records/
# response returns a PERMISSION_DENIED error

/records/ 의 읽기 작업은 원자적이며 /records/ 아래의 모든 데이터에 대한 액세스 권한을 부여하는 읽기 규칙이 없기 때문에 PERMISSION_DENIED 오류가 발생합니다. Firebase 콘솔 의 보안 시뮬레이터에서 이 규칙을 평가하면 /records/ 경로에 대한 액세스를 허용하는 읽기 규칙이 없기 때문에 읽기 작업이 거부되었음을 알 수 있습니다. 그러나 rec1 에 대한 규칙은 우리가 요청한 경로에 없기 때문에 평가되지 않았습니다. rec1 을 가져오려면 직접 액세스해야 합니다.

자바스크립트
var db = firebase.database();
db.ref("records/rec1").once("value", function(snap) {
  // SUCCESS!
}, function(err) {
  // error callback is not called
});
오브젝티브-C
참고: 이 Firebase 제품은 App Clip 대상에서 사용할 수 없습니다.
FIRDatabaseReference *ref = [[FIRDatabase database] reference];
[[ref child:@"records/rec1"] observeSingleEventOfType:FEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) {
    // SUCCESS!
}];
빠른
참고: 이 Firebase 제품은 App Clip 대상에서 사용할 수 없습니다.
var ref = FIRDatabase.database().reference()
ref.child("records/rec1").observeSingleEventOfType(.Value, withBlock: { snapshot in
    // SUCCESS!
})
자바
FirebaseDatabase database = FirebaseDatabase.getInstance();
DatabaseReference ref = database.getReference("records/rec1");
ref.addListenerForSingleValueEvent(new ValueEventListener() {
  @Override
  public void onDataChange(DataSnapshot snapshot) {
    // SUCCESS!
  }

  @Override
  public void onCancelled(FirebaseError firebaseError) {
    // error callback is not called
  }
});
쉬다
curl https://docs-examples.firebaseio.com/rest/records/rec1
# SUCCESS!

겹치는 문

하나 이상의 규칙이 노드에 적용될 수 있습니다. 여러 규칙 표현식이 노드를 식별하는 경우 조건 중 하나 라도 false 이면 액세스 방법이 거부됩니다.

{
  "rules": {
    "messages": {
      // A rule expression that applies to all nodes in the 'messages' node
      "$message": {
        ".read": "true",
        ".write": "true"
      },
      // A second rule expression applying specifically to the 'message1` node
      "message1": {
        ".read": "false",
        ".write": "false"
      }
    }
  }
}

위의 예에서 message1 노드에 대한 읽기는 첫 번째 규칙이 항상 true 임에도 불구하고 두 번째 규칙이 항상 false 이므로 거부됩니다.

다음 단계

Firebase 실시간 데이터베이스 보안 규칙에 대한 이해를 심화할 수 있습니다.

  • 규칙 언어의 다음 주요 개념인 동적 조건 에 대해 알아보세요. 이를 통해 규칙에서 사용자 권한 부여를 확인하고, 기존 데이터와 수신 데이터를 비교하고, 수신 데이터의 유효성을 검사하고, 클라이언트에서 오는 쿼리 구조를 확인하는 등의 작업을 수행할 수 있습니다.

  • 일반적인 보안 사용 사례와 이를 해결하는 Firebase 보안 규칙 정의를 검토합니다 .