如何处理 passport.deserializeUser() 中的错误?

How do I handle errors in passport.deserializeUser()?

我已经像这样配置了 Express 和 Passport:

var express = require("express");
var site = express();
var flash = require("connect-flash");
var passport = require("passport");

site.use(require("cookie-parser")());
site.use(require("body-parser").urlencoded({extended:false}));
site.use(require("express-session")(...));
site.use(flash());
site.use(passport.initialize());
site.use(passport.session());

我有一个很好的 deserializeUser 实现(我正在使用 MySQL 通过 Bookshelf 支持的 local 身份验证):

var db = require("./database.js"); // exports models e.g. db.User

passport.deserializeUser(function(id, done) {
    db.User.findById(id).then(function (user) {
        done(null, user);
    }).catch(function (err) {
        done(err, null);
    });
});

我 运行 遇到以下具体问题:当登录用户从数据库中删除时(发生在例如站点管理员删除用户时),反序列化如预期的那样失败,来自 Bookshelf 的 CustomError("EmptyResponse")。但是,我不知道如何处理它; done(err, null) 最终只会导致错误消息和堆栈跟踪作为 HTML 发送回客户端。

问题是:如何在失败时从 deserializeUser 提供自定义的、优雅的错误处理?

现在,如果它简化了事情,我可以将 {require:false} 添加到 db.User.findById 调用中给我一个 null 用户而不是错误,但我仍然不知道如何处理它(而且我仍然需要处理错误对象,例如,如果数据库服务器已关闭并且存在连接错误)。

我想在失败时采取的行动是将用户重定向回登录页面,可能带有描述性的快速消息,但我无权访问 [=13] 中的 request/response =] 而且我不确定如何回复。

Passport docs "Configure" section中,它们显示了发送特定消息的能力:

return done(null, false, { message: 'Incorrect username.' });

上面的例子在他们的文档中,但在你的情况下,你可以这样做:

passport.deserializeUser(function(id, done) {
    db.User.findById(id).then(function (user) {
        done(null, user);
    }).catch(function (err) {
        done(err, null, { message: 'User does not exist' });
    });
});

我想出了一个解决办法。这里的错误可以在 Express 中间件错误处理程序中处理。所以,例如:

// Note: Must be used *after* passport middleware.
site.use(function(err, req, res, next) {
    if (err) {
        // Handle deserialization errors here.
    } else {
        next();
    }
});

一个重要的警告是,如果反序列化失败不可恢复,所有 通过护照访问的路由(即使是那些不需要身份验证的路由)将继续失败,这很可能还包括您的登录和注销端点,因此会永久中断访问,直到用户清除他们的 cookie。所以你必须强制注销,这是唯一真正的恢复方法:

site.use(function(err, req, res, next) {
    if (err) {
        req.logout(); // So deserialization won't continue to fail.
    } else {
        next();
    }
});

在此基础上进一步构建,在我的例子中,我想用一条闪现消息重定向回登录页面。但是你必须小心:如果登录页面本身发生错误,你需要避免无限重定向,所以:

site.use(function(err, req, res, next) {
    if (err) {
        req.logout();
        if (req.originalUrl == "/loginpage") {
            next(); // never redirect login page to itself
        } else {
            req.flash("error", err.message);
            res.redirect("/loginpage");
        }
    } else {
        next();
    }
});

当然,您可能只想为 CustomError("EmptyResponse") 执行此操作,或者甚至在反序列化实现中重做错误处理,让它抛出您自己的更具体的错误。

有点奇怪的方法,但似乎可以完成工作。接受更清洁的建议。