Как реализовать ограничение скорости записи в правилах безопасности Cloud Firestore? - PullRequest
2 голосов
/ 07 июня 2019

У меня есть приложение, которое использует Firebase SDK для прямой связи с Cloud Firestore из приложения. Мой код обеспечивает запись данных только через разумные интервалы Но злонамеренный пользователь может взять данные конфигурации из моего приложения и использовать их для записи бесконечного потока данных в мою базу данных.

Как я могу убедиться, что пользователь может писать слова только раз в несколько секунд, без необходимости писать какой-либо код на стороне сервера.

1 Ответ

7 голосов
/ 07 июня 2019

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

Скажем, у нас есть коллекция users, и этоу каждого документа есть идентификатор с UID пользователя.Эти правила безопасности гарантируют, что пользователь может писать только свой собственный документ, но не чаще, чем раз в 5 секунд:

match /users/{document=**} {
  allow create: if isMine() && hasTimestamp();
  allow update: if isMine() && hasTimestamp() && isCalm();
  function isMine() {
    return request.resource.id == request.auth.uid;
  }
  function hasTimestamp() {
    return request.resource.data.timestamp == request.time;
  }
  function isCalm() {
    return request.time > resource.data.timestamp + duration.value(5, 's');
  }
}

Может помочь пошаговое руководство:

  1. Первая строка определяет область действия правил в них, поэтому эти правила применяются ко всем документам в коллекции /users.

  2. Пользователь может создать документ, если он принадлежит ему (isMine()), если у него есть временная метка (hasTimestamp()).

  3. Пользователь может обновить документ, если он у него есть, имеет временную метку, и если он тоже не пишетчасто (isCalm()).

    Давайте посмотрим на все три функции по очереди ...

  4. Функция isMine() проверяет, совпадает ли идентификатор документа спользователь, который выполняет операцию записи.Поскольку auth.uid заполняется Firebase автоматически в зависимости от пользователя, вошедшего в систему, злоумышленник не может подделать это значение.

  5. Функция hasTimestamp() проверяет,записываемый документ (request.resource) имеет поле метки времени, и если да, то если эта метка времени совпадает с текущим временем на стороне сервера.Это означает, что в коде вам нужно будет указать FieldValue.serverTimestamp(), чтобы запись была приемлемой.Таким образом, вы можете записать только текущую временную метку на стороне сервера, и злонамеренный пользователь не сможет передать другую временную метку.

  6. Функция isCalm() гарантирует, что пользователь не пишетслишком часто.Это позволяет производить запись, если разница между значениями timestamp в существующем документе (resource.data.timestamp) и документе (request.resource.data.timestamp), который в настоящее время записывается, составляет не менее 5 секунд.

За комментарий Дуга:

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

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


Вот jsbin того, как я проверял эти правила: https://jsbin.com/kejobej/2/edit?js,console. С этим кодом:

firebase.auth().signInAnonymously().then(function(auth) {
  var doc = collection.doc(auth.user.uid);
  doc.set({
    timestamp: firebase.firestore.FieldValue.serverTimestamp()
  }).then(function() {
    console.log("Written at "+new Date());
  }).catch(function(error) {
    console.error(error.code);
  })
})

Если вы несколько раз нажмете кнопку Run , онаразрешит следующую запись, только если прошло не менее 5 секунд с момента предыдущей записи.

Когда я нажимаю кнопку «Выполнить» примерно раз в секунду, я получаю:

«Записано вЧт 06 июня 2019 20:20:19 GMT-0700 (тихоокеанское летнее время) "

" разрешение запрещено "

" разрешение запрещено "

" разрешение запрещено"

" отказано в разрешении "

" Написано в четверг 06 июня 2019 20:20:24 GMT-0700 (тихоокеанское летнее время) "

" отказано в разрешении "

"отказано в разрешении"

"отказано в разрешении"

"отказано в разрешении"

"Написано в четверг, 06 июня 2019 20:20:30 GMT-0700(Тихоокеанское летнее время) "


Последний пример - ограничение скорости записи для каждого пользователя.Допустим, у вас есть приложение для социальных сетей, в котором пользователи создают сообщения, а у каждого пользователя есть свой профиль.Итак, у нас есть две коллекции: posts и users.И мы хотим убедиться, что пользователь может создавать новые сообщения не чаще, чем раз в 5 секунд.

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

Отличительной особенностью является то, что мы храним метку времени в их профиле пользователя (/users/$uid), даже когда они создают новый почтовый документ (/posts/$newid). Поскольку обе эти записи должны выполняться как одна, на этот раз мы будем использовать BatchedWrite:

var root = firebase.firestore();
var users = root.collection("users");
var posts = root.collection("posts");

firebase.auth().signInAnonymously().then(function(auth) {
  var batch = db.batch();
  var userDoc = users.doc(auth.user.uid);
  batch.set(userDoc, {
    timestamp: firebase.firestore.FieldValue.serverTimestamp()
  })
  batch.set(posts.doc(), { 
    title: "Hello world"
  });
  batch.commit().then(function() {
    console.log("Written at "+new Date());
  }).catch(function(error) {
    console.error(error.code);
  })
})

Итак, партия пишет две вещи:

  • Записывает текущее время на стороне сервера в профиль пользователя.
  • Создает новый пост с полем заголовка.

Правила безопасности верхнего уровня для этого (как сказано) в значительной степени такие же, как и раньше:

match /users/{user} {
  allow write: if isMine() && hasTimestamp();
}
match /posts/{post} {
    allow write: if isCalm();
}

Таким образом, пользователь может записывать в документ профиля, если он принадлежит ему, и если этот документ содержит временную метку, равную текущему времени на стороне сервера / запроса. Пользователь может написать сообщение, если оно не было опубликовано слишком недавно.

Реализация isMine() и hasTimstamp() такая же, как и раньше. Но реализация isCalm() теперь ищет документ профиля пользователя как до, так и после операции записи, чтобы выполнить его проверку метки времени:

function isCalm() {
    return getAfter(/databases/$(database)/documents/users/$(request.auth.uid)).data.timestamp
              > get(/databases/$(database)/documents/users/$(request.auth.uid)).data.timestamp + duration.value(5, 's');
}

Путь к get() и getAfter(), к сожалению, должен быть абсолютным и полностью квалифицированным, но сводится к следующему:

// These won't work, but are easier to read.    
function isCalm() {
  return getAfter(/users/$(request.auth.uid)).data.timestamp
            > get(/users/$(request.auth.uid)).data.timestamp + duration.value(5, 's');
}

Несколько замечаний:

  • Как и прежде, мы сравниваем две метки времени. Но здесь мы читаем метки времени из разных документов.
  • Это требует чтения двух дополнительных документов, что означает, что с вас будет взиматься плата за две дополнительные операции чтения. Если цель ограничения скорости заключается в том, чтобы не взимать плату за операции записи злоумышленника, возможно, это не то решение, которое вы ищете.
...