이 가이드는 Firebase 실시간 데이터베이스 보안 규칙에 조건을 추가하는 방법을 보여주는 핵심 Firebase 보안 규칙 언어 가이드를 기반으로 합니다.
실시간 데이터베이스 보안 규칙의 기본 구성 요소는 조건 입니다. 조건은 특정 작업을 허용할지 또는 거부할지를 결정하는 부울 식입니다. 기본 규칙의 경우 true
및 false
리터럴을 조건으로 사용하면 완벽하게 잘 작동합니다. 그러나 실시간 데이터베이스 보안 규칙 언어는 다음과 같은 보다 복잡한 조건을 작성할 수 있는 방법을 제공합니다.
- 사용자 인증 확인
- 새로 제출된 데이터에 대해 기존 데이터 평가
- 데이터베이스의 다른 부분에 액세스하고 비교하십시오.
- 수신 데이터 유효성 검사
- 보안 논리에 대한 수신 쿼리 구조 사용
$ 변수를 사용하여 경로 세그먼트 캡처
$
접두사를 사용하여 캡처 변수를 선언하여 읽기 또는 쓰기 경로의 일부를 캡처할 수 있습니다. 이것은 와일드 카드 역할을 하며 규칙 조건 내에서 사용하기 위해 해당 키의 값을 저장합니다.
{ "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
변수를 사용하여 widget
에 title
및 color
이외의 자식이 없도록 하는 .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 } } } }
입증
가장 일반적인 보안 규칙 패턴 중 하나는 사용자의 인증 상태에 따라 액세스를 제어하는 것입니다. 예를 들어 앱에서 로그인한 사용자만 데이터를 쓰도록 허용할 수 있습니다.
앱에서 Firebase 인증을 사용하는 경우 request.auth
변수에는 데이터를 요청하는 클라이언트에 대한 인증 정보가 포함됩니다. request.auth
에 대한 자세한 내용 은 참조 문서 를 참조하십시오.
Firebase 인증은 Firebase 실시간 데이터베이스와 통합되어 조건을 사용하여 사용자별로 데이터 액세스를 제어할 수 있습니다. 사용자가 인증되면 실시간 데이터베이스 보안 규칙 규칙의 auth
변수가 사용자 정보로 채워집니다. 이 정보에는 고유 식별자( uid
)와 Facebook ID 또는 이메일 주소와 같은 연결된 계정 데이터 및 기타 정보가 포함됩니다. 사용자 지정 인증 공급자를 구현하는 경우 사용자의 인증 페이로드에 자신의 필드를 추가할 수 있습니다.
이 섹션에서는 Firebase 실시간 데이터베이스 보안 규칙 언어를 사용자에 대한 인증 정보와 결합하는 방법을 설명합니다. 이 두 가지 개념을 결합하여 사용자 ID를 기반으로 데이터에 대한 액세스를 제어할 수 있습니다.
auth
변수
규칙에서 미리 정의된 auth
변수는 인증이 발생하기 전에는 null입니다.
사용자가 Firebase 인증 으로 인증되면 다음 속성이 포함됩니다.
공급자 | 사용된 인증 방법("password", "anonymous", "facebook", "github", "google" 또는 "twitter"). |
액체 | 모든 제공업체에서 고유한 고유한 사용자 ID입니다. |
토큰 | Firebase 인증 ID 토큰의 콘텐츠입니다. 자세한 내용은 auth.token 에 대한 참조 문서를 참조하세요. |
다음은 auth
변수를 사용하여 각 사용자가 사용자별 경로에만 쓸 수 있도록 하는 규칙의 예입니다.
{ "rules": { "users": { "$user_id": { // grants write access to the owner of this user account // whose uid must exactly match the key ($user_id) ".write": "$user_id === auth.uid" } } } }
인증 조건을 지원하도록 데이터베이스 구조화
일반적으로 규칙을 더 쉽게 작성할 수 있는 방식으로 데이터베이스를 구성하는 것이 좋습니다. 실시간 데이터베이스에 사용자 데이터를 저장하는 일반적인 패턴 중 하나는 하위 항목이 모든 사용자의 uid
값인 단일 users
노드에 모든 사용자를 저장하는 것입니다. 로그인한 사용자만 자신의 데이터를 볼 수 있도록 이 데이터에 대한 액세스를 제한하려는 경우 규칙은 다음과 같습니다.
{ "rules": { "users": { "$uid": { ".read": "auth !== null && auth.uid === $uid" } } } }
인증 사용자 지정 클레임 작업
다양한 사용자에 대한 맞춤 액세스 제어가 필요한 앱의 경우 Firebase 인증을 통해 개발자가 Firebase 사용자에 대한 클레임을 설정할 수 있습니다. 이러한 클레임은 규칙의 auth.token
변수에서 액세스할 수 있습니다. 다음은 hasEmergencyTowel
사용자 지정 클레임을 사용하는 규칙의 예입니다.
{ "rules": { "frood": { // A towel is about the most massively useful thing an interstellar // hitchhiker can have ".read": "auth.token.hasEmergencyTowel === true" } } }
고유한 사용자 지정 인증 토큰 을 만드는 개발자는 선택적으로 이러한 토큰에 클레임을 추가할 수 있습니다. 이러한 클레임은 규칙의 auth.token
변수에서 사용할 수 있습니다.
기존 데이터와 새 데이터 비교
미리 정의된 data
변수는 쓰기 작업이 발생하기 전에 데이터를 참조하는 데 사용됩니다. 반대로 newData
변수에는 쓰기 작업이 성공할 경우 존재할 새 데이터가 포함됩니다. newData
는 작성 중인 새 데이터와 기존 데이터의 병합 결과를 나타냅니다.
예를 들어 이 규칙을 사용하면 새 레코드를 만들거나 기존 레코드를 삭제할 수 있지만 null이 아닌 기존 데이터를 변경할 수는 없습니다.
// we can write as long as old data or new data does not exist // in other words, if this is a delete or a create, but not an update ".write": "!data.exists() || !newData.exists()"
다른 경로의 데이터 참조
모든 데이터를 규칙의 기준으로 사용할 수 있습니다. 미리 정의된 변수 root
, data
및 newData
를 사용하여 쓰기 이벤트 전후에 존재하는 모든 경로에 액세스할 수 있습니다.
/allow_writes/
노드의 값이 true
이고 부모 노드에 readOnly
플래그가 설정되어 있지 않고 새로 작성된 데이터에 foo
라는 자식이 있는 한 쓰기 작업을 허용하는 다음 예제를 고려하십시오.
".write": "root.child('allow_writes').val() === true && !data.parent().child('readOnly').exists() && newData.child('foo').exists()"
데이터 검증
.write
규칙이 액세스 권한을 부여한 후에만 실행되는 .validate
규칙을 사용하여 데이터 구조를 적용하고 데이터의 형식 및 콘텐츠 유효성을 검사해야 합니다. 다음은 1900-2099년 사이의 YYYY-MM-DD 형식의 날짜만 허용하는 샘플 .validate
규칙 정의이며 정규식을 사용하여 확인됩니다.
".validate": "newData.isString() && newData.val().matches(/^(19|20)[0-9][0-9][-\\/. ](0[1-9]|1[012])[-\\/. ](0[1-9]|[12][0-9]|3[01])$/)"
.validate
규칙은 종속되지 않는 유일한 유형의 보안 규칙입니다. 하위 레코드에서 유효성 검사 규칙이 실패하면 전체 쓰기 작업이 거부됩니다. 또한 유효성 검사 정의는 데이터가 삭제될 때(즉, 작성 중인 새 값이 null
인 경우) 무시됩니다.
사소한 점처럼 보일 수 있지만 사실 강력한 Firebase 실시간 데이터베이스 보안 규칙을 작성하기 위한 중요한 기능입니다. 다음 규칙을 고려하십시오.
{ "rules": { // write is allowed for all paths ".write": true, "widget": { // a valid widget must have attributes "color" and "size" // allows deleting widgets (since .validate is not applied to delete rules) ".validate": "newData.hasChildren(['color', 'size'])", "size": { // the value of "size" must be a number between 0 and 99 ".validate": "newData.isNumber() && newData.val() >= 0 && newData.val() <= 99" }, "color": { // the value of "color" must exist as a key in our mythical // /valid_colors/ index ".validate": "root.child('valid_colors/' + newData.val()).exists()" } } } }
이 변형을 염두에 두고 다음 쓰기 작업의 결과를 살펴보십시오.
자바스크립트
var ref = db.ref("/widget"); // PERMISSION_DENIED: does not have children color and size ref.set('foo'); // PERMISSION DENIED: does not have child color ref.set({size: 22}); // PERMISSION_DENIED: size is not a number ref.set({ size: 'foo', color: 'red' }); // SUCCESS (assuming 'blue' appears in our colors list) ref.set({ size: 21, color: 'blue'}); // If the record already exists and has a color, this will // succeed, otherwise it will fail since newData.hasChildren(['color', 'size']) // will fail to validate ref.child('size').set(99);
목표-C
FIRDatabaseReference *ref = [[[FIRDatabase database] reference] child: @"widget"]; // PERMISSION_DENIED: does not have children color and size [ref setValue: @"foo"]; // PERMISSION DENIED: does not have child color [ref setValue: @{ @"size": @"foo" }]; // PERMISSION_DENIED: size is not a number [ref setValue: @{ @"size": @"foo", @"color": @"red" }]; // SUCCESS (assuming 'blue' appears in our colors list) [ref setValue: @{ @"size": @21, @"color": @"blue" }]; // If the record already exists and has a color, this will // succeed, otherwise it will fail since newData.hasChildren(['color', 'size']) // will fail to validate [[ref child:@"size"] setValue: @99];
빠른
var ref = FIRDatabase.database().reference().child("widget") // PERMISSION_DENIED: does not have children color and size ref.setValue("foo") // PERMISSION DENIED: does not have child color ref.setValue(["size": "foo"]) // PERMISSION_DENIED: size is not a number ref.setValue(["size": "foo", "color": "red"]) // SUCCESS (assuming 'blue' appears in our colors list) ref.setValue(["size": 21, "color": "blue"]) // If the record already exists and has a color, this will // succeed, otherwise it will fail since newData.hasChildren(['color', 'size']) // will fail to validate ref.child("size").setValue(99);
자바
FirebaseDatabase database = FirebaseDatabase.getInstance(); DatabaseReference ref = database.getReference("widget"); // PERMISSION_DENIED: does not have children color and size ref.setValue("foo"); // PERMISSION DENIED: does not have child color ref.child("size").setValue(22); // PERMISSION_DENIED: size is not a number Map<String,Object> map = new HashMap<String, Object>(); map.put("size","foo"); map.put("color","red"); ref.setValue(map); // SUCCESS (assuming 'blue' appears in our colors list) map = new HashMap<String, Object>(); map.put("size", 21); map.put("color","blue"); ref.setValue(map); // If the record already exists and has a color, this will // succeed, otherwise it will fail since newData.hasChildren(['color', 'size']) // will fail to validate ref.child("size").setValue(99);
쉬다
# PERMISSION_DENIED: does not have children color and size curl -X PUT -d 'foo' \ https://docs-examples.firebaseio.com/rest/securing-data/example.json # PERMISSION DENIED: does not have child color curl -X PUT -d '{"size": 22}' \ https://docs-examples.firebaseio.com/rest/securing-data/example.json # PERMISSION_DENIED: size is not a number curl -X PUT -d '{"size": "foo", "color": "red"}' \ https://docs-examples.firebaseio.com/rest/securing-data/example.json # SUCCESS (assuming 'blue' appears in our colors list) curl -X PUT -d '{"size": 21, "color": "blue"}' \ https://docs-examples.firebaseio.com/rest/securing-data/example.json # If the record already exists and has a color, this will # succeed, otherwise it will fail since newData.hasChildren(['color', 'size']) # will fail to validate curl -X PUT -d '99' \ https://docs-examples.firebaseio.com/rest/securing-data/example/size.json
이제 동일한 구조를 살펴보겠습니다. 그러나 .validate
대신 .write
규칙을 사용합니다.
{ "rules": { // this variant will NOT allow deleting records (since .write would be disallowed) "widget": { // a widget must have 'color' and 'size' in order to be written to this path ".write": "newData.hasChildren(['color', 'size'])", "size": { // the value of "size" must be a number between 0 and 99, ONLY IF WE WRITE DIRECTLY TO SIZE ".write": "newData.isNumber() && newData.val() >= 0 && newData.val() <= 99" }, "color": { // the value of "color" must exist as a key in our mythical valid_colors/ index // BUT ONLY IF WE WRITE DIRECTLY TO COLOR ".write": "root.child('valid_colors/'+newData.val()).exists()" } } } }
이 변형에서는 다음 작업 중 하나가 성공합니다.
자바스크립트
var ref = new Firebase(URL + "/widget"); // ALLOWED? Even though size is invalid, widget has children color and size, // so write is allowed and the .write rule under color is ignored ref.set({size: 99999, color: 'red'}); // ALLOWED? Works even if widget does not exist, allowing us to create a widget // which is invalid and does not have a valid color. // (allowed by the write rule under "color") ref.child('size').set(99);
목표-C
Firebase *ref = [[Firebase alloc] initWithUrl:URL]; // ALLOWED? Even though size is invalid, widget has children color and size, // so write is allowed and the .write rule under color is ignored [ref setValue: @{ @"size": @9999, @"color": @"red" }]; // ALLOWED? Works even if widget does not exist, allowing us to create a widget // which is invalid and does not have a valid color. // (allowed by the write rule under "color") [[ref childByAppendingPath:@"size"] setValue: @99];
빠른
var ref = Firebase(url:URL) // ALLOWED? Even though size is invalid, widget has children color and size, // so write is allowed and the .write rule under color is ignored ref.setValue(["size": 9999, "color": "red"]) // ALLOWED? Works even if widget does not exist, allowing us to create a widget // which is invalid and does not have a valid color. // (allowed by the write rule under "color") ref.childByAppendingPath("size").setValue(99)
자바
Firebase ref = new Firebase(URL + "/widget"); // ALLOWED? Even though size is invalid, widget has children color and size, // so write is allowed and the .write rule under color is ignored Map<String,Object> map = new HashMap<String, Object>(); map.put("size", 99999); map.put("color", "red"); ref.setValue(map); // ALLOWED? Works even if widget does not exist, allowing us to create a widget // which is invalid and does not have a valid color. // (allowed by the write rule under "color") ref.child("size").setValue(99);
쉬다
# ALLOWED? Even though size is invalid, widget has children color and size, # so write is allowed and the .write rule under color is ignored curl -X PUT -d '{size: 99999, color: "red"}' \ https://docs-examples.firebaseio.com/rest/securing-data/example.json # ALLOWED? Works even if widget does not exist, allowing us to create a widget # which is invalid and does not have a valid color. # (allowed by the write rule under "color") curl -X PUT -d '99' \ https://docs-examples.firebaseio.com/rest/securing-data/example/size.json
이는 .write
및 .validate
규칙 간의 차이점을 보여줍니다. 설명된 바와 같이 삭제 허용 여부에 따라 달라지는 newData.hasChildren()
규칙을 제외하고 이러한 모든 규칙은 .validate
를 사용하여 작성해야 합니다.
쿼리 기반 규칙
규칙을 필터로 사용할 수는 없지만 규칙 에서 검색어 매개변수를 사용하여 데이터 하위 집합에 대한 액세스를 제한할 수 있습니다. query.
쿼리 매개변수를 기반으로 읽기 또는 쓰기 액세스 권한을 부여하는 규칙의 표현식입니다.
예를 들어 다음 쿼리 기반 규칙은 사용자 기반 보안 규칙 과 쿼리 기반 규칙을 사용하여 활성 사용자가 소유한 장바구니로만 baskets
컬렉션의 데이터에 대한 액세스를 제한합니다.
"baskets": {
".read": "auth.uid !== null &&
query.orderByChild === 'owner' &&
query.equalTo === auth.uid" // restrict basket access to owner of basket
}
규칙에 쿼리 매개변수를 포함하는 다음 쿼리는 성공합니다.
db.ref("baskets").orderByChild("owner")
.equalTo(auth.currentUser.uid)
.on("value", cb) // Would succeed
그러나 규칙에 매개변수가 포함되지 않은 쿼리는 PermissionDenied
오류와 함께 실패합니다.
db.ref("baskets").on("value", cb) // Would fail with PermissionDenied
쿼리 기반 규칙을 사용하여 클라이언트가 읽기 작업을 통해 다운로드하는 데이터의 양을 제한할 수도 있습니다.
예를 들어 다음 규칙은 우선 순위에 따라 쿼리의 처음 1000개 결과에 대한 읽기 액세스를 제한합니다.
messages: {
".read": "query.orderByKey &&
query.limitToFirst <= 1000"
}
// Example queries:
db.ref("messages").on("value", cb) // Would fail with PermissionDenied
db.ref("messages").limitToFirst(1000)
.on("value", cb) // Would succeed (default order by key)
다음 query.
표현식은 실시간 데이터베이스 보안 규칙에서 사용할 수 있습니다.
쿼리 기반 규칙 표현식 | ||
---|---|---|
표현 | 유형 | 설명 |
query.orderByKey 쿼리.orderByPriority query.orderByValue | 부울 | 키, 우선 순위 또는 값으로 정렬된 쿼리의 경우 True입니다. 그렇지 않으면 거짓입니다. |
쿼리.orderByChild | 끈 없는 | 문자열을 사용하여 자식 노드에 대한 상대 경로를 나타냅니다. 예를 들어 query.orderByChild === "address/zip" 입니다. 쿼리가 하위 노드에 의해 정렬되지 않은 경우 이 값은 null입니다. |
query.startAt query.endAt query.equalTo | 끈 숫자 부울 없는 | 실행 중인 쿼리의 경계를 검색하거나 경계 집합이 없는 경우 null을 반환합니다. |
query.limitToFirst query.limitToLast | 숫자 없는 | 실행 중인 쿼리에 대한 제한을 검색하거나 설정된 제한이 없는 경우 null을 반환합니다. |
다음 단계
이 조건에 대한 논의를 마치면 규칙을 보다 정교하게 이해하고 다음을 수행할 준비가 됩니다.
핵심 사용 사례를 처리하는 방법을 알아보고 규칙을 개발, 테스트 및 배포하기 위한 워크플로를 알아보세요.
- 조건을 구축하는 데 사용할 수 있는 사전 정의된 규칙 변수 의 전체 집합에 대해 알아봅니다.
- 일반적인 시나리오 를 다루는 규칙을 작성합니다.
- 안전하지 않은 규칙을 발견하고 피해야 하는 상황을 검토하여 지식을 쌓으십시오.
- Firebase 로컬 에뮬레이터 도구 모음과 이를 사용하여 규칙을 테스트 하는 방법에 대해 알아보세요.
- 규칙 배포 에 사용할 수 있는 방법을 검토합니다.
실시간 데이터베이스와 관련된 규칙 기능 알아보기:
- 실시간 데이터베이스의 색인 을 생성하는 방법을 알아보세요.
- 규칙 배포를 위한 REST API를 검토하십시오.