为什么节点中的PassportJS无法在注销时删除会话


70

我无法让我的系统使用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();
}

在我的代码中,我req.logOut()使用大写的O大小写,但是关于指南,您的代码也应该工作。
balazs 2013年

我尝试了很多解决方案,但没有一个对我有用。最终,我尝试将程序包wallet@0.2.0更新为passport @@ 0.2.2,它可以正常工作!
prisan

Answers:


82

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!
  });
});

希望这可以帮助!


2
我只是得到'Object#<Session>没有方法'destroy'作为错误消息
schlenger 2014年

@schlenger Hmm,上面使用的是Express3。您使用的是版本4吗?
jlmakes 2014年

4
注意,因为在此处给出的示例中,session.destroy的使用假定使用express-sessions模块。其他会话模块(例如mozilla node-client-sessions)添加了destroy方法,该方法仅同步返回,并且不考虑任何回调参数。在这种情况下,您的代码将挂起
不稳定,2017年

你是基于这个client-sessions吗?它的destroy函数不带参数:github.com/mozilla/node-client-sessions/blob/master/lib/…–
oztune

好的,我用第二种方法。会话文件夹中的会话文件被删除,会话cookie从客户端中删除,但是节点服务器在控制台中显示以下错误消息:[session-file-store] will retry, error on last attempt: Error: ENOENT: no such file or directory, open ...我该如何解决?
emonhossain

42

遇到同样的问题。使用req.session.destroy();代替req.logout();工程,但是我不知道这是否是最佳实践。


1
添加req.session.destroy(); 为我工作,我遇到了同样的问题
Michael

3
req.session.destroy(); 也为我工作,请记住使用res.clearCookie('cookiename')清除cookie。但是,有谁知道req.logout()的确切功能吗?
Hrushikesh 2013年

@WebHrushi,您可以在passport / lib / passport / http / request.js中找到该功能。今天,这两个版本都不再对我有用,在会话最终被销毁之前,重定向似乎以两种方式调用。在重定向页面上,此处描述的两种方法似乎都可以正常工作。
ztirom

3
我不喜欢不使用的想法,因为它不req.logout()具有向前兼容性,因此我只添加了req.session.destroy();after req.logout(),并且效果很好。
加文2014年

14

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

这里的问题是,如果您的应用程序还用作单页应用程序的API(不建议使用,但很常见),那么Express可能会处理一些请求,这些请求在注销之前开始,而在注销之后结束。如果是这种情况,则此运行时间更长的请求将在删除会话后以redis的方式恢复会话。并且由于浏览器下次打开页面时仍具有相同的cookie,因此您将成功登录。

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

这就是可能发生的其他情况:

  1. 收到请求1(任何请求)
  2. 请求1将会话从Redis加载到内存
  3. 收到注销请求
  4. 注销请求加载会话
  5. 注销请求破坏会话
  6. 注销请求将重定向发送到浏览器(未删除Cookie)
  7. 需求1完成处理
  8. 请求1将会话从内存保存到Redis
  9. 用户打开了没有登录对话框的页面,因为cookie和会话都已到位

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


1
这解决了客户端(而不是服务器)上的问题,这似乎是不安全的。如果connect.sid您在登出前遭到劫持,则在登出后不应允许另一方以您的身份登录。
保罗S

8

我遇到了同样的问题,事实证明这根本不是Passport函数的问题,而是我调用/logout路线的方式。我使用fetch来调用路由:

(坏)

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

事实证明,这样做不会发送cookie,因此会话不会继续进行,我猜这res.logout()将应用于其他会话吗?无论如何,执行以下操作可以立即修复它:

(好)

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

在搜寻4个小时后才得出这个答案,这是一个愚蠢的错误,但感谢您指出。
Azhar Husain


4

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

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

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

基本上我只是改变saveUninitializedresavetruefalse。看来已经解决了这个问题。

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

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

我在注销和登录时都遇到问题,偶尔会遇到问题,这对我来说已经解决了。
汤姆·休斯

@TomHughes很高兴它有所帮助!
查理·菲什

4

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

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

只是说,我使用Redis作为会话存储。


如果缓存速度很慢,或者在注销逻辑之后运行了中间件,则您的代码可能会导致意外行为,应使用req.session.destroy(()=> res.redirect(“ /”));
Fareed Alnamrouti

session.destroy是异步的,需要回调。您应该调用res.redirect('/');该回调。
绿色,

3

没有答案对我有用,所以我将与我分享

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('/')
})

1

自己破坏会话看起来很奇怪。我面临着具有下一个配置的问题:

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

我应该说这种配置效果很好。问题的原因是我在sessionStore此处定义的习惯:

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

为了确保您的问题在这里也只是注释存储行并在没有会话持续的情况下运行。如果可行,则应深入研究自定义会话存储。在我看来,set方法定义错误。当您使用req.logout()会话存储destroy()方法时,没有像我以前想象的那样被调用。而是set使用更新的会话调用方法。

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


1

我的经验是,有时无法正常工作,因为您无法正确设置护照。例如,我这样做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.js可以使用会话表app.host.sub1.js

app.host.sub1.js

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

所以,当我要注销...时,这是行不通的,因为app.js在开始初始化passport.js之前做错了什么express-session.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('/');
    });
});

但在我的情况下,正确的方法是...交换的EXPRESS-session.jspassport.js

文件还提到

请注意,启用会话支持完全是可选的,尽管建议将其用于大多数应用程序。如果启用,请确保在passport.session()之前使用express.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();工作了。


1

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

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


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


0

我有同样的问题。原来,我的护照版本与Express 4.0不兼容。只需要安装一个较旧的版本。

    npm install --save express@3.0.0

0

这为我工作:

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');
});

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


0

我正在与程序员合作,建议删除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实质上是异步的,因此有时可能无法正常工作


0

我在Passport 0.3.2中遇到了类似的问题。

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

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

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

0

由于您正在使用通行证认证,通行证通过connect.sidcookie使用它自己的会话,因此最简单的注销方法是让通行证处理该会话。

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

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

0

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

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


0

这仍然是一个问题。

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

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

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

也许不是最好的方法,但是它可行。


感谢您的支持。我建议您在答案中修复代码格式。您发布的主要代码段不是有效的JS。
安德鲁·诺兰

0

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

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

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


-1

您应该使用req.logout()破坏浏览器中的会话。

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

-3

我不知道如何ng-href="https://stackoverflow.com/signout"解决问题。以前我曾使用service登出,但我直接使用了它。


1
我没有使用Angular,所以这没有任何意义。:/
IIllIIll '16

-3

就我而言,使用回调传递req.session.destroy仅在某些情况下有所帮助,因此我不得不求助于这种hack:

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

我不知道为什么这是我能够上班的唯一解决方案,但是不幸的是,@ JulianLloyd的回答始终无法为我工作。

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

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

不幸的是,我还没有找到更好的解决方案。


为什么可以使用异步回调时延迟2秒
Godfather

回调对我而言并不一致。
马特·布朗
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.