会话管理 #
一、会话管理概述 #
1.1 会话管理方式 #
| 方式 | 说明 | 特点 |
|---|---|---|
| Cookie | 存储在客户端 | 简单,不安全 |
| Session | 服务端存储 | 安全,需要存储 |
| JWT | 无状态令牌 | 可扩展,无状态 |
1.2 选择建议 #
| 场景 | 推荐方式 |
|---|---|
| 传统Web应用 | Session |
| SPA/移动应用 | JWT |
| 微服务架构 | JWT |
二、Cookie-Session #
2.1 安装 #
bash
npm install express-session cookie-parser
2.2 基本配置 #
javascript
const session = require('express-session');
const cookieParser = require('cookie-parser');
app.use(cookieParser());
app.use(session({
secret: 'your-secret-key',
resave: false,
saveUninitialized: false,
cookie: {
secure: process.env.NODE_ENV === 'production',
httpOnly: true,
maxAge: 24 * 60 * 60 * 1000
}
}));
2.3 使用Session #
javascript
app.post('/login', (req, res) => {
req.session.userId = user.id;
req.session.username = user.name;
res.json({ message: '登录成功' });
});
app.get('/profile', (req, res) => {
if (!req.session.userId) {
return res.status(401).json({ error: '请先登录' });
}
res.json({ userId: req.session.userId });
});
app.post('/logout', (req, res) => {
req.session.destroy();
res.json({ message: '退出成功' });
});
2.4 Session存储 #
内存存储(开发):
javascript
app.use(session({
secret: 'secret',
resave: false,
saveUninitialized: false
}));
Redis存储(生产):
bash
npm install connect-redis
javascript
const RedisStore = require('connect-redis')(session);
const redis = require('redis');
const redisClient = redis.createClient({
host: 'localhost',
port: 6379
});
app.use(session({
store: new RedisStore({ client: redisClient }),
secret: 'secret',
resave: false,
saveUninitialized: false
}));
MongoDB存储:
bash
npm install connect-mongo
javascript
const MongoStore = require('connect-mongo');
app.use(session({
store: MongoStore.create({
mongoUrl: process.env.DATABASE_URL
}),
secret: 'secret',
resave: false,
saveUninitialized: false
}));
三、JWT认证 #
3.1 安装 #
bash
npm install jsonwebtoken bcryptjs
3.2 JWT工具 #
javascript
const jwt = require('jsonwebtoken');
const bcrypt = require('bcryptjs');
const generateToken = (payload) => {
return jwt.sign(payload, process.env.JWT_SECRET, {
expiresIn: process.env.JWT_EXPIRES_IN || '7d'
});
};
const verifyToken = (token) => {
return jwt.verify(token, process.env.JWT_SECRET);
};
const hashPassword = async (password) => {
return await bcrypt.hash(password, 10);
};
const comparePassword = async (password, hashedPassword) => {
return await bcrypt.compare(password, hashedPassword);
};
module.exports = { generateToken, verifyToken, hashPassword, comparePassword };
3.3 认证中间件 #
javascript
const { verifyToken } = require('../utils/jwt');
const User = require('../models/User');
const auth = async (req, res, next) => {
try {
const authHeader = req.headers['authorization'];
const token = authHeader?.split(' ')[1];
if (!token) {
return res.status(401).json({ error: '请先登录' });
}
const decoded = verifyToken(token);
const user = await User.findById(decoded.id);
if (!user) {
return res.status(401).json({ error: '用户不存在' });
}
req.user = user;
next();
} catch (error) {
res.status(401).json({ error: '无效的token' });
}
};
const authorize = (...roles) => {
return (req, res, next) => {
if (!roles.includes(req.user.role)) {
return res.status(403).json({ error: '没有权限' });
}
next();
};
};
module.exports = { auth, authorize };
3.4 认证路由 #
javascript
const router = require('express').Router();
const { generateToken, comparePassword } = require('../utils/jwt');
const User = require('../models/User');
router.post('/register', async (req, res) => {
try {
const { name, email, password } = req.body;
const existingUser = await User.findOne({ email });
if (existingUser) {
return res.status(400).json({ error: '邮箱已被注册' });
}
const user = await User.create({ name, email, password });
const token = generateToken({ id: user._id });
res.status(201).json({ user, token });
} catch (error) {
res.status(500).json({ error: error.message });
}
});
router.post('/login', async (req, res) => {
try {
const { email, password } = req.body;
const user = await User.findOne({ email }).select('+password');
if (!user) {
return res.status(401).json({ error: '邮箱或密码错误' });
}
const isMatch = await comparePassword(password, user.password);
if (!isMatch) {
return res.status(401).json({ error: '邮箱或密码错误' });
}
const token = generateToken({ id: user._id });
res.json({ user, token });
} catch (error) {
res.status(500).json({ error: error.message });
}
});
router.get('/me', auth, (req, res) => {
res.json(req.user);
});
module.exports = router;
四、Token刷新 #
4.1 刷新Token机制 #
javascript
const generateTokens = (userId) => {
const accessToken = jwt.sign(
{ id: userId },
process.env.JWT_SECRET,
{ expiresIn: '15m' }
);
const refreshToken = jwt.sign(
{ id: userId },
process.env.JWT_REFRESH_SECRET,
{ expiresIn: '7d' }
);
return { accessToken, refreshToken };
};
router.post('/refresh', async (req, res) => {
const { refreshToken } = req.body;
if (!refreshToken) {
return res.status(401).json({ error: '缺少refresh token' });
}
try {
const decoded = jwt.verify(refreshToken, process.env.JWT_REFRESH_SECRET);
const accessToken = generateToken({ id: decoded.id });
res.json({ accessToken });
} catch (error) {
res.status(401).json({ error: '无效的refresh token' });
}
});
五、OAuth集成 #
5.1 Passport.js #
bash
npm install passport passport-google-oauth20
javascript
const passport = require('passport');
const GoogleStrategy = require('passport-google-oauth20').Strategy;
passport.use(new GoogleStrategy({
clientID: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
callbackURL: '/auth/google/callback'
}, async (accessToken, refreshToken, profile, done) => {
let user = await User.findOne({ googleId: profile.id });
if (!user) {
user = await User.create({
googleId: profile.id,
name: profile.displayName,
email: profile.emails[0].value
});
}
done(null, user);
}));
app.get('/auth/google', passport.authenticate('google', { scope: ['profile', 'email'] }));
app.get('/auth/google/callback',
passport.authenticate('google', { session: false }),
(req, res) => {
const token = generateToken({ id: req.user._id });
res.redirect(`/auth/callback?token=${token}`);
}
);
六、总结 #
会话管理要点:
| 方式 | 说明 |
|---|---|
| Session | 服务端存储,需要存储后端 |
| JWT | 无状态,适合分布式 |
| Token刷新 | 延长会话时间 |
| OAuth | 第三方登录 |
下一步,让我们学习API设计!
最后更新:2026-03-28