为啥 Node 中的 PassportJS 不会在注销时删除会话

Posted

技术标签:

【中文标题】为啥 Node 中的 PassportJS 不会在注销时删除会话【英文标题】:Why is PassportJS in Node not removing session on logout为什么 Node 中的 PassportJS 不会在注销时删除会话 【发布时间】:2012-11-25 08:14:24 【问题描述】:

我无法让我的系统使用 PassportJS 注销。似乎正在调用注销路由,但它没有删除会话。如果用户未登录特定路线,我希望它返回 401。我调用 authenticateUser 来检查用户是否已登录。

非常感谢!

/******* This in index.js *********/
// setup passport for username & passport authentication
adminToolsSetup.setup(passport);

// admin tool login/logout logic
app.post("/adminTool/login",
    passport.authenticate('local', 
        successRedirect: '/adminTool/index.html',
        failureRedirect: '/',
        failureFlash: false )
);
app.get('/adminTool/logout', adminToolsSetup.authenticateUser, function(req, res)
    console.log("logging out");
    console.log(res.user);
    req.logout();
    res.redirect('/');
);


// ******* This is in adminToolSetup ********
// Setting up user authentication to be using user name and passport as authentication method,
// this function will fetch the user information from the user name, and compare the password     for authentication
exports.setup = function(passport) 
    setupLocalStrategy(passport);
    setupSerialization(passport);


function setupLocalStrategy(passport) 
    passport.use(new LocalStrategy(
        function(username, password, done) 
            console.log('validating user login');
            dao.retrieveAdminbyName(username, function(err, user) 
                if (err)  return done(err); 
                if (!user) 
                    return done(null, false,  message: 'Incorrect username.' );
                
                // has password then compare password
                var hashedPassword = crypto.createHash('md5').update(password).digest("hex");
                if (user.adminPassword != hashedPassword) 
                    console.log('incorrect password');
                    return done(null, false,  message: 'Incorrect password.' );
                
                console.log('user validated');
                return done(null, user);
            );
        
    ));


function setupSerialization(passport) 
    // serialization
    passport.serializeUser(function(user, done) 
        console.log("serialize user");
        done(null, user.adminId);
    );

    // de-serialization
    passport.deserializeUser(function(id, done) 
        dao.retrieveUserById(id, function(err, user) 
            console.log("de-serialize user");
            done(err, user);
        );
    );


// authenticating the user as needed
exports.authenticateUser = function(req, res, next) 
    console.log(req.user);
    if (!req.user) 
        return res.send("401 unauthorized", 401);
    
    next();

【问题讨论】:

在我的代码中,我使用大写 O 调用 req.logOut(),但对于 guide,您的代码也应该可以工作。 我尝试了很多解决方案,但没有一个对我有用。最后,我尝试将包 passport@0.2.0 更新为 passport@0.2.2 并且成功了! 【参考方案1】:

我通过将axios.post 请求中的withCredentials: true 设置为注销路由解决了这个问题。我猜没有发送识别会话所需的凭据,因此 req.logOut() 没有效果(我还注意到req.user 在注销路由上未定义,这是一个很大的线索)

【讨论】:

【参考方案2】:

我同时使用了req.logout()req.session.destroy() 并且工作正常。

server.get('/logout', (req, res) => 
  req.logout();
  req.session.destroy(()=>
    res.redirect('/');
  );
);

顺便提一下,我使用 Redis 作为会话存储。

【讨论】:

如果缓存很慢或者在注销逻辑后运行中间件,您的代码可能会导致意外行为,您应该使用 req.session.destroy(()=> res.redirect ("/")); session.destroy 是异步的,需要回调。您应该在该回调中调用res.redirect('/');【参考方案3】:

试试这个

    app.get('/logout', (req, res) => 
    req.logout();
    req.session.destroy();
    res.redirect('/');
    

【讨论】:

为什么这段代码可以工作?你能提供更多的细节吗?谢谢!【参考方案4】:

您可以尝试手动重新生成会话:

app.get('/logout', (req, res) => 
    req.logOut();
    req.session.regenerate(err => 
        err && console.log(err);
    );
    res.redirect('/');
);

这不会从会话中删除其他数据(如护照)。

【讨论】:

【参考方案5】:

这仍然是个问题。

我所做的是在服务器端和客户端使用req.session.destroy(function (err) );,每当他们注销时:

const logout = () => 
    const url = '/users/logout'
    fetch(url)
    setTimeout(function () 
      location.reload();    , 500);

这样,当刷新页面时,用户没有会话。如果没有人通过身份验证,请确保您重定向到正确的页面。

也许不是最好的方法,但它确实有效。

【讨论】:

感谢您的支持。我建议在您的答案中修复代码格式。您发布的主要 sn-p 不是有效的 JS。【参考方案6】:

只需添加 req.logOut();解决了这个问题; “O”应该大写

【讨论】:

欢迎您!请参阅本指南以改进您的答案***.com/help/how-to-answer【参考方案7】:

我遇到了同样的问题,大写 O 解决了它;

app.get('/logout', function (req, res)
  req.logOut()  // <-- not req.logout();
  res.redirect('/')
);

编辑:这不再是问题。

【讨论】:

当前版本的 Passport 接受 logOut 或 logout(与 login 和 logIn 相同) logoutlogOut 在 2015 年相互别名:github.com/jaredhanson/passport/blame/…【参考方案8】:

没有一个对我有用,所以我将分享我的答案

app.use(session(
    secret: 'some_secret',
    resave: false,
    saveUninitialized: false,
   cookie: maxAge: 1000 // this is the key
))

router.get('/logout', (req, res, next) => 
    req.logOut()
    req.redirect('/')
)

【讨论】:

【参考方案9】:

这里的所有示例都在 req.session.destroy 之后进行重定向。 但要意识到 Express 会立即为您重定向到的页面创建一个新会话。 结合 Postman,我发现在注销后立即执行 Passport-Login 会产生 Passport 成功但无法将用户 ID 存储到会话文件的效果的奇怪行为。原因是 Postman 需要更新该组的所有请求中的 cookie,这需要一段时间。 销毁回调中的重定向也无济于事。

我通过不执行重定向而仅返回 json 消息来解决它。

【讨论】:

【参考方案10】:

您应该使用 req.logout() 来销毁浏览器中的会话。

app.get('/logout', function(req, res) 
    req.logout();
    res.redirect('/'); // whatever the route to your default page is
);

【讨论】:

【参考方案11】:

由于您使用的是通过 connect.sid cookie 使用自己的会话的护照身份验证,因此处理注销的最简单方法是让护照处理会话。

app.get('/logout', function(req, res)
  if (req.isAuthenticated()) 
    req.logOut()
    return res.redirect('/') // Handle valid logout
  

  return res.status(401) // Handle unauthenticated response
)

【讨论】:

【参考方案12】:

显然,这个问题有多种可能的原因。在我的情况下,问题是声明的顺序错误,即注销端点是在护照初始化之前声明的。正确的顺序是:

app.use(passport.initialize());
app.use(passport.session());


app.get('/logout', function(req, res) 
  req.logout();
  res.redirect('/');
);

【讨论】:

【参考方案13】:

我在使用 Passport 0.3.2 时遇到了类似的问题。

当我使用自定义回调进行护照登录和注册时,问题仍然存在。

问题已通过升级到 Passport 0.4.0 并添加行来解决

app.get('/logout', function(req, res) 
    req.logOut();
    res.redirect('/');
);

【讨论】:

【参考方案14】:

我正在与一名程序员合作,建议删除 req 的用户:

app.get('/logout', function (req, res)
  req.session.destroy(function (err) 
    req.user = null;
    res.redirect('/'); //Inside a callback… bulletproof!
  );
);

原因: 我们需要从 req 中删除(passportjs 也这样做但异步方式),因为注销后没有使用用户数据 即使这样会节省内存,也可能是passportjs找到的用户数据,可能会创建新的会话和重定向(但还没有发生) 顺便说一句,这是我们删除无关事物的责任。 PassportJS 在登录后将数据分配给 req.user,如果我们使用 req.logout() 也会删除,但它可能无法正常工作,因为 NodeJS 本质上是异步的

【讨论】:

【参考方案15】:

我有一个经验,有时它不起作用,因为您没有正确设置护照。 例如,我使用vhost,但在主应用程序上我设置了这样的护照,这是错误的。

app.js(为什么错了?请看下面的blockqoute)

require('./modules/middleware.bodyparser')(app);
require('./modules/middleware.passport')(app);
require('./modules/middleware.session')(app);
require('./modules/app.config.default.js')(app, express);

// default router across domain
app.use('/login', require('./controllers/loginController'));
app.get('/logout', function (req, res) 
    req.logout();
    res.redirect('/');
);

// vhost setup
app.use(vhost('sub1.somehost.dev', require('./app.host.sub1.js')));
app.use(vhost('somehost.dev', require('./app.host.main.js')));

实际上,它一定无法登录,但我设法做到了,因为我继续犯更多错误。通过在此处设置另一个护照设置,以便app.host.sub1.js 可以使用会话表单app.js

app.host.sub1.js

// default app configuration
require('./modules/middleware.passport')(app);
require('./modules/app.config.default.js')(app, express);

所以,当我想注销时......它不起作用,因为app.jsexpress-session.js 之前开始初始化passport.js 做错了,这是错误的!!。

但是,正如其他人提到的那样,此代码无论如何都可以解决问题。

app.js

app.get('/logout', function (req, res) 
    req.logout();
    req.session.destroy(function (err) 
        if (err) 
            return next(err);
        

        // destroy session data
        req.session = null;

        // redirect to homepage
        res.redirect('/');
    );
);

但在我的情况下,正确的方法是...在 passport.js

之前交换 express-session.js

document也提到

请注意,启用会话支持是完全可选的,尽管它是 推荐用于大多数应用程序。如果启用,请务必使用 express.session() 在 passport.session() 之前确保登录 会话以正确的顺序恢复。

所以,解决了我的情况下的注销问题..

app.js

require('./modules/middleware.bodyparser')(app);
require('./modules/middleware.session')(app);
require('./modules/middleware.passport')(app);
require('./modules/app.config.default.js')(app, express);


// default router across domain
app.use('/login', require('./controllers/loginController'));
app.get('/logout', function (req, res) 
    req.logout();
    res.redirect('/');
);

app.host.sub1.js

// default app configuration
require('./modules/app.config.default.js')(app, express);

现在req.logout(); 可以工作了。

【讨论】:

【参考方案16】:

我最近遇到了同样的问题,但没有一个答案为我解决了这个问题。可能是错误的,但它似乎与竞争条件有关。

将会话详细信息更改为以下选项似乎已经解决了我的问题。我现在已经测试了大约 10 次左右,一切似乎都正常工作。

app.use(session(
    secret: 'secret',
    saveUninitialized: false,
    resave: false
));

基本上我只是将saveUninitializedresavetrue 更改为false。这似乎已经解决了这个问题。

仅供参考,我在注销路径中使用标准的req.logout(); 方法。我没有像其他人提到的那样使用会话销毁。

app.get('/logout', function(req, res) 
    req.logout();
    res.redirect('/');
);

【讨论】:

我在注销和登录时遇到问题,偶尔不卡,这为我解决了问题。 @TomHughes 很高兴它有帮助!【参考方案17】:

我遇到了同样的问题,结果证明根本不是 Passport 功能的问题,而是我调用 /logout 路由的方式。我用 fetch 来调用路由:

(坏)

fetch('/auth/logout')
  .then([other stuff]);

原来这样做不会发送 cookie,因此会话不会继续,我猜 res.logout() 会应用于不同的会话?无论如何,执行以下操作可以解决问题:

(好)

fetch('/auth/logout',  credentials: 'same-origin' )
  .then([other stuff]);

【讨论】:

在找到这个答案之前搜索了 4 个小时,这是一个愚蠢的错误,但感谢您指出。【参考方案18】:

在我的情况下,使用传递给 req.session.destroy 的回调仅在某些时候有帮助,我不得不求助于这个 hack:

req.session.destroy();
setTimeout(function() 
    res.redirect "/";
, 2000);

我不知道为什么这是我能够开始工作的唯一解决方案,但不幸的是,@JulianLloyd 的回答并没有对我始终有效。

这可能与我的实时登录页面使用 SSL 的事实有关(我无法在暂存站点或本地主机上重现该问题)。我的应用程序中可能还会发生其他事情;我正在使用 derby-passport 模块,因为我的应用程序使用的是 Derby 框架,因此很难隔离问题。

这显然是一个时间问题,因为我首先尝试了 100 毫秒的超时,这还不够。

很遗憾,我还没有找到更好的解决方案。

【讨论】:

为什么延迟2秒,什么时候可以使用异步回调 我的回调没有持续触发。【参考方案19】:

我不知道如何,但ng-href="/signout" 解决了我的问题。之前我是用service注销的,现在直接用了。

【讨论】:

我没有使用 Angular,所以这没有任何意义。 ://【参考方案20】:

session.destroy 可能不够用,要确保用户完全注销,您还必须清除会话 cookie。

这里的问题是,如果您的应用程序还用作单页应用程序的 API(不推荐,但很常见),那么 express 可能会处理一些在注销前开始并在注销后结束的请求.如果是这种情况,那么这个运行时间较长的请求将在删除后恢复 redis 中的会话。并且因为浏览器还有相同的cookie,下次打开页面就可以成功登录了。

req.session.destroy(function() 
    res.clearCookie('connect.sid');
    res.redirect('/');
);

否则可能会发生这种情况:

    收到请求 1(任何请求) 请求 1 将会话从 redis 加载到内存中 收到注销请求 注销请求加载会话 注销请求会破坏会话 注销请求将重定向发送到浏览器(cookie 未删除) 请求 1 完成处理 请求 1 将会话从内存保存到 redis 用户打开页面时没有登录对话框,因为 cookie 和会话都已就位

理想情况下,您需要对 api 调用使用令牌身份验证,并且仅在仅加载页面的 web 应用程序中使用会话,但即使您的 web 应用程序仅用于获取 api 令牌,这种竞争条件仍然可能。

【讨论】:

这解决了客户端上的问题,而不是服务器上的问题,这似乎不安全。如果connect.sid 在您注销之前被劫持,则它不应该在您注销后允许另一方以您的身份登录。【参考方案21】:

这对我有用:

app.get('/user', restrictRoute, function (req, res) 
  res.header('Cache-Control', 'no-cache, private, no-store, must-revalidate,
              max-stale=0, post-check=0, pre-check=0');
);

它确保您的页面不会被存储在缓存中

【讨论】:

【参考方案22】:

自己破坏会话看起来很奇怪。 我在进行下一个配置时遇到了这个问题:

"express": "^4.12.3",
"passport": "^0.2.1",
"passport-local": "^1.0.0",

我应该说这个配置运行良好。 我的问题的原因是我在这里定义的自定义sessionStore

app.use(expressSession(
    ...
    store: dbSessionStore,
    ...
));

为了确保您的问题在这里也只是评论存储行并在没有会话持续存在的情况下运行。如果它可以工作,您应该深入了解您的自定义会话存储。就我而言,set 方法定义错误。当您使用 req.logout() 会话存储 destroy() 方法时,不会像我之前想的那样调用。而是使用更新的会话调用 set 方法。

祝你好运,我希望这个答案对你有所帮助。

【讨论】:

【参考方案23】:

我遇到了同样的问题。原来我的护照版本与 Express 4.0 不兼容。只需安装旧版本即可。

    npm install --save express@3.0.0

【讨论】:

【参考方案24】:

遇到了同样的问题。使用req.session.destroy(); 而不是req.logout(); 可以,但我不知道这是否是最佳做法。

【讨论】:

添加 req.session.destroy();为我工作,我遇到了同样的问题 req.session.destroy();也为我工作,记得用 res.clearCookie('cookiename') 清除你的 cookie。但是有谁知道 req.logout() 到底是做什么的? @WebHrushi 您可以在 passport/lib/passport/http/request.js 中找到该功能。今天这两个版本都停止了对我的工作,重定向似乎在会话最终被销毁之前以两种方式调用。在重定向页面上,此处描述的两种方式似乎都可以正常工作。 我不喜欢不使用 req.logout() 的想法,为了向前兼容等等,所以我只是在 req.logout() 之后添加了 req.session.destroy(); 并且效果很好。【参考方案25】:

Brice 的回答很棒,但我仍然注意到一个重要的区别; Passport 指南建议使用.logout()(也称为.logOut()):

app.get('/logout', function(req, res)
  req.logout();
  res.redirect('/'); //Can fire before session is destroyed?
);

但是如上所述,这是不可靠的。在执行 Brice 的建议时,我发现它的行为符合预期:

app.get('/logout', function (req, res)
  req.session.destroy(function (err) 
    res.redirect('/'); //Inside a callback… bulletproof!
  );
);

希望这会有所帮助!

【讨论】:

我只是得到 'Object # has no method 'destroy'' 作为错误消息 @schlenger 嗯,上面使用的是Express 3。您使用的是版本4吗? 要小心,因为在这里给出的示例中, session.destroy 的使用假定使用 express-sessions 模块。其他会话模块(例如 mozilla node-client-sessions)添加了一个销毁方法,该方法仅同步返回并忽略任何回调参数。在这种情况下,您的代码将挂起 你是基于client-sessions吗?它的销毁函数不带参数:github.com/mozilla/node-client-sessions/blob/master/lib/… 好的,我使用你的第二种方法。会话文件夹中的会话文件被删除,会话 cookie 被从客户端中删除,但节点服务器在控制台中显示以下错误消息:[session-file-store] will retry, error on last attempt: Error: ENOENT: no such file or directory, open ... 我该如何解决这个问题?

以上是关于为啥 Node 中的 PassportJS 不会在注销时删除会话的主要内容,如果未能解决你的问题,请参考以下文章

为啥异常会导致 Node.js 中的资源泄漏?

PassportJs 身份验证无限循环和执行(默认)查询

Nodejs和PassportJs:如果身份验证失败,则不会调用passport.authenticate后重定向中间件

如何告诉 Passport JS 中的 typescript req.user 永远不会被定义?

为啥 catch 块不会触发并且应用程序停止在 node.js 中工作

谁能帮我解决与passportjs相关的错误