passport.js登出后无法结束会话 退出后仍可访问/profile路由
问题排查记录
项目依赖版本
"express": "^4.18.1", "express-session": "^1.17.3", "mongoose": "^6.4.0", "mongoose-encryption": "^2.1.2", "passport": "^0.6.0", "passport-local": "^1.0.0", "passport-local-mongoose": "^7.1.2"
app.js核心配置代码
require("dotenv").config(); const express = require("express"); const ejs = require("ejs"); const bodyParser = require("body-parser"); const mongoose = require("mongoose"); const session = require("express-session"); const passport = require("passport"); const passportLocalMongoose = require("passport-local-mongoose"); const app = express(); app.use(express.static("public")); app.set("view engine", "ejs"); app.use(bodyParser.urlencoded({ extended: true })); app.use( session({ secret: process.env.SECRET, resave: false, saveUninitialized: true, cookie: { secure: true }, }) ); app.use(passport.initialize()); app.use(passport.session()); mongoose.connect("mongodb://localhost:27017/userDB", { useNewUrlParser: true }); const userSchema = new mongoose.Schema({ email: String, password: String }); userSchema.plugin(passportLocalMongoose); const User = new mongoose.model("User", userSchema); passport.use(User.createStrategy()); passport.serializeUser(function (user, done) { done(null, user.id); }); passport.deserializeUser(function (id, done) { User.findById(id, function (err, user) { done(err, user); }); }); app.get("/profile", function (req, res) { User.find({ email: { $ne: null } }, function (err, foundUsers) { if (err) { console.log(err); } else { if (foundUsers) { res.render("profile", { foundUsersEjs: foundUsers }); } } }); });
已尝试的登出逻辑
- 方法一
app.post("/logout", function (req, res) { req.logout(function (err) { if (err) { console.log(err); } else { req.session.destroy(function (err) { res.redirect("/"); }); } }); });
- 方法二
app.post("/logout", function (req, res) { req.session.destroy(function (err) { if (err) { console.log(err); } else { req.logout(function (err) { res.redirect("/"); }); } }); });
前端登出按钮代码
<form action="/logout" method="post"> <button class="btn btn-light btn-lg">Log Out</button> </form>
问题现象
在http://localhost:4100/profile页面点击登出按钮后,页面会正常跳转至首页/,但手动在地址栏输入/profile路径时,仍然可以直接访问profile页面内容,未做登录拦截。
问题诱因
问题由多个配置错误共同导致:
- 路由缺少登录鉴权逻辑:当前
/profile路由没有任何登录状态校验,不管用户是否登录,只要发起请求就会直接查询数据库返回页面内容,这是未登录状态下仍能访问profile页的直接原因。 - Session Cookie配置不符合本地开发环境:session配置中写死了
cookie: { secure: true },该属性要求浏览器仅在HTTPS协议下才会携带、持久化session cookie。本地开发用的是HTTP协议,会导致session状态无法正常在客户端留存,登出、登录状态校验相关逻辑的实际执行效果不符合预期。 - 方法二的登出逻辑顺序错误:
req.logout()是passport提供的清除请求上用户挂载信息的方法,需要在session销毁前执行,先销毁session再调用logout没有实际意义。
修复方案
- 给受保护路由加鉴权校验
对需要登录才能访问的路由,统一加登录状态判断,可抽成公共中间件复用:
function isAuth(req, res, next) { if (req.isAuthenticated()) return next(); res.redirect("/login"); // 未登录直接跳登录页 } // 路由注册时挂载中间件 app.get("/profile", isAuth, function (req, res) { User.find({ email: { $ne: null } }, function (err, foundUsers) { if (err) { console.log(err); } else { if (foundUsers) { res.render("profile", { foundUsersEjs: foundUsers }); } } }); });
- 修正Session Cookie配置
根据运行环境动态适配secure属性,本地开发环境关闭secure限制:
app.use( session({ secret: process.env.SECRET, resave: false, saveUninitialized: false, // 调整为false,不存储未初始化的无效会话 cookie: { secure: process.env.NODE_ENV === "production", // 生产环境HTTPS下开启true,本地开发为false maxAge: 1000 * 60 * 60 * 24 // 可按需设置cookie过期时间 }, }) );
- 使用标准登出逻辑
按先清除passport登录态、再销毁session、最后清除客户端残留cookie的顺序编写:
app.post("/logout", function (req, res, next) { req.logout(function (err) { if (err) return next(err); req.session.destroy(function (err) { if (err) return next(err); res.clearCookie("connect.sid"); // 清除express-session默认的客户端cookie res.redirect("/"); }); }); });
内容的提问来源于stack exchange,提问作者user16704275
相关产品推荐
相关产品推荐

