【发布时间】:2013-10-30 18:24:49
【问题描述】:
我正在使用 Node 和 Express JS 构建网站,并希望限制无效的登录尝试。既防止在线破解,又减少不必要的数据库调用。我可以通过哪些方式来实现它?
【问题讨论】:
标签: node.js security login express brute-force
我正在使用 Node 和 Express JS 构建网站,并希望限制无效的登录尝试。既防止在线破解,又减少不必要的数据库调用。我可以通过哪些方式来实现它?
【问题讨论】:
标签: node.js security login express brute-force
也许这样的事情可能会帮助您入门。
var failures = {};
function tryToLogin() {
var f = failures[remoteIp];
if (f && Date.now() < f.nextTry) {
// Throttled. Can't try yet.
return res.error();
}
// Otherwise do login
...
}
function onLoginFail() {
var f = failures[remoteIp] = failures[remoteIp] || {count: 0, nextTry: new Date()};
++f.count;
f.nextTry.setTime(Date.now() + 2000 * f.count); // Wait another two seconds for every failed attempt
}
function onLoginSuccess() { delete failures[remoteIp]; }
// Clean up people that have given up
var MINS10 = 600000, MINS30 = 3 * MINS10;
setInterval(function() {
for (var ip in failures) {
if (Date.now() - failures[ip].nextTry > MINS10) {
delete failures[ip];
}
}
}, MINS30);
【讨论】:
failures 删除失败的 IP。
所以在进行了一些搜索之后,我找不到我喜欢的解决方案,所以我根据 Trevor 的解决方案和 express-brute 编写了自己的解决方案。你可以找到它here。
【讨论】:
rate-limiter-flexible 包含 Redis 或 Mongo 的软件包,用于分布式应用程序和内存中或集群帮助
这里是 Redis 的示例
const { RateLimiterRedis } = require('rate-limiter-flexible');
const Redis = require('ioredis');
const redisClient = new Redis({
options: {
enableOfflineQueue: false
}
});
const opts = {
redis: redisClient,
points: 5, // 5 points
duration: 15 * 60, // Per 15 minutes
blockDuration: 15 * 60, // block for 15 minutes if more than points consumed
};
const rateLimiter = new RateLimiterRedis(opts);
app.post('/auth', (req, res, next) => {
// Consume 1 point for each login attempt
rateLimiter.consume(req.connection.remoteAddress)
.then((data) => {
const loggedIn = loginUser();
if (!loggedIn) {
// Message to user
res.status(400).send(data.remainingPoints + ' attempts left');
} else {
// successful login
}
})
.catch((rejRes) => {
// Blocked
const secBeforeNext = Math.ceil(rejRes.msBeforeNext / 1000) || 1;
res.set('Retry-After', String(secBeforeNext));
res.status(429).send('Too Many Requests');
});
});
【讨论】:
const loggedIn = loginUser() 放在rateLimiter.consume.then() 块中。这将正确阻止未来请求的登录尝试。
好的,我在 mongoose 和 expressjs 中找到了 max login attemp 密码错误的解决方案。有一个解决方案。 *首先我们将定义用户模式 *其次,我们将在错误密码处理函数上定义最大登录。 *第三次创建登录api时,我们会检查这个函数有多少次用户使用错误的密码登录。所以准备好代码
var config = require('../config');
var userSchema = new mongoose.Schema({
email: { type: String, unique: true, required: true },
password: String,
verificationToken: { type: String, unique: true, required: true },
isVerified: { type: Boolean, required: true, default: false },
passwordResetToken: { type: String, unique: true },
passwordResetExpires: Date,
loginAttempts: { type: Number, required: true, default: 0 },
lockUntil: Number,
role: String
});
userSchema.virtual('isLocked').get(function() {
return !!(this.lockUntil && this.lockUntil > Date.now());
});
userSchema.methods.incrementLoginAttempts = function(callback) {
console.log("lock until",this.lockUntil)
// if we have a previous lock that has expired, restart at 1
var lockExpired = !!(this.lockUntil && this.lockUntil < Date.now());
console.log("lockExpired",lockExpired)
if (lockExpired) {
return this.update({
$set: { loginAttempts: 1 },
$unset: { lockUntil: 1 }
}, callback);
}
// otherwise we're incrementing
var updates = { $inc: { loginAttempts: 1 } };
// lock the account if we've reached max attempts and it's not locked already
var needToLock = !!(this.loginAttempts + 1 >= config.login.maxAttempts && !this.isLocked);
console.log("needToLock",needToLock)
console.log("loginAttempts",this.loginAttempts)
if (needToLock) {
updates.$set = { lockUntil: Date.now() + config.login.lockoutHours };
console.log("config.login.lockoutHours",Date.now() + config.login.lockoutHours)
}
//console.log("lockUntil",this.lockUntil)
return this.update(updates, callback);
};
这是我的登录函数,我们检查了错误密码的最大登录尝试次数。所以我们将调用此函数
User.findOne({ email: email }, function(err, user) {
console.log("i am aurhebengdfhdbndbcxnvndcvb")
if (!user) {
return done(null, false, { msg: 'No user with the email ' + email + ' was found.' });
}
if (user.isLocked) {
return user.incrementLoginAttempts(function(err) {
if (err) {
return done(err);
}
return done(null, false, { msg: 'You have exceeded the maximum number of login attempts. Your account is locked until ' + moment(user.lockUntil).tz(config.server.timezone).format('LT z') + '. You may attempt to log in again after that time.' });
});
}
if (!user.isVerified) {
return done(null, false, { msg: 'Your email has not been verified. Check your inbox for a verification email.<p><a href="/user/verify-resend/' + email + '" class="btn waves-effect white black-text"><i class="material-icons left">email</i>Re-send verification email</a></p>' });
}
user.comparePassword(password, function(err, isMatch) {
if (isMatch) {
return done(null, user);
}
else {
user.incrementLoginAttempts(function(err) {
if (err) {
return done(err);
}
return done(null, false, { msg: 'Invalid password. Please try again.' });
});
}
});
});
}));
【讨论】:
看看这个:https://github.com/AdamPflug/express-brute
A brute-force protection middleware for express routes that rate-limits incoming requests, increasing the delay with each request in a fibonacci-like sequence.
【讨论】:
我自己想知道如何解决这个问题,但我尝试了以下方法,但我不确定它在性能和良好代码方面有多好。
基本上,我在我的架构中创建了一个名为“登录尝试”的标志并将其设置为 0
然后在登录过程中,我执行以下操作:比较密码,如果没问题,然后我登录。否则,每次用户输入错误密码时,我都会在我的数据库中增加登录尝试标志。如果登录尝试超过 3 次,我会显示一条错误消息,指出您已超过登录尝试。
现在到此为止一切正常,接下来就是将该标志切换为零的方法。
现在我使用 setTimeout 函数在 5 分钟后运行并将该标志切换为 0 并且它起作用了。
我主要关心的是:像这样使用 setTimeout 是否安全。
另一个问题是这将如何影响性能。
因此,就完成工作而言,它正在发挥作用,但就性能和最佳方法而言,我不确定。
【讨论】: