我正在开发一个 api,其中包括发送电子邮件以进行密码重置和电子邮件确认。以及“用户注册”等功能。
我正在尝试包含一个操作限制器,允许用户在给定的时间范围内执行这些操作的次数有限,以防止恶意使用。
起初我认为使用 IP 地址会很好,因为即使是恶意用户最终也会用完 IP 地址(至少我知道),但后来我意识到这可能会阻止在大型建筑物中的用户,并可能给 VPN 用户带来不便。
唯一标识未登录用户以限制他们对某些功能的操作的最佳方法是什么?这可能吗?FAANG 是如何处理这个问题的?
这是我在 nodejs 中编写的一个示例,如果有人对如何使其更加独特有任何反馈和/或想法,我会全力以赴。
const db = require('../../common/database');
// const ActionLimiterEnum = require('../../enums/action-limiter').actionLimiterEnum;
const NumberUtil = require('../../utils/number');
const ObjectUtil = require('../../utils/object');
// !==========================================================================================!
// This module has been put on hold until I can think of a way to uniquely identify users
// Major problem about this is that it may deny large groups of people whom use the same ip
// VPNS and/or large buildings
//
// Could cause more problems than it solves
// !==========================================================================================!
// Simple action limiter for how often a user can perform actions
// Needs to be saved to a database and not an instance because there may be multiple instances and/or they may be reset
// Object of action or "signIn"
async function actionLimiter(action,ip){
const d = {err: {code:0,message:""},res:{}}; let r,sql,vars;
r = await checkLimit(action,ip);
if(r.err.code) return r;
r = await incrementLimit(action,ip);
if(r.err.code) return r;
return d;
}
async function checkLimit(action,ip){
const d = {err: {code:0,message:""},res:{}}; let r,sql,vars;
if(action === "signIn"){
r = await checkLimit(ActionLimiterEnum.signInShortTerm,ip); if(r.err.code) return r;
r = await checkLimit(ActionLimiterEnum.signInMidTerm,ip); if(r.err.code) return r;
r = await checkLimit(ActionLimiterEnum.signInLongTerm,ip); if(r.err.code) return r;
return d;
}
const numberIp = NumberUtil.ipToNumber(ip);
var deleteDate = new Date();
deleteDate.setMilliseconds(deleteDate.getMilliseconds() - action.time);
sql = "DELETE FROM m_admin_action_limiter WHERE action_id = ? AND created_date <= ?";
vars = [action.id,deleteDate];
r = await db.query(sql,vars);
if(r.err.code) return r;
sql = "SELECT * FROM m_admin_action_limiter WHERE action_id = ? AND ip = ?";
vars = [action.id,numberIp];
r = await db.query(sql,vars);
if(r.err.code) return r;
if(r.res.length){
const results = ObjectUtil.toCamelCaseKeys(r.res[0]);
if(results.actionCount >= action.maxCount){
d.err.code = 1;
d.err.message = "Sorry this ip has performed this action too often please try again later. ";
switch(action.id){
case ActionLimiterEnum.signInShortTerm.id:
case ActionLimiterEnum.signInMidTerm.id:
case ActionLimiterEnum.signInLongTerm.id:
d.err.message += "If you're having trouble remembering your password you can reset it via email. ";
break;
default:
break;
}
d.err.actionLimited = true;
return d;
}
}
return d;
}
async function incrementLimit(action,ip){
const d = {err: {code:0,message:""},res:{}}; let r,sql,vars;
if(action === "signIn"){
r = await incrementLimit(ActionLimiterEnum.signInShortTerm,ip); if(r.err.code) return r;
r = await incrementLimit(ActionLimiterEnum.signInMidTerm,ip); if(r.err.code) return r;
r = await incrementLimit(ActionLimiterEnum.signInLongTerm,ip); if(r.err.code) return r;
return d;
}
const numberIp = NumberUtil.ipToNumber(ip);
const timenow = new Date();
sql = "SELECT admin_action_limiter_id FROM m_admin_action_limiter WHERE action_id = ? AND ip = ?";
vars = [action.id,numberIp];
r = await db.query(sql,vars);
if(r.err.code) return r;
if(r.res.length){
// update
const id = r.res[0]['admin_action_limiter_id']
sql = "UPDATE m_admin_action_limiter SET action_count = action_count + 1 WHERE admin_action_limiter_id = ?";
vars = [id];
r = await db.query(sql,vars);
if(r.err.code) return r;
}else{
// insert
sql = "INSERT INTO m_admin_action_limiter (action_id,ip,action_count,created_date) VALUES(?,?,?,?)";
vars = [action.id,numberIp,1,timenow];
r = await db.query(sql,vars);
if(r.err.code) return r;
}
return d;
}
module.exports = {
actionLimiter,
checkLimit,
incrementLimit,
Enum: ActionLimiterEnum,
};
// SQL
// -- -----------------------------------------------------
// -- Table `m_admin_action_limiter`
// -- -----------------------------------------------------
// CREATE TABLE IF NOT EXISTS `m_admin_action_limiter`(
// `admin_action_limiter_id` int(11) unsigned NOT NULL AUTO_INCREMENT PRIMARY KEY,
// `action_id` int(2) unsigned NOT NULL,
// `ip` int(11) unsigned NOT NULL,
// `action_count` unsigned int(11) DEFAULT 1,
// `created_date` DATETIME NOT NULL
// ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
// ALTER TABLE `m_admin_action_limiter`
// ADD CONSTRAINT m_admin_action_limiter_unique UNIQUE (`action_id`,`ip`);
// CREATE INDEX `created_date_index` ON `m_admin_action_limiter` (`created_date`);
// Enums
// time: (days * hours * minutes * seconds * milliseconds)
// time - amount of times they can try within the alotted count
// const actionLimiterEnum = {
// signInShortTerm: {
// id: 1,
// time: (1 * 1 * 60 * 60 * 1000), // 1 hour
// maxCount: 24,
// },
// signInMidTerm: {
// id: 2,
// time: (7 * 24 * 60 * 60 * 1000), // 7 days
// maxCount: 150,
// },
// signInLongTerm: {
// id: 3,
// time: (120 * 24 * 60 * 60 * 1000), // 120 days
// maxCount: 840,
// },
// authToken: {
// id: 4,
// time: (1 * 24 * 60 * 60 * 1000), // 1 day
// maxCount: 16,
// },
// createAccount: {
// id: 5,
// time: (90 * 24 * 60 * 60 * 1000), // 90 days
// maxCount: 3,
// },
// passwordCheck: {
// id: 6,
// time: (7 * 24 * 60 * 60 * 1000), // 1 week
// maxCount: 150,
// },
// }
// module.exports = {
// actionLimiterEnum,
// };