Passport.js 集成 Express:认证实战指南
Passport.js 是 Node.js 最流行的认证中间件,500+ 策略覆盖本地登录与社交 OAuth。本文实战 Local 策略注册登录、会话管理与路由保护。
Passport 的核心是策略(Strategy)模式:passport.use(new LocalStrategy(verifyFn)) 注册验证逻辑,passport.authenticate('local') 中间件守卫路由。配 express-session 维持登录态,req.user 在后续请求中可用。
中间件准备
npm install express express-session passport passport-local bcrypt
const session = require('express-session');
const passport = require('passport');
app.use(express.json());
app.use(session({
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: false,
cookie: { secure: 'auto', maxAge: 24 * 60 * 60 * 1000 },
}));
app.use(passport.initialize());
app.use(passport.session());
配置 Local 策略
const LocalStrategy = require('passport-local').Strategy;
const bcrypt = require('bcrypt');
passport.use(new LocalStrategy(
{ usernameField: 'email' },
async (email, password, done) => {
try {
const user = await db.users.findByEmail(email);
if (!user) return done(null, false, { message: '邮箱或密码错误' });
const ok = await bcrypt.compare(password, user.passwordHash);
if (!ok) return done(null, false, { message: '邮箱或密码错误' });
return done(null, user);
} catch (err) {
return done(err);
}
}
));
// 会话序列化:只存用户 ID
passport.serializeUser((user, done) => done(null, user.id));
passport.deserializeUser(async (id, done) => {
try { done(null, await db.users.findById(id)); }
catch (err) { done(err); }
});
done 的三态:done(null, user) 成功;done(null, false, message) 认证失败;done(err) 系统错误——语义区分是 Passport 的契约核心。
认证路由
app.post('/auth/register', async (req, res) => {
const { email, password } = req.body;
if (await db.users.findByEmail(email)) {
return res.status(409).json({ error: '邮箱已注册' });
}
const passwordHash = await bcrypt.hash(password, 12);
const user = await db.users.create({ email, passwordHash });
req.login(user, () => res.status(201).json({ id: user.id, email }));
});
app.post('/auth/login', passport.authenticate('local'), (req, res) => {
res.json({ id: req.user.id, email: req.user.email });
});
app.post('/auth/logout', (req, res) => {
req.logout(() => res.json({ ok: true }));
});
路由保护
function requireAuth(req, res, next) {
if (req.isAuthenticated()) return next();
res.status(401).json({ error: '请先登录' });
}
app.get('/api/profile', requireAuth, (req, res) => res.json(req.user));
安全清单
- 密码 bcrypt 哈希(cost ≥ 12),绝不明文;
- session 的 secret 用环境变量,生产 cookie 开 secure + httpOnly;
- 登录接口加限流(防暴力破解)与验证码;
- 会话存储生产用 Redis(connect-redis)——内存存储重启即丢失。
常见问题(FAQ)
Q:Passport 适合 REST API + JWT 吗?
可以(passport-jwt 策略)。但想清楚:纯 API + SPA 场景 JWT 无状态更方便;传统服务端渲染或需要服务端登出控制用 session。Passport 两种都支持。
Q:500 多个策略都靠谱吗?
官方维护的核心策略(local/oauth/google/github)质量可靠;社区长尾策略质量参差——用前看维护状态与周下载量。
Q:现代替代方案?
NestJS 生态用 @nestjs/passport 包装;新框架(Hono 等)有自己的认证中间件。Express 存量项目 Passport 仍是标准答案。
参考文档
本文依据公开文档整理,示例需按项目依赖版本验证;不将文档中的性能描述视为本项目实测结果。