Passport.js 安装与配置 #
环境准备 #
系统要求 #
在开始之前,请确保你的系统满足以下要求:
| 要求 | 版本 |
|---|---|
| Node.js | >= 14.0.0(推荐 LTS 版本) |
| npm | >= 6.0.0 |
| Express | >= 4.0.0 |
检查环境 #
bash
# 检查 Node.js 版本
node --version
# 检查 npm 版本
npm --version
项目初始化 #
创建项目 #
bash
# 创建项目目录
mkdir my-auth-app
cd my-auth-app
# 初始化 package.json
npm init -y
# 安装 Express
npm install express
# 安装 Passport
npm install passport
项目结构 #
text
my-auth-app/
├── config/
│ └── passport.js # Passport 配置
├── models/
│ └── User.js # 用户模型
├── routes/
│ ├── auth.js # 认证路由
│ └── index.js # 主路由
├── middleware/
│ └── auth.js # 认证中间件
├── app.js # 应用入口
└── package.json
安装依赖 #
核心依赖 #
bash
# Express 框架
npm install express
# Passport 核心
npm install passport
# 会话支持
npm install express-session
# 本地策略
npm install passport-local
# 密码加密
npm install bcryptjs
可选依赖 #
bash
# MongoDB 用户模型
npm install mongoose
# 环境变量
npm install dotenv
# 请求体解析
npm install body-parser
# 闪存消息
npm install connect-flash
基础配置 #
创建 Express 应用 #
javascript
// app.js
const express = require('express');
const session = require('express-session');
const passport = require('passport');
const flash = require('connect-flash');
const app = express();
// 解析请求体
app.use(express.urlencoded({ extended: true }));
app.use(express.json());
// 会话配置
app.use(session({
secret: 'your-secret-key',
resave: false,
saveUninitialized: false,
cookie: {
secure: false, // 开发环境设为 false,生产环境设为 true
maxAge: 24 * 60 * 60 * 1000 // 24 小时
}
}));
// Passport 初始化
app.use(passport.initialize());
app.use(passport.session());
// 闪存消息
app.use(flash());
// 路由
app.use('/auth', require('./routes/auth'));
app.use('/', require('./routes/index'));
// 启动服务器
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
Passport 配置文件 #
javascript
// config/passport.js
const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;
const User = require('../models/User');
// 配置本地策略
passport.use(new LocalStrategy(
{
usernameField: 'username',
passwordField: 'password'
},
async (username, password, done) => {
try {
const user = await User.findOne({ username });
if (!user) {
return done(null, false, { message: '用户不存在' });
}
const isMatch = await user.comparePassword(password);
if (!isMatch) {
return done(null, false, { message: '密码错误' });
}
return done(null, user);
} catch (error) {
return done(error);
}
}
));
// 序列化用户
passport.serializeUser((user, done) => {
done(null, user.id);
});
// 反序列化用户
passport.deserializeUser(async (id, done) => {
try {
const user = await User.findById(id);
done(null, user);
} catch (error) {
done(error);
}
});
module.exports = passport;
用户模型 #
javascript
// models/User.js
const mongoose = require('mongoose');
const bcrypt = require('bcryptjs');
const userSchema = new mongoose.Schema({
username: {
type: String,
required: true,
unique: true,
trim: true
},
email: {
type: String,
required: true,
unique: true,
trim: true,
lowercase: true
},
password: {
type: String,
required: true
},
createdAt: {
type: Date,
default: Date.now
}
});
// 密码加密中间件
userSchema.pre('save', async function(next) {
if (!this.isModified('password')) {
return next();
}
const salt = await bcrypt.genSalt(10);
this.password = await bcrypt.hash(this.password, salt);
next();
});
// 验证密码方法
userSchema.methods.comparePassword = async function(candidatePassword) {
return await bcrypt.compare(candidatePassword, this.password);
};
module.exports = mongoose.model('User', userSchema);
认证路由 #
注册路由 #
javascript
// routes/auth.js
const express = require('express');
const router = express.Router();
const User = require('../models/User');
// 注册页面
router.get('/register', (req, res) => {
res.render('register', { message: req.flash('error') });
});
// 注册处理
router.post('/register', async (req, res) => {
try {
const { username, email, password, confirmPassword } = req.body;
// 验证
if (password !== confirmPassword) {
req.flash('error', '两次密码不一致');
return res.redirect('/auth/register');
}
// 检查用户是否存在
const existingUser = await User.findOne({
$or: [{ username }, { email }]
});
if (existingUser) {
req.flash('error', '用户名或邮箱已存在');
return res.redirect('/auth/register');
}
// 创建用户
const user = new User({ username, email, password });
await user.save();
req.flash('success', '注册成功,请登录');
res.redirect('/auth/login');
} catch (error) {
console.error(error);
req.flash('error', '注册失败');
res.redirect('/auth/register');
}
});
module.exports = router;
登录路由 #
javascript
// routes/auth.js (续)
const passport = require('passport');
// 登录页面
router.get('/login', (req, res) => {
res.render('login', {
message: req.flash('error'),
success: req.flash('success')
});
});
// 登录处理
router.post('/login', (req, res, next) => {
passport.authenticate('local', {
successRedirect: '/profile',
failureRedirect: '/auth/login',
failureFlash: true
})(req, res, next);
});
// 登出
router.get('/logout', (req, res, next) => {
req.logout((err) => {
if (err) return next(err);
res.redirect('/');
});
});
module.exports = router;
受保护路由 #
javascript
// middleware/auth.js
function ensureAuthenticated(req, res, next) {
if (req.isAuthenticated()) {
return next();
}
req.flash('error', '请先登录');
res.redirect('/auth/login');
}
function ensureNotAuthenticated(req, res, next) {
if (!req.isAuthenticated()) {
return next();
}
res.redirect('/profile');
}
module.exports = {
ensureAuthenticated,
ensureNotAuthenticated
};
javascript
// routes/index.js
const express = require('express');
const router = express.Router();
const { ensureAuthenticated } = require('../middleware/auth');
// 首页
router.get('/', (req, res) => {
res.render('index', { user: req.user });
});
// 个人资料页(需要登录)
router.get('/profile', ensureAuthenticated, (req, res) => {
res.render('profile', { user: req.user });
});
module.exports = router;
环境变量配置 #
创建 .env 文件 #
bash
# .env
NODE_ENV=development
PORT=3000
# Session
SESSION_SECRET=your-super-secret-key-change-in-production
# MongoDB
MONGODB_URI=mongodb://localhost:27017/my-auth-app
# 其他配置
BCRYPT_SALT_ROUNDS=10
加载环境变量 #
javascript
// app.js
require('dotenv').config();
const express = require('express');
const mongoose = require('mongoose');
const session = require('express-session');
const passport = require('passport');
// 连接数据库
mongoose.connect(process.env.MONGODB_URI)
.then(() => console.log('MongoDB connected'))
.catch(err => console.error('MongoDB connection error:', err));
const app = express();
// 会话配置
app.use(session({
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: false
}));
// Passport 配置
require('./config/passport')(passport);
app.use(passport.initialize());
app.use(passport.session());
// ... 其他配置
完整示例 #
完整的 app.js #
javascript
// app.js
require('dotenv').config();
const express = require('express');
const mongoose = require('mongoose');
const session = require('express-session');
const passport = require('passport');
const flash = require('connect-flash');
const path = require('path');
// 连接数据库
mongoose.connect(process.env.MONGODB_URI)
.then(() => console.log('MongoDB connected'))
.catch(err => console.error('MongoDB connection error:', err));
const app = express();
// 视图引擎设置
app.set('view engine', 'ejs');
app.set('views', path.join(__dirname, 'views'));
// 静态文件
app.use(express.static(path.join(__dirname, 'public')));
// 请求体解析
app.use(express.urlencoded({ extended: true }));
app.use(express.json());
// 会话配置
app.use(session({
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: false,
cookie: {
maxAge: 24 * 60 * 60 * 1000
}
}));
// Passport 初始化
app.use(passport.initialize());
app.use(passport.session());
// 闪存消息
app.use(flash());
// 全局变量
app.use((req, res, next) => {
res.locals.user = req.user || null;
res.locals.success_msg = req.flash('success');
res.locals.error_msg = req.flash('error');
next();
});
// 路由
app.use('/auth', require('./routes/auth'));
app.use('/', require('./routes/index'));
// 错误处理
app.use((req, res) => {
res.status(404).render('404');
});
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).render('500');
});
// 启动服务器
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
验证安装 #
测试脚本 #
javascript
// test/test.js
const request = require('supertest');
const app = require('../app');
describe('Authentication', () => {
it('should show login page', async () => {
const response = await request(app).get('/auth/login');
expect(response.status).toBe(200);
});
it('should show register page', async () => {
const response = await request(app).get('/auth/register');
expect(response.status).toBe(200);
});
it('should redirect to login for protected route', async () => {
const response = await request(app).get('/profile');
expect(response.status).toBe(302);
expect(response.headers.location).toContain('/auth/login');
});
});
运行测试 #
bash
# 安装测试依赖
npm install --save-dev jest supertest
# 运行测试
npx jest
常见问题 #
1. 会话不持久 #
javascript
// 确保会话配置正确
app.use(session({
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: false, // 设为 false 避免空会话
cookie: {
secure: process.env.NODE_ENV === 'production', // 生产环境启用
maxAge: 24 * 60 * 60 * 1000
}
}));
2. Passport 初始化顺序 #
javascript
// 正确的顺序
app.use(session({...})); // 1. 先配置会话
app.use(passport.initialize()); // 2. 初始化 Passport
app.use(passport.session()); // 3. 启用 Passport 会话支持
3. 跨域问题 #
javascript
// 开发环境 CORS 配置
const cors = require('cors');
app.use(cors({
origin: 'http://localhost:3000',
credentials: true // 允许携带 Cookie
}));
下一步 #
现在你已经完成了 Passport.js 的安装和基础配置,接下来学习 本地策略,实现完整的用户名/密码认证!
最后更新:2026-03-28