如何使用passport在我的应用中添加“记住我”功能

28

我需要一个像这个那样的登录时“记住我”复选框. 并且在使用passport之前添加中间件。

app.use(function(req, res, next) {
  if (req.method == 'POST' && req.url == '/login') {
    if (req.body.rememberme) {
      req.session.cookie.maxAge = 1000 * 60 * 3;
    } else {
      req.session.cookie.expires = false;
    }
  }
  next();
});
app.use(passport.initialize());
app.use(passport.session());

req.body.rememberme为真时,我无法登录,而当req.body.rememberme为假时,用户会被记住。

我还尝试了connect-ensure-login,但仍然有问题。

另外一个问题是:我应该在什么时候删除我的数据库中的cookie以及如何删除?

:)

其他代码与护照指南完全相同。

路由:

app.get('/', passport.authenticate('local', {
  failureRedirect: '/login'
}), function(req, res) {
  res.redirect('/user/home');
});
app.post('/login', passport.authenticate('local', {
  failureRedirect: '/login'
}), function(req, res) {
  res.redirect('/user/home');
});

会话:

passport.serializeUser(function(user, done) {
  var CreateAccessToken = function() {
    var token = user.GenerateSalt();
    User.findOne({
      accessToken: token
    }, function(err, existingUser) {
      if (err)
        return done(err);
      if (existingUser) {
        CreateAccessToken();
      } else {
        user.set('accessToken', token);
        user.save(function(err) {
          if (err)
            return done(err);
          return done(null, user.get('accessToken'));
        })
      }
    });
  };
  if (user._id)
    CreateAccessToken();
});
passport.deserializeUser(function(token, done) {
  User.findOne({
    accessToken: token
  }, function(err, user) {
    if (err)
      return done(err);
    return done(err, user);
  });
});

以及策略:

passport.use(new LocalStrategy(function(userId, password, done) {
  User.findOne().or([{
    username: userId
  }, {
    email: userId
  }]).exec(function(err, user) {
    if (err)
      return done(err);
    if (!user) {
      return done(null, false, {
        message: 'Invalid password or username'
      });
    }
    if (user.Authenticate(password)) {
      return done(null, user);
    } else {
      return done(null, false, {
        message: 'Invalid password or username'
      });
    }
  });
}));

我注意到Express只会在哈希值改变时更新Cookie,所以我更改了中间件的代码。
app.use(function(req, res, next) {
  if (req.method == 'POST' && req.url == '/login') {
    if (req.body.rememberme) {
      req.session.cookie.maxAge = 1000 * 60 * 3;
      req.session._garbage = Date();
      req.session.touch();
    } else {
      req.session.cookie.expires = false;
    }
  }
  next();
});

现在我可以使用“记住我”功能登录,但它只能在Ubuntu上的chromium和firefox上工作。我仍然无法在Win7和Android上的chrome和firefox上勾选“记住我”复选框进行登录。

当我在Win7上的chrome上POST到“/login”时,我检查了响应头,它与Ubuntu上相同具有相同的“Set-Cookie”字段,为什么它不能工作?


时间不同步...所以我提交了一个额外的时间字段。

$('#login').ajaxForm({
  beforeSubmit: function(arr, $form, option) {
    arr.push({
      name: '__time',
      value: (new Date()).toGMTString()
    });
  }
});

以及“RememberMe”中间件:

app.use(function(req, res, next) {
  if (req.method == 'POST' && req.url == '/login') {
    if (req.body.rememberme) {
      req.session.cookie.maxAge = moment(req.body.__time).add('m', 3) - moment();
      req.session._garbage = Date();
      req.session.touch();
    } else {
      req.session.cookie.expires = false;
    }
  }
  next();
});

这不是语义错误吗?如果“rememberme == true”,那么意味着cookie不应该过期吗?您应该发布更多的代码,因为您需要超出这三个中间件。 - pfried
如果rememberme == true,则Cookie将会有一个到期时间(在这种情况下为3分钟),否则该Cookie将被设置为会话Cookie(当浏览器会话结束时到期)。但我同意:需要更多的代码。 - robertklep
这段代码非常令人困惑。你应该分离关注点。serializedeserialize函数只用于通过用户id(使用数据库id)获取用户并将其序列化为会话cookie(如果您担心,id不会以明文形式存在)。不要混杂一些可疑的安全代码。 - pfried
@pfried,我不是很清楚passport-local的工作原理,就像他们的指南一样,在serialize中我创建了一个唯一的令牌,并在deserialize中找到了用户获得的令牌,但我不清楚这些函数何时被调用。 - QingYun
我最近也一直在处理这个问题,这篇文章对我有所帮助:Node.js + express.js + passport.js : stay authenticated between server restart - Matthew Bakaitis
3个回答

37

我和你遇到了完全相同的问题。以下代码适用于我:

app.post("/login", passport.authenticate('local',
    { failureRedirect: '/login',
      failureFlash: true }), function(req, res) {
        if (req.body.remember) {
          req.session.cookie.maxAge = 30 * 24 * 60 * 60 * 1000; // Cookie expires after 30 days
        } else {
          req.session.cookie.expires = false; // Cookie expires at end of session
        }
      res.redirect('/');
});

我已将示例时间从3分钟(少于大多数会话)编辑为30天。我认为3分钟是非记住我模式,而...expires = false;表示永不过期。 - rjmunro
@rjmunro 为什么要重定向到'/'?那是根路径还是'login/'? - williamcodes
这不是我的代码 - 我只是添加了一些注释,但看起来一旦你登录,它会重定向到站点的根目录。这个答案是一段时间前写的 :-). - rjmunro
出于安全考虑,这不是理想的。每次应该生成一个新的令牌。请参见其他带有passport-remember-me的答案。 - Joe C.

6

现在有一个由Jared Hanson编写的Passport策略,用于添加“记住我”功能。

https://github.com/jaredhanson/passport-remember-me

这个策略通过在每个会话期间发出独特的“记住我”令牌,在下一次会话开始时使用该令牌(使其无效以供未来使用)。因此,这可能是一个更安全的解决方案。


如果你的应用程序是koa.js应用程序,https://github.com/clthck/passport-remember-me 对我来说很好用。它实际上是jaredhanson原始存储库的一个分支,它只是为koa.js应用程序添加了支持(以及koa-passport)。 - elquimista
3
值得注意的是,对于真正的“记住我”功能,您需要在缓存或数据库中进行某种形式的长期持久性来存储令牌,否则服务器将将它们存储在内存中。您希望能够重新启动服务器并仍然使“记住我”令牌有效。 - tcmoore

2

请确保app.use(express.bodyParser())放置在你的中间件上方,因为你依赖于req.body.rememberme


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接