A segurança pode ser uma das partes mais complexas do quebra-cabeças de desenvolvimento de aplicativos. Na maioria dos apps, os desenvolvedores precisam criar e executar um servidor que realize a autenticação (identificar quem é o usuário) e a autorização (definir o que um usuário pode fazer).
As regras de segurança do Firebase removem a camada do meio (servidor). Assim, é possível especificar permissões baseadas em caminho para clientes que se conectam diretamente aos seus dados. Veja neste guia mais sobre como as regras são aplicadas às solicitações recebidas.
Selecione um produto para saber mais sobre as regras dele.
Cloud Firestore
Estrutura básica
As regras de segurança do Firebase no Firestore e no Cloud Storage usam a estrutura e sintaxe a seguir:
service <<name>> {
// Match the resource path.
match <<path>> {
// Allow the request if the following conditions are true.
allow <<methods>> : if <<condition>>
}
}
Veja abaixo os principais conceitos que você precisa conhecer antes de criar as regras.
- Solicitação: o método ou os métodos invocados na instrução
allow
que você permite serem executados. Os métodos padrão são:get
,list
,create
,update
edelete
. Os métodos de conveniênciaread
ewrite
permitem amplo acesso de leitura e gravação no banco de dados ou caminho de armazenamento especificado. - Caminho: o banco de dados ou o local de armazenamento, representado como um caminho de URI.
- Regra: a instrução
allow
, que inclui uma condição que permite uma solicitação se ela for avaliada como verdadeira.
Versão 2 das regras de segurança
A partir de maio de 2019, a versão 2 das regras de segurança do Firebase já está
disponível. A versão 2 das regras muda o comportamento dos caracteres curingas
recorrentes {name=**}
. Escolha essa versão se pretende
usar consultas do grupo de coleções. Para ativar a
versão 2, é preciso tornar rules_version = '2';
a primeira linha nas regras de
segurança:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
Caminhos correspondentes
Todas as instruções de correspondência devem se referir a documentos e não coleções. Uma instrução de correspondência pode indicar um documento específico, como em match /cities/SF
, ou usar caracteres curingas para indicar qualquer documento no caminho especificado, como em match /cities/{city}
.
No exemplo acima, a instrução de correspondência usa a sintaxe com caractere curinga {city}
.
Isso significa que a regra se aplica a qualquer documento na coleção cities
, como
/cities/SF
ou /cities/NYC
. Quando as expressões allow
na instrução de correspondência forem
avaliadas, a variável city
resultará no nome do documento da cidade,
como SF
ou NYC
.
Subconjuntos correspondentes
Os dados no Cloud Firestore são organizados em conjuntos de documentos, e cada documento pode estender a hierarquia por meio de subconjuntos. É importante entender como as regras de segurança interagem com dados hierárquicos.
Considere a situação em que cada documento na coleção cities
contém uma
subcoleção landmarks
. As regras de segurança se aplicam somente ao caminho correspondente, de modo que
os controles de acesso definidos na coleção cities
não se aplicam à
subcoleção landmarks
. Em vez disso, escreva regras explícitas para controlar o acesso
a subcoleções:
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>;
}
}
}
}
Ao aninhar instruções match
, o caminho da instrução match
interna é sempre
relativo ao caminho da instrução match
externa. Os seguintes conjuntos de regras
são equivalentes:
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>;
}
}
}
Caracteres curinga recursivos
Se você quer aplicar regras a uma hierarquia arbitrariamente profunda, use a
sintaxe de caractere curinga recorrente, {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>;
}
}
}
Ao usar a sintaxe com caractere curinga recorrente, a variável dele terá
todo o segmento do caminho de correspondência, mesmo que o documento esteja localizado em uma subcoleção aninhada
em vários níveis. Por exemplo, as regras listadas acima corresponderiam
a um documento localizado em /cities/SF/landmarks/coit_tower
, e o valor
da variável document
seria SF/landmarks/coit_tower
.
No entanto, o comportamento de caracteres curinga recursivos depende da versão das regras.
Versão 1
As regras de segurança usam a versão 1 por padrão. Nela, os caracteres curinga recursivos
correspondem a um ou mais itens de caminho. Eles não correspondem a um caminho vazio, por isso
match /cities/{city}/{document=**}
corresponde aos documentos em subcoleções, mas
não na coleção cities
, enquanto match /cities/{document=**}
corresponde
a ambos os documentos na coleção cities
e nas subcoleções.
Os caracteres curinga recursivos precisam aparecer no fim de uma instrução de correspondência.
Versão 2
Na versão 2 das regras de segurança, caracteres curingas recorrentes correspondem a zero ou mais itens
de caminho. match/cities/{city}/{document=**}
corresponde a documentos em todas as
subcoleções, bem como aos documentos na coleção cities
.
Você deve ativar a versão 2 adicionando rules_version = '2';
na parte de cima das
suas regras de segurança:
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>;
}
}
}
É permitido ter no máximo um caractere curinga recursivo por instrução de correspondência. No entanto, na versão 2, é possível posicioná-lo em qualquer lugar da instrução. Exemplo:
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>;
}
}
}
Se você usar consultas do grupo de coleções, será necessário utilizar a versão 2. Consulte Como proteger as consultas do grupo de coleções.
Sobreposição de instruções de correspondência
É possível que um documento corresponda a mais de uma instrução match
. No caso de
várias expressões allow
corresponderem a uma solicitação, o acesso será permitido
se qualquer uma das condições for 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 or subcollections.
match /cities/{document=**} {
allow read, write: if true;
}
}
}
No exemplo acima, todas as leituras e gravações da coleção cities
serão
permitidas porque a segunda regra é sempre true
, mesmo que a primeira
regra seja sempre false
.
Limites da regra de segurança
Ao trabalhar com regras de segurança, observe os seguintes limites:
Limite | Detalhes |
---|---|
Número máximo de chamadas exists() , get() e getAfter() por solicitação. |
Ao exceder qualquer um desses limites, ocorrerá um erro de permissão negada. Algumas chamadas de acesso a documentos podem ser armazenadas em cache. Elas não entram na conta dos limites. |
Máxima profundidade da instrução match aninhada |
10 |
Tamanho do caminho máximo, em segmentos de caminho, permitido em um grupo de instruções
match aninhadas |
100 |
Número máximo de variáveis de captura de caminho permitidas em um conjunto de
instruções match aninhadas |
20 |
Profundidade máxima da chamada da função | 20 |
Número máximo de argumentos de função | 7 |
Número máximo de vinculações de variáveis let por função |
10 |
Número máximo de chamadas de função recorrentes ou cíclicas | 0 (não permitido) |
Número máximo de expressões avaliadas por solicitação | 1.000 |
Tamanho máximo de um conjunto de regras | Os conjuntos de regras precisam atender a dois limites de tamanho:
|
Cloud Storage
Estrutura básica
As regras de segurança do Firebase no Firestore e no Cloud Storage usam a estrutura e sintaxe a seguir:
service <<name>> {
// Match the resource path.
match <<path>> {
// Allow the request if the following conditions are true.
allow <<methods>> : if <<condition>>
}
}
Veja abaixo os principais conceitos que você precisa conhecer antes de criar as regras.
- Solicitação: o método ou os métodos invocados na instrução
allow
que você permite serem executados. Os métodos padrão são:get
,list
,create
,update
edelete
. Os métodos de conveniênciaread
ewrite
permitem amplo acesso de leitura e gravação no banco de dados ou caminho de armazenamento especificado. - Caminho: o banco de dados ou o local de armazenamento, representado como um caminho de URI.
- Regra: a instrução
allow
, que inclui uma condição que permite uma solicitação se ela for avaliada como verdadeira.
Caminhos correspondentes
Com as regras de segurança match
do Cloud Storage, você verifica a correspondência dos caminhos usados para acessar os arquivos do serviço. As regras match
podem corresponder aos caminhos exatos ou curinga, e as regras também podem ser aninhadas. Se nenhuma regra de correspondência permite um método de solicitação ou se a
condição é avaliada como false
, a solicitação é negada.
Correspondências exatas
// 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>; }
Correspondências aninhadas
// 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>; } }
Correspondências de caractere curinga
Use regras match
para definir um padrão correspondente com caracteres curinga. Um curinga é uma
variável nomeada que representa uma única string, como
profilePhoto.png
, ou vários segmentos de caminho, como
images/profilePhoto.png
.
Para criar um caractere curinga, basta colocar o nome do curinga entre chaves, como
{string}
. Um caractere curinga com vários segmentos pode ser declarado adicionando =**
ao
nome do curinga, como {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>; } }
Se um arquivo atende a várias regras de correspondência, o OR
do resultado de todas as avaliações é fornecido. Isso significa que, se alguma regra corresponder a true
, o
resultado será true
.
Nas regras acima, o arquivo "images/profilePhoto.png" poderá ser lido se condition
ou other_condition
forem avaliados como verdadeiros, enquanto o arquivo "images/users/user:12345/profilePhoto.png" estará sujeito apenas ao resultado de other_condition
.
Uma variável curinga pode ser referenciada de dentro do match
, basta fornecer a autorização do nome do arquivo ou caminho:
// Another way to restrict the name of a file match /images/{imageId} { allow read: if imageId == "profilePhoto.png"; }
As regras de segurança do Cloud Storage não são aplicadas em cascata e só são avaliadas se o caminho da solicitação corresponder a um caminho com regras especificadas.
Avaliação de solicitação
Uploads, downloads, alterações em metadados e exclusões são avaliados com a
request
enviada ao Cloud Storage. A variável request
contém o
caminho do arquivo em que a solicitação está sendo realizada, o horário em que a solicitação é
recebida e o novo valor resource
se a solicitação for uma gravação. Os cabeçalhos HTTP
e o estado de autenticação também são incluídos.
O objeto request
também contém o ID exclusivo do usuário e o
payload do Firebase Authentication no objeto request.auth
, que será explicado
em mais detalhes na seção Authentication
da documentação.
Veja uma lista completa de propriedades no objeto request
:
Propriedade | Tipo | Descrição |
---|---|---|
auth |
map<string, string> | Quando um usuário está conectado, fornece uid , o ID exclusivo do usuário, e token , um mapa de declarações JWT do Firebase Authentication. Caso contrário, será
null . |
params |
map<string, string> | Mapa contendo os parâmetros de consulta da solicitação. |
path |
path | Um path que representa o caminho em que a solicitação está sendo
realizada. |
resource |
map<string, string> | O novo valor do recurso, presente apenas nas solicitações write .
|
time |
timestamp | Um carimbo de data/hora que representa o horário do servidor em que a solicitação é avaliada. |
Avaliação de recurso
Durante a avaliação de regras, é possível também examinar os metadados do arquivo que está sendo modificado, excluído ou transferido por upload ou download. Crie regras complexas e eficazes, como permitir o upload somente de arquivos com um conteúdo específico ou apenas a exclusão de arquivos maiores com um tamanho determinado.
As regras de segurança do Firebase para Cloud Storage fornecem metadados de arquivos no objeto resource
,
que contém pares de chave/valor dos metadados exibidos em um objeto do
Cloud Storage. Essas propriedades podem ser inspecionadas em solicitações read
ou write
para garantir a integridade dos dados.
Em solicitações write
(como uploads, atualizações de metadados e exclusões), além do objeto resource
, que contém metadados do arquivo que existe atualmente no caminho da solicitação, você também pode usar o objeto request.resource
, que contém um subconjunto dos metadados de arquivos a serem gravados se a gravação for permitida. Use esses dois valores para garantir a integridade dos dados
ou para aplicar restrições no aplicativo, como de tipo ou tamanho.
Veja uma lista completa de propriedades no objeto resource
:
Propriedade | Tipo | Descrição |
---|---|---|
name |
string | Nome completo do objeto. |
bucket |
string | Nome do bucket do objeto. |
generation |
int | A geração de objetos do Google Cloud Storage desse objeto. |
metageneration |
int | A metageração de objetos do Google Cloud Storage desse objeto. |
size |
int | Tamanho do objeto em bytes. |
timeCreated |
timestamp | Timestamp que indica o horário em que um objeto foi criado. |
updated |
timestamp | Carimbo de data/hora que indica o horário em que um objeto foi atualizado pela última vez. |
md5Hash |
string | Hash MD5 do objeto. |
crc32c |
string | Hash crc32c do objeto. |
etag |
string | Etag associada ao objeto. |
contentDisposition |
string | Disposição do conteúdo associada ao objeto. |
contentEncoding |
string | Codificação do conteúdo associada ao objeto. |
contentLanguage |
string | Idioma do conteúdo associado ao objeto. |
contentType |
string | Tipo do conteúdo associado ao objeto. |
metadata |
map<string, string> | Outros pares de chave/valor de metadados personalizados especificados pelo desenvolvedor. |
request.resource
contém tudo isso, exceto generation
,
metageneration
, etag
, timeCreated
e updated
.
Limites das regras de segurança
Ao trabalhar com regras de segurança, observe os seguintes limites:
Limite | Detalhes |
---|---|
Número máximo de chamadas firestore.exists()
firestore.get() por solicitação. |
2 para solicitações de documento único e de consulta. Ao exceder esse limite, ocorrerá um erro de permissão negada. As chamadas de acesso aos mesmos documentos podem ser armazenadas em cache. Elas não entram na conta dos limites. |
Exemplo completo
Reúna todos esses recursos e crie um exemplo completo de regras para uma solução de armazenamento de imagens:
service firebase.storage { match /b/{bucket}/o { match /images { // Cascade read to any image type at any path match /{allImages=**} { allow read; } // 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) File name (stored in imageId wildcard variable) is less than 32 characters match /{imageId} { allow write: if request.resource.size < 5 * 1024 * 1024 && request.resource.contentType.matches('image/.*') && request.resource.contentType == resource.contentType && imageId.size() < 32 } } } }
Realtime Database
Estrutura básica
No Realtime Database, as regras de segurança do Firebase consistem em expressões semelhantes a JavaScript contidas em um documento JSON.
Elas usam a seguinte sintaxe:
{
"rules": {
"<<path>>": {
// Allow the request if the condition for each method is true.
".read": <<condition>>,
".write": <<condition>>,
".validate": <<condition>>
}
}
}
Existem três elementos básicos na regra:
- Caminho: o local do banco de dados. Ele corresponde à estrutura JSON do seu banco de dados.
- Solicitação: são os métodos que a regra usa para conceder acesso. As regras
read
ewrite
concedem amplo acesso de leitura e gravação, enquanto as regrasvalidate
atuam como uma verificação secundária para conceder acesso com base nos dados recebidos ou atuais. - Condição: a condição que permitirá uma solicitação se ela for avaliada como verdadeira.
Como as regras se aplicam aos caminhos
No Realtime Database, as regras se aplicam atomicamente, o que significa que as regras em nós pais de nível superior modificam aquelas em nós filhos, e as regras em um nó mais profundo não concedem acesso a um caminho pai. Caso você já tenha concedido acesso para um dos caminhos pais, não será possível refinar ou revogar o acesso aos dados em um caminho mais profundo na sua estrutura de banco de dados.
Suponha as seguintes regras:
{ "rules": { "foo": { // allows read to /foo/* ".read": "data.child('baz').val() === true", "bar": { // ignored, since read was allowed already ".read": false } } } }
Essa estrutura de segurança permite que /bar/
seja lido sempre que /foo/
contiver um baz
filho com o valor true
.
A regra ".read": false
em /foo/bar/
não tem
efeito aqui porque o acesso não pode ser revogado por um caminho filho.
Mesmo que isso não pareça intuitivo de imediato, essa é uma parte eficiente da Linguagem das regras e permite implementar privilégios de acesso muito complexos com o mínimo esforço. Isso é particularmente útil para a segurança com base no usuário.
No entanto, as regras .validate
não são aplicadas em cascata. Todas as regras de validação precisam ser atendidas em todos os níveis da hierarquia para que uma gravação seja permitida.
Além disso, como as regras não se aplicam novamente a um caminho pai, a operação de leitura ou gravação vai falhar se não houver uma regra que conceda acesso no local solicitado ou em um local pai. Mesmo que cada caminho filho afetado seja acessível, a leitura no local pai falhará totalmente. Suponha esta estrutura:
{ "rules": { "records": { "rec1": { ".read": true }, "rec2": { ".read": false } } } }
Sem entender que as regras são avaliadas atomicamente, pode parecer que buscar o caminho /records/
retornaria rec1
, mas não rec2
. O resultado real, no entanto, é um erro:
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
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 }];
Swift
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 })
Java
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 }); });
REST
curl https://docs-examples.firebaseio.com/rest/records/ # response returns a PERMISSION_DENIED error
Como a operação de leitura em /records/
é atômica, e não há regra de leitura que conceda acesso a todos os dados em /records/
, isso gerará um erro PERMISSION_DENIED
. Se avaliarmos essa regra no simulador de segurança do Console do Firebase, vemos que a operação de leitura foi negada:
Attempt to read /records with auth=Success(null) / /records No .read rule allowed the operation. Read was denied.
A operação foi negada porque nenhuma regra de leitura permitiu acesso ao caminho /records/
, mas a regra para rec1
nunca foi avaliada porque não estava no caminho solicitado. Para buscar rec1
, precisamos acessá-lo diretamente:
JavaScript
var db = firebase.database(); db.ref("records/rec1").once("value", function(snap) { // SUCCESS! }, function(err) { // error callback is not called });
Objective-C
FIRDatabaseReference *ref = [[FIRDatabase database] reference]; [[ref child:@"records/rec1"] observeSingleEventOfType:FEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) { // SUCCESS! }];
Swift
var ref = FIRDatabase.database().reference() ref.child("records/rec1").observeSingleEventOfType(.Value, withBlock: { snapshot in // SUCCESS! })
Java
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 } });
REST
curl https://docs-examples.firebaseio.com/rest/records/rec1 # SUCCESS!
Variável de local
As regras do Realtime Database são compatíveis com uma variável $location
para corresponder a segmentos de caminho. Use o prefixo $
na frente do seu segmento de caminho para corresponder sua regra a qualquer nó filho no caminho.
{
"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')"
}
}
}
}
}
Também é possível usar o $variable
em paralelo com nomes de caminho constantes.
{
"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 }
}
}
}