Как работают правила безопасности

Безопасность может быть одной из самых сложных частей головоломки разработки приложения. В большинстве приложений разработчики должны создать и запустить сервер, который обрабатывает аутентификацию (кто пользователь) и авторизацию (что пользователь может делать).

Firebase Security Rules удаляют средний (серверный) уровень и позволяют вам указывать разрешения на основе пути для клиентов, которые подключаются к вашим данным напрямую. Используйте это руководство, чтобы узнать больше о том, как правила применяются к входящим запросам.

Выберите продукт, чтобы узнать больше о его правилах.

Cloud Firestore

Базовая структура

Firebase Security Rules в Cloud Firestore и Cloud Storage используют следующую структуру и синтаксис:

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

При разработке правил важно понимать следующие ключевые концепции:

  • Запрос: Метод или методы, вызываемые в операторе allow . Это методы, запуск которых вы разрешаете. Стандартные методы: get , list , create , update и delete . Методы удобства read и write обеспечивают широкий доступ для чтения и записи в указанной базе данных или пути хранения.
  • Путь: База данных или место хранения, представленное в виде пути URI.
  • Правило: allow оператор, включающий условие, разрешающее запрос, если оно оценивается как истинное.

Правила безопасности версии 2

По состоянию на май 2019 года доступна версия 2 правил безопасности Firebase . Версия 2 правил изменяет поведение рекурсивных подстановочных знаков {name=**} . Вы должны использовать версию 2, если планируете использовать запросы групп коллекций . Вы должны согласиться на версию 2, сделав rules_version = '2'; в первой строке ваших правил безопасности:

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {

Соответствующие пути

Все операторы сопоставления должны указывать на документы, а не на коллекции. Оператор сопоставления может указывать на конкретный документ, как в match /cities/SF , или использовать подстановочные знаки для указания на любой документ в указанном пути, как в match /cities/{city} .

В этом примере оператор match использует синтаксис подстановочных знаков {city} . Это означает, что правило применяется к любому документу в коллекции cities , например /cities/SF или /cities/NYC . Когда выражения allow в операторе match оцениваются, переменная city будет преобразована в название документа города, например SF или NYC .

Соответствующие подколлекции

Данные в Cloud Firestore организованы в коллекции документов, и каждый документ может расширять иерархию через подколлекции. Важно понимать, как правила безопасности взаимодействуют с иерархическими данными.

Рассмотрим ситуацию, когда каждый документ в коллекции cities содержит подколлекцию landmarks . Правила безопасности применяются только к соответствующему пути, поэтому элементы управления доступом, определенные в коллекции cities не применяются к подколлекцией landmarks . Вместо этого напишите явные правила для управления доступом к подколлекциям:

service cloud.firestore {
  match /databases/{database}/documents {
    match /cities/{city} {
      allow read, write: if <condition>;

      // Explicitly define rules for the 'landmarks' subcollection
      match /landmarks/{landmark} {
        allow read, write: if <condition>;
      }
    }
  }
}

При вложении операторов match путь внутреннего оператора match всегда относительен пути внешнего оператора match . Поэтому следующие наборы правил эквивалентны:

service cloud.firestore {
  match /databases/{database}/documents {
    match /cities/{city} {
      match /landmarks/{landmark} {
        allow read, write: if <condition>;
      }
    }
  }
}
service cloud.firestore {
  match /databases/{database}/documents {
    match /cities/{city}/landmarks/{landmark} {
      allow read, write: if <condition>;
    }
  }
}

Перекрывающиеся заявления о совпадении

Документ может соответствовать более чем одному оператору match . В случае, когда несколько выражений allow соответствуют запросу, доступ разрешается, если true любое из условий:

service cloud.firestore {
  match /databases/{database}/documents {
    // Matches any document in the 'cities' collection.
    match /cities/{city} {
      allow read, write: if false;
    }

    // Matches any document in the 'cities' collection.
    match /cities/{document} {
      allow read, write: if true;
    }
  }
}

В этом примере все операции чтения и записи в коллекцию cities будут разрешены, поскольку второе правило всегда true , даже если первое правило всегда false .

Рекурсивные подстановочные знаки

Если вы хотите, чтобы правила применялись к произвольно глубокой иерархии, используйте рекурсивный синтаксис подстановочных знаков, {name=**} :

service cloud.firestore {
  match /databases/{database}/documents {
    // Matches any document in the cities collection as well as any document
    // in a subcollection.
    match /cities/{document=**} {
      allow read, write: if <condition>;
    }
  }
}

При использовании рекурсивного синтаксиса подстановочных знаков переменная подстановочных знаков будет содержать весь соответствующий сегмент пути, даже если документ находится в глубоко вложенной подколлекции. Например, перечисленные правила будут соответствовать документу, расположенному в /cities/SF/landmarks/coit_tower , а значение переменной document будет SF/landmarks/coit_tower .

Однако следует отметить, что поведение рекурсивных подстановочных знаков зависит от версии правил.

Версия 1

Правила безопасности по умолчанию используют версию 1. В версии 1 рекурсивные подстановочные знаки соответствуют одному или нескольким элементам пути. Они не соответствуют пустому пути, поэтому match /cities/{city}/{document=**} соответствует документам в подколлекциях, но не в коллекции cities , тогда как match /cities/{document=**} соответствует документам как в коллекции cities , так и в подколлекциях.

Рекурсивные подстановочные знаки должны располагаться в конце оператора сопоставления.

Версия 2

В версии 2 правил безопасности рекурсивные подстановочные знаки соответствуют нулю или более элементам пути. match/cities/{city}/{document=**} соответствует документам в любых подколлекциях, а также документам в коллекции cities .

Вам необходимо включить версию 2, добавив rules_version = '2'; в верхней части правил безопасности:

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    // Matches any document in the cities collection as well as any document
    // in a subcollection.
    match /cities/{city}/{document=**} {
      allow read, write: if <condition>;
    }
  }
}

Вы можете иметь максимум один рекурсивный подстановочный знак на оператор сопоставления, но в версии 2 вы можете разместить этот подстановочный знак в любом месте оператора сопоставления. Например:

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    // Matches any document in the songs collection group
    match /{path=**}/songs/{song} {
      allow read, write: if <condition>;
    }
  }
}

Если вы используете запросы групп сбора данных , вам необходимо использовать версию 2, см . раздел Защита запросов групп сбора данных .

Ограничения правил безопасности

При работе с правилами безопасности обратите внимание на следующие ограничения:

Предел Подробности
Максимальное количество вызовов exists() , get() и getAfter() на запрос
  • 10 для запросов на отдельные документы и запросов.
  • 20 для многодокументных чтений, транзакций и пакетных записей. Предыдущий предел в 10 также применяется к каждой операции.

    Например, представьте, что вы создаете пакетный запрос на запись с 3 операциями записи, и что ваши правила безопасности используют 2 вызова доступа к документу для проверки каждой записи. В этом случае каждая запись использует 2 из своих 10 вызовов доступа, а пакетный запрос на запись использует 6 из своих 20 вызовов доступа.

Превышение любого из этих ограничений приводит к ошибке отказа в доступе.

Некоторые вызовы доступа к документам могут кэшироваться, а кэшированные вызовы не учитываются при подсчете лимитов.

Максимальная глубина вложенного оператора match 10
Максимальная длина пути в сегментах пути, разрешенная в наборе вложенных операторов match 100
Максимальное количество переменных захвата пути, разрешенное в наборе вложенных операторов match 20
Максимальная глубина вызова функции 20
Максимальное количество аргументов функции 7
Максимальное количество привязок переменных let на функцию 10
Максимальное количество рекурсивных или циклических вызовов функций 0 (не разрешено)
Максимальное количество выражений, оцениваемых за один запрос 1000
Максимальный размер набора правил Наборы правил должны соответствовать двум ограничениям по размеру:
  • ограничение в 256 КБ на размер исходного текста набора правил, публикуемого из консоли Firebase или из CLI с помощью firebase deploy .
  • ограничение в 250 КБ на размер скомпилированного набора правил, который получается, когда Firebase обрабатывает исходный код и делает его активным на бэкэнде.

Cloud Storage

Базовая структура

Firebase Security Rules в Cloud Firestore и Cloud Storage используют следующую структуру и синтаксис:

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

При разработке правил важно понимать следующие ключевые концепции:

  • Запрос: Метод или методы, вызываемые в операторе allow . Это методы, запуск которых вы разрешаете. Стандартные методы: get , list , create , update и delete . Методы удобства read и write обеспечивают широкий доступ для чтения и записи в указанной базе данных или пути хранения.
  • Путь: База данных или место хранения, представленное в виде пути URI.
  • Правило: allow оператор, включающий условие, разрешающее запрос, если оно оценивается как истинное.

Соответствующие пути

Cloud Storage Security Rules match путям файлов, используемым для доступа к файлам в Cloud Storage . Правила могут match точным путям или подстановочным путям, а также могут быть вложенными. Если ни одно правило соответствия не разрешает метод запроса или условие оценивается как false , запрос отклоняется.

Точные совпадения

// Exact match for "images/profilePhoto.png"
match /images/profilePhoto.png {
  allow write: if <condition>;
}

// Exact match for "images/croppedProfilePhoto.png"
match /images/croppedProfilePhoto.png {
  allow write: if <other_condition>;
}

Вложенные совпадения

// Partial match for files that start with "images"
match /images {
  // Exact match for "images/profilePhoto.png"
  match /profilePhoto.png {
    allow write: if <condition>;
  }

  // Exact match for "images/croppedProfilePhoto.png"
  match /croppedProfilePhoto.png {
    allow write: if <other_condition>;
  }
}

Совпадения с подстановочными знаками

Правила также можно использовать для match шаблона с использованием подстановочных знаков. Подстановочный знак — это именованная переменная, которая представляет либо одну строку, например profilePhoto.png , либо несколько сегментов пути, например images/profilePhoto.png .

Подстановочный знак создается путем добавления фигурных скобок вокруг имени подстановочного знака, например {string} . Подстановочный знак с несколькими сегментами может быть объявлен путем добавления =** к имени подстановочного знака, например {path=**} :

// Partial match for files that start with "images"
match /images {
  // Exact match for "images/*"
  // e.g. images/profilePhoto.png is matched
  match /{imageId} {
    // This rule only matches a single path segment (*)
    // imageId is a string that contains the specific segment matched
    allow read: if <condition>;
  }

  // Exact match for "images/**"
  // e.g. images/users/user:12345/profilePhoto.png is matched
  // images/profilePhoto.png is also matched!
  match /{allImages=**} {
    // This rule matches one or more path segments (**)
    // allImages is a path that contains all segments matched
    allow read: if <other_condition>;
  }
}

Если файлу соответствуют несколько правил, результат — это OR результата всех оценок правил. То есть, если какое-либо правило, которому соответствует файл, оценивается как true , результат — true .

В правилах файл «images/profilePhoto.png» может быть прочитан, если condition или other_condition имеет значение true, тогда как файл «images/users/user:12345/profilePhoto.png» зависит только от результата other_condition .

На подстановочную переменную можно ссылаться из имени файла или пути, предоставленного для match :

// Another way to restrict the name of a file
match /images/{imageId} {
  allow read: if imageId == "profilePhoto.png";
}

Cloud Storage Security Rules не каскадируются, и правила оцениваются только тогда, когда путь запроса совпадает с путем, для которого указаны правила.

Запросить оценку

Загрузки, скачивания, изменения метаданных и удаления оцениваются с использованием request отправленного в Cloud Storage . Переменная request содержит путь, по которому выполняется запрос, время получения запроса и новое значение resource , если запрос является записью. Также включены заголовки HTTP и состояние аутентификации.

Объект request также содержит уникальный идентификатор пользователя и полезную нагрузку Firebase Authentication в объекте request.auth , который будет более подробно описан в разделе «Аутентификация» документации.

Полный список свойств объекта request доступен ниже:

Свойство Тип Описание
auth карта<строка, строка> Когда пользователь вошел в систему, предоставляет uid , уникальный идентификатор пользователя, и token , карту утверждений Firebase Authentication JWT. В противном случае это будет null .
params карта<строка, строка> Карта, содержащая параметры запроса.
path путь path представляющий путь, по которому выполняется запрос.
resource карта<строка, строка> Новое значение ресурса, присутствующее только в запросах write .
time временная метка Временная метка, представляющая время сервера, когда оценивается запрос.

Оценка ресурсов

При оценке правил вы также можете захотеть оценить метаданные загружаемого, скачиваемого, изменяемого или удаляемого файла. Это позволяет вам создавать сложные и мощные правила, которые делают такие вещи, как разрешают загрузку только файлов с определенными типами контента или удаление только файлов, превышающих определенный размер.

Firebase Security Rules for Cloud Storage предоставляет метаданные файла в объекте resource , который содержит пары ключ/значение метаданных, представленных в объекте Cloud Storage . Эти свойства можно проверять при запросах read или write , чтобы гарантировать целостность данных.

В запросах write (таких как загрузки, обновления метаданных и удаления), в дополнение к resource объекту, который содержит метаданные файла для файла, существующего по пути запроса, вы также можете использовать объект request.resource , который содержит подмножество метаданных файла, которые должны быть записаны, если запись разрешена. Вы можете использовать эти два значения для обеспечения целостности данных или принудительного применения ограничений приложения, таких как тип или размер файла.

Полный список свойств объекта resource доступен:

Свойство Тип Описание
name нить Полное наименование объекта
bucket нить Имя контейнера, в котором находится этот объект.
generation инт Генерация объекта Google Cloud Storage для этого объекта.
metageneration инт Мета-генерация объекта Google Cloud Storage этого объекта.
size инт Размер объекта в байтах.
timeCreated временная метка Метка времени, указывающая время создания объекта.
updated временная метка Временная метка, представляющая время последнего обновления объекта.
md5Hash нить MD5-хеш объекта.
crc32c нить Хеш crc32c объекта.
etag нить Тег e, связанный с этим объектом.
contentDisposition нить Расположение контента, связанного с этим объектом.
contentEncoding нить Кодировка содержимого, связанная с этим объектом.
contentLanguage нить Язык контента, связанный с этим объектом.
contentType нить Тип контента, связанный с этим объектом.
metadata карта<строка, строка> Пары ключ/значение дополнительных, указанных разработчиком пользовательских метаданных.

request.resource содержит все эти данные, за исключением generation , metageneration , etag , timeCreated и updated .

Ограничения правил безопасности

При работе с правилами безопасности обратите внимание на следующие ограничения:

Предел Подробности
Максимальное количество вызовов firestore.exists() и firestore.get() на запрос

2 для запросов на отдельные документы и запросов.

Превышение этого лимита приведет к ошибке отказа в доступе.

Вызовы доступа к тем же документам могут кэшироваться, а кэшированные вызовы не учитываются при подсчете лимитов.

Полный пример

Собрав все это вместе, можно создать полный пример правил для решения по хранению изображений:

service firebase.storage {
 match /b/{bucket}/o {
   match /images {
     // Allow write files to the path "images/*", subject to the constraints:
     // 1) File is less than 5MB
     // 2) Content type is an image
     // 3) Uploaded content type matches existing content type
     // 4) Filename (stored in imageId wildcard variable) is less than 32 characters
     match /{imageId} {
       allow read;
       allow write: if request.resource.size < 5 * 1024 * 1024
                    && request.resource.contentType.matches('image/.*')
                    && request.resource.contentType == resource.contentType
                    && imageId.size() < 32
     }
   }
 }
}

Realtime Database

Базовая структура

В Realtime Database Firebase Security Rules состоят из выражений, подобных JavaScript, содержащихся в документе JSON.

Они используют следующий синтаксис:

{
  "rules": {
    "<<path>>": {
    // Allow the request if the condition for each method is true.
      ".read": <<condition>>,
      ".write": <<condition>>,
      ".validate": <<condition>>
    }
  }
}

Правило содержит три основных элемента:

  • Путь: Расположение базы данных. Это отражает структуру JSON вашей базы данных.
  • Запрос: Это методы, которые правило использует для предоставления доступа. Правила read и write предоставляют широкий доступ для чтения и записи, в то время как правила validate действуют как вторичная проверка для предоставления доступа на основе входящих или существующих данных.
  • Условие: Условие, разрешающее запрос, если оно оценивается как истинное.

Как правила применяются к путям

В Realtime Database Rules применяются атомарно, то есть правила на родительских узлах более высокого уровня переопределяют правила на более гранулярных дочерних узлах, а правила на более глубоком узле не могут предоставить доступ к родительскому пути. Вы не можете уточнить или отменить доступ к более глубокому пути в структуре вашей базы данных, если вы уже предоставили его для одного из родительских путей.

Примите во внимание следующие правила:

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

Эта структура безопасности позволяет читать /bar/ всякий раз, когда /foo/ содержит дочерний baz со значением true . Правило ".read": false в /foo/bar/ здесь не действует, поскольку доступ не может быть отозван дочерним путем.

Хотя это может показаться не совсем интуитивно понятным, это мощная часть языка правил, которая позволяет реализовать очень сложные привилегии доступа с минимальными усилиями. Это особенно полезно для безопасности на основе пользователей .

Однако правила .validate не каскадируются. Все правила проверки должны быть выполнены на всех уровнях иерархии, чтобы запись была разрешена.

Кроме того, поскольку правила не применяются обратно к родительскому пути, операция чтения или записи завершается неудачей, если в запрошенном месте или родительском месте нет правила, предоставляющего доступ. Даже если каждый затронутый дочерний путь доступен, чтение в родительском месте полностью завершится неудачей. Рассмотрим эту структуру:

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

Без понимания того, что правила оцениваются атомарно, может показаться, что выборка пути /records/ вернет rec1 , но не rec2 . Однако фактическим результатом является ошибка:

JavaScript
var db = firebase.database();
db.ref("records").once("value", function(snap) {
  // success method is not called
}, function(err) {
  // error callback triggered with PERMISSION_DENIED
});
Objective-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 , мы увидим, что операция чтения была отклонена:

Attempt to read /records with auth=Success(null)
    /
    /records

No .read rule allowed the operation.
Read was denied.

Операция была отклонена, поскольку ни одно правило чтения не разрешало доступ к пути /records/ , но обратите внимание, что правило для rec1 никогда не оценивалось, поскольку его не было в запрошенном нами пути. Чтобы получить rec1 , нам нужно было бы получить к нему прямой доступ:

JavaScript
var db = firebase.database();
db.ref("records/rec1").once("value", function(snap) {
  // SUCCESS!
}, function(err) {
  // error callback is not called
});
Objective-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!

Переменная местоположения

Realtime Database Rules поддерживает переменную $location для сопоставления сегментов пути. Используйте префикс $ перед сегментом пути, чтобы сопоставить правило с любыми дочерними узлами вдоль пути.

  {
    "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')"
          }
        }
      }
    }
  }

Вы также можете использовать $variable параллельно с постоянными именами путей.

  {
    "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 }
      }
    }
  }