보안 규칙에서 화재 기준 비율 제한?
저는 제 첫 번째 오픈 저장소 프로젝트인 EpChat을 시작했고, 사람들은 즉시 요청을 쇄도하기 시작했습니다.
Firebase에는 보안 규칙에서 제한 요청을 등급으로 지정할 수 있는 방법이 있습니까?요청한 시간과 이전에 작성한 데이터의 시간을 사용하여 수행할 수 있는 방법이 있을 것으로 예상되지만 문서에서 이 작업을 수행하는 방법에 대한 내용을 찾을 수 없습니다.
현재 보안 규칙은 다음과 같습니다.
{
"rules": {
"rooms": {
"$RoomId": {
"connections": {
".read": true,
".write": "auth.username == newData.child('FBUserId').val()"
},
"messages": {
"$any": {
".write": "!newData.exists() || root.child('rooms').child(newData.child('RoomId').val()).child('connections').hasChild(newData.child('FBUserId').val())",
".validate": "newData.hasChildren(['RoomId','FBUserId','userName','userId','message']) && newData.child('message').val().length >= 1",
".read": "root.child('rooms').child(data.child('RoomId').val()).child('connections').hasChild(data.child('FBUserId').val())"
}
},
"poll": {
".write": "auth.username == newData.child('FBUserId').val()",
".read": true
}
}
}
}
}
전체 Rooms 개체에 대해 DB에 대한 쓰기(및 읽기)를 속도 제한하여 초당 1개의 요청만 수행할 수 있습니다(예:
이 방법은 사용자가 마지막으로 메시지를 게시한 시간을 감사하는 것입니다.그런 다음 감사 값을 기준으로 각 메시지가 게시되는 시간을 적용할 수 있습니다.
{
"rules": {
// this stores the last message I sent so I can throttle them by timestamp
"last_message": {
"$user": {
// timestamp can't be deleted or I could just recreate it to bypass our throttle
".write": "newData.exists() && auth.uid === $user",
// the new value must be at least 5000 milliseconds after the last (no more than one message every five seconds)
// the new value must be before now (it will be since `now` is when it reaches the server unless I try to cheat)
".validate": "newData.isNumber() && newData.val() === now && (!data.exists() || newData.val() > data.val()+5000)"
}
},
"messages": {
"$message_id": {
// message must have a timestamp attribute and a sender attribute
".write": "newData.hasChildren(['timestamp', 'sender', 'message'])",
"sender": {
".validate": "newData.val() === auth.uid"
},
"timestamp": {
// in order to write a message, I must first make an entry in timestamp_index
// additionally, that message must be within 500ms of now, which means I can't
// just re-use the same one over and over, thus, we've effectively required messages
// to be 5 seconds apart
".validate": "newData.val() >= now - 500 && newData.val() === data.parent().parent().parent().child('last_message/'+auth.uid).val()"
},
"message": {
".validate": "newData.isString() && newData.val().length < 500"
},
"$other": {
".validate": false
}
}
}
}
}
이 바이올린에서 그것이 작동하는 것을 보세요.다음은 바이올린의 요점입니다.
var fb = new Firebase(URL);
var userId; // log in and store user.uid here
// run our create routine
createRecord(data, function (recordId, timestamp) {
console.log('created record ' + recordId + ' at time ' + new Date(timestamp));
});
// updates the last_message/ path and returns the current timestamp
function getTimestamp(next) {
var ref = fb.child('last_message/' + userId);
ref.set(Firebase.ServerValue.TIMESTAMP, function (err) {
if (err) { console.error(err); }
else {
ref.once('value', function (snap) {
next(snap.val());
});
}
});
}
function createRecord(data, next) {
getTimestamp(function (timestamp) {
// add the new timestamp to the record data
var data = {
sender: userId,
timestamp: timestamp,
message: 'hello world'
};
var ref = fb.child('messages').push(data, function (err) {
if (err) { console.error(err); }
else {
next(ref.name(), timestamp);
}
});
})
}
저는 댓글을 달만큼의 명성은 없지만, 빅터의 댓글에 동의합니다.를 삽입할 경우fb.child('messages').push(...)
고리 모양으로(즉, 고리 모양으로).for (let i = 0; i < 100; i++) {...}
그러면 (500ms 창 프레임에서) 60-80개의 메시지를 성공적으로 푸시할 수 있습니다.
카토의 해결책에 영감을 받아, 저는 다음과 같이 규칙을 수정할 것을 제안합니다.
rules: {
users: {
"$uid": {
"timestamp": { // similar to Kato's answer
".write": "auth.uid === $uid && newData.exists()"
,".read": "auth.uid === $uid"
,".validate": "newData.hasChildren(['time', 'key'])"
,"time": {
".validate": "newData.isNumber() && newData.val() === now && (!data.exists() || newData.val() > data.val() + 1000)"
}
,"key": {
}
}
,"messages": {
"$key": { /// this key has to be the same is the key in timestamp (checked by .validate)
".write": "auth.uid === $uid && !data.exists()" ///only 'create' allow
,".validate": "newData.hasChildren(['message']) && $key === root.child('/users/' + $uid + '/timestamp/key').val()"
,"message": { ".validate": "newData.isString()" }
/// ...and any other datas such as 'time', 'to'....
}
}
}
}
}
.js 코드는 getTimestamp가 다음 콜백에 {time: number, key: string}을(를) 반환한다는 점을 제외하면 Kato의 솔루션과 매우 유사합니다.그러면 우리는 그냥ref.update({[key]: data})
이 솔루션을 사용하면 500ms의 시간 창을 피할 수 있으므로 클라이언트가 500ms 내에 메시지를 전송할 수 있을 정도로 빨라야 한다는 걱정을 할 필요가 없습니다.여러 개의 쓰기 요청이 전송된 경우(스팸), 해당 요청은 에서 하나의 단일 키에만 쓸 수 있습니다.messages
선택적으로 의 생성 전용 규칙messages
그런 일이 일어나지 않도록 합니다.
저는 카토의 답변이 마음에 들었지만 단순히 for 루프를 사용하여 500ms 창 사이의 채팅을 범람시키는 악의적인 사용자를 고려하지 않았습니다.저는 가능성을 제거하는 이 변형을 제안합니다.
{
"rules": {
"users": {
"$uid": {
"rateLimit": {
"lastMessage": {
// newData.exists() ensures newData is not null and prevents deleting node
// and $uid === auth.uid ensures the user writing this child node is the owner
".write": "newData.exists() && $uid === auth.uid",
// newData.val() === now ensures the value written is the current timestamp
// to avoid tricking the rules writting false values
// and (!data.exists() || newData.val() > data.val() + 5000)
// ensures no data exists currently in the node. Otherwise it checks if the
// data that will overwrite the node is a value higher than the current timestamp
// plus the value that will rate limit our messages expressed in milliseconds.
// In this case a value of 5000 means that we can only send a message if
// the last message we sent was more than 5 seconds ago
".validate": "newData.val() === now && (!data.exists() || newData.val() > data.val() + 5000)"
}
}
}
},
"messages": {
"$messageId": {
// This rule ensures that we write lastMessage node avoiding just sending the message without
// registering a new timestamp
".write": "newData.parent().parent().child('users').child(auth.uid).child('rateLimit').child('lastMessage').val() === now",
// This rule ensures that we have all the required message fields
".validate": "newData.hasChildren(['timestamp', 'uid', 'message'])",
"uid": {
// This rule ensures that the value written is the id of the message sender
".validate": "newData.val() === auth.uid"
},
"timestamp": {
// This rule ensures that the message timestamp can't be modified
".write": "!data.exists()",
// This rule ensures that the value written is the current timestamp
".validate": "newData.val() === now"
},
"message": {
// This rule ensures that the value written is a string
".validate": "newData.isString()"
},
"$other": {
// This rule ensures that we cant write other fields in the message other than
// the explicitly declared above
".validate": false
}
}
}
}
}
코드 구현은 여러 위치에 걸쳐 원자적 쓰기를 사용합니다.하나의 유효성 검사에 실패하면 작업이 완료되지 않고 데이터베이스에서 작업이 수행되지 않습니다.
function sendMessage(message) {
const database = firebase.database();
const pushId = database.ref().child("messages").push().key;
const userId = firebase.auth().currentUser.uid;
const timestampPlaceholder = firebase.database.ServerValue.TIMESTAMP;
let updates = {};
updates["messages/" + pushId] = {
uid: userId,
timestamp: timestampPlaceholder,
message: message,
};
updates[`users/${userId}/rateLimit/lastMessage`] = timestampPlaceholder;
database.ref().update(updates);
}
기존 응답은 (1) 타임스탬프를 표시하고 (2) 표시된 타임스탬프를 실제 쓰기에 첨부하는 두 가지 데이터베이스 업데이트를 사용합니다.Kato의 답변은 500ms의 시간 창이 필요한 반면 ChiNhan의 답변은 다음 키를 기억해야 합니다.
단일 데이터베이스 업데이트에서 이 작업을 수행하는 더 간단한 방법이 있습니다.메소드를 사용하여 한 번에 여러 값을 데이터베이스에 쓰는 것이 좋습니다.보안 규칙은 쓰기가 할당량을 초과하지 않도록 쓰기 값을 검증합니다.할당량은 다음 값 쌍으로 정의됩니다.quotaTimestamp
그리고.postCount
.postCount
는 1분 입니다.quotaTimestamp
보안 규칙은 postCount가 특정 값을 초과할 경우 다음 쓰기를 거부합니다.됩니다.postCount " quotaTimestamp " 1번째 quotaTimestamp " 입니다.
다음은 새 메시지를 게시하는 방법입니다.
function postMessage(user, message) {
const now = Date.now() + serverTimeOffset;
if (!user.quotaTimestamp || user.quotaTimestamp + 60 * 1000 < now) {
// Resets the quota when 1 minute has elapsed since the quotaTimestamp.
user.quotaTimestamp = database.ServerValue.TIMESTAMP;
user.postCount = 0;
}
user.postCount++;
const values = {};
const messageId = // generate unique id
values[`users/${user.uid}/quotaTimestamp`] = user.quotaTimestamp;
values[`users/${user.uid}/postCount`] = user.postCount;
values[`messages/${messageId}`] = {
sender: ...,
message: ...,
...
};
return this.db.database.ref().update(values);
}
분당 게시물 수를 최대 5개로 제한하는 보안 규칙:
{
"rules": {
"users": {
"$uid": {
".read": "$uid === auth.uid",
".write": "$uid === auth.uid && newData.child('postCount').val() <= 5",
"quotaTimestamp": {
// Only allow updating quotaTimestamp if it's staler than 1 minute.
".validate": "
newData.isNumber()
&& (newData.val() === now
? (data.val() + 60 * 1000 < now)
: (data.val() == newData.val()))"
},
"postCount": {
// Only allow postCount to be incremented by 1
// or reset to 1 when the quotaTimestamp is being refreshed.
".validate": "
newData.isNumber()
&& (data.exists()
? (data.val() + 1 === newData.val()
|| (newData.val() === 1
&& newData.parent().child('quotaTimestamp').val() === now))
: (newData.val() === 1))"
},
"$other": { ".validate": false }
}
},
"messages": {
...
}
}
}
참고: 시계 왜곡을 방지하려면 serverTimeOffset을 유지해야 합니다.
언급URL : https://stackoverflow.com/questions/24830079/firebase-rate-limiting-in-security-rules
'programing' 카테고리의 다른 글
Git: 새 원격 분기를 볼 수 없습니다. (0) | 2023.06.25 |
---|---|
스프링 부트 테스트 - 테스트 속성을 찾을 수 없음 (0) | 2023.06.25 |
Python을 사용하여 ssh를 통해 명령 수행 (0) | 2023.06.25 |
스프링 데이터 탄력적 검색을 사용한 탄력적 검색 Rest 클라이언트 (0) | 2023.06.25 |
Oracle Optimizer가 동일한 SELECT에서 여러 힌트를 사용합니까? (0) | 2023.06.25 |