Search K
Appearance
Appearance
📊 SEO元描述:2024年最新Express会话管理教程,详解Cookie操作、Session管理、JWT认证。包含完整安全实践,适合Node.js开发者掌握用户认证技术。
核心关键词:Express会话管理2024、Node.js用户认证、Cookie Session管理、JWT认证实现、Express安全最佳实践
长尾关键词:Express会话管理怎么实现、Cookie和Session区别、JWT认证原理、Node.js用户登录系统、Express安全认证
通过本节Express会话管理教程,你将系统性掌握:
会话管理是什么?这是Web开发中的核心概念。由于HTTP协议是无状态的,服务器无法识别不同请求是否来自同一用户,会话管理技术解决了这个问题,也是用户认证和状态保持的基础。
💡 设计理念:会话管理应该安全、高效、用户友好,同时保护用户隐私
让我们从Cookie的基础操作开始:
// 🍪 Cookie操作详解
const express = require('express');
const cookieParser = require('cookie-parser');
const app = express();
// 配置cookie解析中间件
app.use(cookieParser('your-secret-key')); // 签名密钥
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
// Cookie基础操作演示
app.get('/cookie-demo', (req, res) => {
// 读取Cookie
const allCookies = req.cookies;
const signedCookies = req.signedCookies;
const specificCookie = req.cookies.username;
console.log('所有Cookie:', allCookies);
console.log('签名Cookie:', signedCookies);
console.log('特定Cookie:', specificCookie);
res.json({
message: 'Cookie演示',
cookies: allCookies,
signedCookies: signedCookies,
specificCookie: specificCookie
});
});
// 设置Cookie
app.post('/set-cookie', (req, res) => {
const { name, value, options = {} } = req.body;
// 基础Cookie设置
res.cookie(name, value, {
maxAge: options.maxAge || 24 * 60 * 60 * 1000, // 24小时
httpOnly: options.httpOnly !== false, // 防止XSS攻击
secure: options.secure || false, // HTTPS环境设为true
sameSite: options.sameSite || 'lax', // CSRF防护
domain: options.domain, // 域名限制
path: options.path || '/' // 路径限制
});
res.json({
message: 'Cookie设置成功',
cookie: { name, value, options }
});
});
// 设置签名Cookie
app.post('/set-signed-cookie', (req, res) => {
const { name, value } = req.body;
// 签名Cookie(防篡改)
res.cookie(name, value, {
signed: true,
maxAge: 24 * 60 * 60 * 1000,
httpOnly: true,
secure: process.env.NODE_ENV === 'production'
});
res.json({
message: '签名Cookie设置成功',
cookie: { name, value, signed: true }
});
});
// 删除Cookie
app.delete('/cookie/:name', (req, res) => {
const cookieName = req.params.name;
// 清除Cookie
res.clearCookie(cookieName);
res.json({
message: `Cookie ${cookieName} 已删除`
});
});
// Cookie安全配置示例
app.get('/secure-cookie-demo', (req, res) => {
// 设置多种安全Cookie
// 1. 会话Cookie(浏览器关闭时删除)
res.cookie('sessionId', 'abc123', {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'strict'
});
// 2. 持久Cookie(指定过期时间)
res.cookie('rememberMe', 'true', {
maxAge: 30 * 24 * 60 * 60 * 1000, // 30天
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'lax'
});
// 3. 用户偏好Cookie(可被JavaScript访问)
res.cookie('theme', 'dark', {
maxAge: 365 * 24 * 60 * 60 * 1000, // 1年
httpOnly: false, // 允许客户端JavaScript访问
secure: process.env.NODE_ENV === 'production',
sameSite: 'lax'
});
// 4. 签名Cookie(防篡改)
res.cookie('userId', '12345', {
signed: true,
maxAge: 24 * 60 * 60 * 1000,
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'strict'
});
res.json({
message: '安全Cookie设置完成',
cookies: [
{ name: 'sessionId', type: '会话Cookie' },
{ name: 'rememberMe', type: '持久Cookie' },
{ name: 'theme', type: '用户偏好Cookie' },
{ name: 'userId', type: '签名Cookie' }
]
});
});
// Cookie工具函数
function getCookieInfo(req) {
return {
total: Object.keys(req.cookies).length,
cookies: req.cookies,
signedCookies: req.signedCookies,
userAgent: req.get('User-Agent'),
ip: req.ip
};
}
app.get('/cookie-info', (req, res) => {
const info = getCookieInfo(req);
res.json(info);
});
console.log('Cookie操作演示配置完成');// 🔐 Session管理详解
const express = require('express');
const session = require('express-session');
const MongoStore = require('connect-mongo');
const bcrypt = require('bcrypt');
const app = express();
// 配置Session中间件
app.use(session({
secret: process.env.SESSION_SECRET || 'your-super-secret-key',
name: 'sessionId', // 自定义session cookie名称
resave: false, // 不强制保存未修改的session
saveUninitialized: false, // 不保存未初始化的session
rolling: true, // 每次请求重置过期时间
cookie: {
secure: process.env.NODE_ENV === 'production', // 生产环境使用HTTPS
httpOnly: true, // 防止XSS
maxAge: 24 * 60 * 60 * 1000, // 24小时
sameSite: 'lax' // CSRF防护
},
store: process.env.MONGODB_URI ? MongoStore.create({
mongoUrl: process.env.MONGODB_URI,
touchAfter: 24 * 3600 // 24小时内只更新一次
}) : undefined // 开发环境使用内存存储
}));
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
// 模拟用户数据库
const users = [
{
id: 1,
username: 'admin',
email: 'admin@example.com',
password: '$2b$10$8K1p/a0dRTlNlJ5n5rJ5.uNrTz8WjXn5rJ5.uNrTz8WjXn5rJ5.uO', // 'password123'
role: 'admin',
createdAt: new Date()
},
{
id: 2,
username: 'user1',
email: 'user1@example.com',
password: '$2b$10$8K1p/a0dRTlNlJ5n5rJ5.uNrTz8WjXn5rJ5.uNrTz8WjXn5rJ5.uO', // 'password123'
role: 'user',
createdAt: new Date()
}
];
// 认证中间件
function requireAuth(req, res, next) {
if (req.session && req.session.userId) {
const user = users.find(u => u.id === req.session.userId);
if (user) {
req.user = user;
return next();
}
}
res.status(401).json({
error: 'Unauthorized',
message: '请先登录'
});
}
// 管理员权限中间件
function requireAdmin(req, res, next) {
if (req.user && req.user.role === 'admin') {
return next();
}
res.status(403).json({
error: 'Forbidden',
message: '需要管理员权限'
});
}
// 用户注册
app.post('/register', async (req, res) => {
try {
const { username, email, password } = req.body;
// 验证输入
if (!username || !email || !password) {
return res.status(400).json({
error: 'Validation Error',
message: '用户名、邮箱和密码都是必需的'
});
}
// 检查用户是否已存在
const existingUser = users.find(u => u.username === username || u.email === email);
if (existingUser) {
return res.status(409).json({
error: 'Conflict',
message: '用户名或邮箱已存在'
});
}
// 密码加密
const saltRounds = 10;
const hashedPassword = await bcrypt.hash(password, saltRounds);
// 创建新用户
const newUser = {
id: users.length + 1,
username,
email,
password: hashedPassword,
role: 'user',
createdAt: new Date()
};
users.push(newUser);
// 自动登录
req.session.userId = newUser.id;
req.session.username = newUser.username;
res.status(201).json({
message: '注册成功',
user: {
id: newUser.id,
username: newUser.username,
email: newUser.email,
role: newUser.role
}
});
} catch (error) {
console.error('注册错误:', error);
res.status(500).json({
error: 'Internal Server Error',
message: '注册失败'
});
}
});
// 用户登录
app.post('/login', async (req, res) => {
try {
const { username, password } = req.body;
// 查找用户
const user = users.find(u => u.username === username || u.email === username);
if (!user) {
return res.status(401).json({
error: 'Authentication Failed',
message: '用户名或密码错误'
});
}
// 验证密码
const isValidPassword = await bcrypt.compare(password, user.password);
if (!isValidPassword) {
return res.status(401).json({
error: 'Authentication Failed',
message: '用户名或密码错误'
});
}
// 创建会话
req.session.userId = user.id;
req.session.username = user.username;
req.session.role = user.role;
req.session.loginTime = new Date();
res.json({
message: '登录成功',
user: {
id: user.id,
username: user.username,
email: user.email,
role: user.role
},
session: {
id: req.sessionID,
loginTime: req.session.loginTime
}
});
} catch (error) {
console.error('登录错误:', error);
res.status(500).json({
error: 'Internal Server Error',
message: '登录失败'
});
}
});
// 用户登出
app.post('/logout', (req, res) => {
if (req.session) {
req.session.destroy((err) => {
if (err) {
console.error('登出错误:', err);
return res.status(500).json({
error: 'Logout Failed',
message: '登出失败'
});
}
res.clearCookie('sessionId');
res.json({
message: '登出成功'
});
});
} else {
res.json({
message: '未登录状态'
});
}
});
// 获取当前用户信息
app.get('/profile', requireAuth, (req, res) => {
res.json({
user: {
id: req.user.id,
username: req.user.username,
email: req.user.email,
role: req.user.role,
createdAt: req.user.createdAt
},
session: {
id: req.sessionID,
loginTime: req.session.loginTime,
cookie: req.session.cookie
}
});
});
// 受保护的路由
app.get('/dashboard', requireAuth, (req, res) => {
res.json({
message: `欢迎 ${req.user.username}!`,
dashboard: {
userInfo: {
id: req.user.id,
username: req.user.username,
role: req.user.role
},
sessionInfo: {
sessionId: req.sessionID,
loginTime: req.session.loginTime,
expiresAt: new Date(Date.now() + req.session.cookie.maxAge)
},
stats: {
totalUsers: users.length,
currentTime: new Date()
}
}
});
});
// 管理员专用路由
app.get('/admin', requireAuth, requireAdmin, (req, res) => {
res.json({
message: '管理员面板',
users: users.map(u => ({
id: u.id,
username: u.username,
email: u.email,
role: u.role,
createdAt: u.createdAt
})),
systemInfo: {
nodeVersion: process.version,
uptime: process.uptime(),
memoryUsage: process.memoryUsage()
}
});
});
// Session信息查看
app.get('/session-info', (req, res) => {
if (req.session) {
res.json({
sessionId: req.sessionID,
session: req.session,
isAuthenticated: !!req.session.userId,
cookie: req.session.cookie
});
} else {
res.json({
message: '无会话信息'
});
}
});
console.log('Session管理配置完成');Session管理要点:
// 🔑 JWT认证详解
const express = require('express');
const jwt = require('jsonwebtoken');
const bcrypt = require('bcrypt');
const rateLimit = require('express-rate-limit');
const app = express();
// JWT配置
const JWT_SECRET = process.env.JWT_SECRET || 'your-jwt-secret-key';
const JWT_EXPIRES_IN = process.env.JWT_EXPIRES_IN || '24h';
const JWT_REFRESH_SECRET = process.env.JWT_REFRESH_SECRET || 'your-refresh-secret-key';
const JWT_REFRESH_EXPIRES_IN = process.env.JWT_REFRESH_EXPIRES_IN || '7d';
// 限流中间件
const loginLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15分钟
max: 5, // 最多5次尝试
message: {
error: 'Too Many Login Attempts',
message: '登录尝试次数过多,请15分钟后再试'
},
standardHeaders: true,
legacyHeaders: false
});
app.use(express.json());
// 模拟用户数据
const users = [
{
id: 1,
username: 'admin',
email: 'admin@example.com',
password: '$2b$10$8K1p/a0dRTlNlJ5n5rJ5.uNrTz8WjXn5rJ5.uNrTz8WjXn5rJ5.uO',
role: 'admin',
permissions: ['read', 'write', 'delete', 'admin'],
isActive: true,
lastLogin: null
},
{
id: 2,
username: 'user1',
email: 'user1@example.com',
password: '$2b$10$8K1p/a0dRTlNlJ5n5rJ5.uNrTz8WjXn5rJ5.uNrTz8WjXn5rJ5.uO',
role: 'user',
permissions: ['read', 'write'],
isActive: true,
lastLogin: null
}
];
// 存储刷新令牌(生产环境应使用数据库)
const refreshTokens = new Set();
// JWT工具函数
function generateTokens(user) {
const payload = {
id: user.id,
username: user.username,
email: user.email,
role: user.role,
permissions: user.permissions
};
const accessToken = jwt.sign(payload, JWT_SECRET, {
expiresIn: JWT_EXPIRES_IN,
issuer: 'your-app-name',
audience: 'your-app-users'
});
const refreshToken = jwt.sign(
{ id: user.id, type: 'refresh' },
JWT_REFRESH_SECRET,
{ expiresIn: JWT_REFRESH_EXPIRES_IN }
);
return { accessToken, refreshToken };
}
function verifyAccessToken(token) {
try {
return jwt.verify(token, JWT_SECRET);
} catch (error) {
throw error;
}
}
function verifyRefreshToken(token) {
try {
return jwt.verify(token, JWT_REFRESH_SECRET);
} catch (error) {
throw error;
}
}
// JWT认证中间件
function authenticateToken(req, res, next) {
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1]; // Bearer TOKEN
if (!token) {
return res.status(401).json({
error: 'Access Token Required',
message: '缺少访问令牌'
});
}
try {
const decoded = verifyAccessToken(token);
req.user = decoded;
next();
} catch (error) {
if (error.name === 'TokenExpiredError') {
return res.status(401).json({
error: 'Token Expired',
message: '访问令牌已过期',
expiredAt: error.expiredAt
});
} else if (error.name === 'JsonWebTokenError') {
return res.status(401).json({
error: 'Invalid Token',
message: '无效的访问令牌'
});
} else {
return res.status(401).json({
error: 'Token Verification Failed',
message: '令牌验证失败'
});
}
}
}
// 权限检查中间件
function requirePermission(permission) {
return function(req, res, next) {
if (!req.user) {
return res.status(401).json({
error: 'Unauthorized',
message: '未认证用户'
});
}
if (!req.user.permissions.includes(permission)) {
return res.status(403).json({
error: 'Insufficient Permissions',
message: `需要 ${permission} 权限`
});
}
next();
};
}
// 用户登录(JWT版本)
app.post('/auth/login', loginLimiter, async (req, res) => {
try {
const { username, password } = req.body;
if (!username || !password) {
return res.status(400).json({
error: 'Missing Credentials',
message: '用户名和密码都是必需的'
});
}
// 查找用户
const user = users.find(u =>
(u.username === username || u.email === username) && u.isActive
);
if (!user) {
return res.status(401).json({
error: 'Authentication Failed',
message: '用户名或密码错误'
});
}
// 验证密码
const isValidPassword = await bcrypt.compare(password, user.password);
if (!isValidPassword) {
return res.status(401).json({
error: 'Authentication Failed',
message: '用户名或密码错误'
});
}
// 生成令牌
const { accessToken, refreshToken } = generateTokens(user);
// 存储刷新令牌
refreshTokens.add(refreshToken);
// 更新最后登录时间
user.lastLogin = new Date();
res.json({
message: '登录成功',
user: {
id: user.id,
username: user.username,
email: user.email,
role: user.role,
permissions: user.permissions
},
tokens: {
accessToken,
refreshToken,
tokenType: 'Bearer',
expiresIn: JWT_EXPIRES_IN
}
});
} catch (error) {
console.error('JWT登录错误:', error);
res.status(500).json({
error: 'Internal Server Error',
message: '登录失败'
});
}
});
// 刷新访问令牌
app.post('/auth/refresh', (req, res) => {
const { refreshToken } = req.body;
if (!refreshToken) {
return res.status(401).json({
error: 'Refresh Token Required',
message: '缺少刷新令牌'
});
}
if (!refreshTokens.has(refreshToken)) {
return res.status(403).json({
error: 'Invalid Refresh Token',
message: '无效的刷新令牌'
});
}
try {
const decoded = verifyRefreshToken(refreshToken);
const user = users.find(u => u.id === decoded.id);
if (!user || !user.isActive) {
refreshTokens.delete(refreshToken);
return res.status(403).json({
error: 'User Not Found',
message: '用户不存在或已被禁用'
});
}
// 生成新的访问令牌
const { accessToken, refreshToken: newRefreshToken } = generateTokens(user);
// 替换刷新令牌
refreshTokens.delete(refreshToken);
refreshTokens.add(newRefreshToken);
res.json({
message: '令牌刷新成功',
tokens: {
accessToken,
refreshToken: newRefreshToken,
tokenType: 'Bearer',
expiresIn: JWT_EXPIRES_IN
}
});
} catch (error) {
refreshTokens.delete(refreshToken);
if (error.name === 'TokenExpiredError') {
return res.status(401).json({
error: 'Refresh Token Expired',
message: '刷新令牌已过期,请重新登录'
});
}
res.status(403).json({
error: 'Invalid Refresh Token',
message: '无效的刷新令牌'
});
}
});
// 用户登出(JWT版本)
app.post('/auth/logout', authenticateToken, (req, res) => {
const { refreshToken } = req.body;
if (refreshToken) {
refreshTokens.delete(refreshToken);
}
res.json({
message: '登出成功'
});
});
// 获取用户信息
app.get('/auth/profile', authenticateToken, (req, res) => {
const user = users.find(u => u.id === req.user.id);
if (!user) {
return res.status(404).json({
error: 'User Not Found',
message: '用户不存在'
});
}
res.json({
user: {
id: user.id,
username: user.username,
email: user.email,
role: user.role,
permissions: user.permissions,
lastLogin: user.lastLogin
},
tokenInfo: {
issuedAt: new Date(req.user.iat * 1000),
expiresAt: new Date(req.user.exp * 1000),
issuer: req.user.iss,
audience: req.user.aud
}
});
});
// 受保护的路由示例
app.get('/api/data', authenticateToken, requirePermission('read'), (req, res) => {
res.json({
message: '这是受保护的数据',
data: {
timestamp: new Date(),
user: req.user.username,
permissions: req.user.permissions
}
});
});
app.post('/api/data', authenticateToken, requirePermission('write'), (req, res) => {
res.json({
message: '数据创建成功',
data: req.body,
createdBy: req.user.username
});
});
app.delete('/api/data/:id', authenticateToken, requirePermission('delete'), (req, res) => {
res.json({
message: `数据 ${req.params.id} 删除成功`,
deletedBy: req.user.username
});
});
// 管理员专用路由
app.get('/api/admin/users', authenticateToken, requirePermission('admin'), (req, res) => {
res.json({
message: '用户列表',
users: users.map(u => ({
id: u.id,
username: u.username,
email: u.email,
role: u.role,
isActive: u.isActive,
lastLogin: u.lastLogin
})),
totalRefreshTokens: refreshTokens.size
});
});
// 令牌验证端点
app.post('/auth/verify', (req, res) => {
const { token } = req.body;
if (!token) {
return res.status(400).json({
error: 'Token Required',
message: '缺少令牌'
});
}
try {
const decoded = verifyAccessToken(token);
res.json({
valid: true,
decoded,
expiresAt: new Date(decoded.exp * 1000)
});
} catch (error) {
res.json({
valid: false,
error: error.name,
message: error.message
});
}
});
console.log('JWT认证配置完成');JWT认证要点:
// 🛡️ 会话安全最佳实践
const express = require('express');
const helmet = require('helmet');
const csrf = require('csurf');
const rateLimit = require('express-rate-limit');
const session = require('express-session');
const MongoStore = require('connect-mongo');
const app = express();
// 1. 安全头设置
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'"],
scriptSrc: ["'self'"],
imgSrc: ["'self'", "data:", "https:"],
connectSrc: ["'self'"],
fontSrc: ["'self'"],
objectSrc: ["'none'"],
mediaSrc: ["'self'"],
frameSrc: ["'none'"]
}
},
hsts: {
maxAge: 31536000,
includeSubDomains: true,
preload: true
}
}));
// 2. 限流配置
const generalLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15分钟
max: 100, // 每个IP最多100个请求
message: {
error: 'Too Many Requests',
message: '请求过于频繁,请稍后再试'
}
});
const authLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 5, // 认证相关请求更严格
skipSuccessfulRequests: true,
message: {
error: 'Too Many Auth Attempts',
message: '认证尝试过于频繁,请稍后再试'
}
});
app.use('/api/', generalLimiter);
app.use('/auth/', authLimiter);
// 3. Session安全配置
app.use(session({
secret: process.env.SESSION_SECRET || 'your-super-secret-key',
name: 'sid', // 自定义session名称,隐藏技术栈
resave: false,
saveUninitialized: false,
rolling: true,
cookie: {
secure: process.env.NODE_ENV === 'production',
httpOnly: true,
maxAge: 30 * 60 * 1000, // 30分钟
sameSite: 'strict' // 严格的CSRF防护
},
store: process.env.MONGODB_URI ? MongoStore.create({
mongoUrl: process.env.MONGODB_URI,
touchAfter: 24 * 3600,
crypto: {
secret: process.env.SESSION_SECRET
}
}) : undefined
}));
// 4. CSRF防护
const csrfProtection = csrf({
cookie: {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'strict'
}
});
app.use(express.json({ limit: '10mb' }));
app.use(express.urlencoded({ extended: true, limit: '10mb' }));
// 5. 安全工具函数
function sanitizeInput(input) {
if (typeof input !== 'string') return input;
return input
.replace(/[<>]/g, '') // 移除潜在的HTML标签
.trim()
.substring(0, 1000); // 限制长度
}
function validateEmail(email) {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
}
function validatePassword(password) {
// 至少8位,包含大小写字母、数字和特殊字符
const passwordRegex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/;
return passwordRegex.test(password);
}
function logSecurityEvent(type, details, req) {
const event = {
timestamp: new Date().toISOString(),
type,
details,
ip: req.ip,
userAgent: req.get('User-Agent'),
sessionId: req.sessionID,
userId: req.session?.userId
};
console.log('🔒 安全事件:', JSON.stringify(event, null, 2));
// 生产环境中应该发送到安全监控系统
if (process.env.NODE_ENV === 'production') {
// 发送到安全监控系统
// sendToSecurityMonitoring(event);
}
}
// 6. 安全中间件
function securityMiddleware(req, res, next) {
// 记录可疑活动
const suspiciousPatterns = [
/script/i,
/javascript/i,
/vbscript/i,
/onload/i,
/onerror/i,
/<iframe/i,
/eval\(/i,
/document\.cookie/i
];
const requestData = JSON.stringify({
url: req.url,
body: req.body,
query: req.query,
headers: req.headers
});
for (const pattern of suspiciousPatterns) {
if (pattern.test(requestData)) {
logSecurityEvent('SUSPICIOUS_REQUEST', {
pattern: pattern.toString(),
url: req.url,
method: req.method
}, req);
break;
}
}
next();
}
app.use(securityMiddleware);
// 7. 输入验证中间件
function validateInput(schema) {
return function(req, res, next) {
const errors = [];
for (const [field, rules] of Object.entries(schema)) {
const value = req.body[field];
if (rules.required && (!value || value.trim() === '')) {
errors.push(`${field} 是必需字段`);
continue;
}
if (value) {
// 清理输入
req.body[field] = sanitizeInput(value);
// 验证规则
if (rules.type === 'email' && !validateEmail(value)) {
errors.push(`${field} 邮箱格式不正确`);
}
if (rules.type === 'password' && !validatePassword(value)) {
errors.push(`${field} 密码强度不够(至少8位,包含大小写字母、数字和特殊字符)`);
}
if (rules.minLength && value.length < rules.minLength) {
errors.push(`${field} 长度不能少于 ${rules.minLength} 个字符`);
}
if (rules.maxLength && value.length > rules.maxLength) {
errors.push(`${field} 长度不能超过 ${rules.maxLength} 个字符`);
}
}
}
if (errors.length > 0) {
logSecurityEvent('VALIDATION_FAILED', {
errors,
url: req.url,
method: req.method
}, req);
return res.status(400).json({
error: 'Validation Failed',
details: errors
});
}
next();
};
}
// 8. 会话安全检查
function sessionSecurityCheck(req, res, next) {
if (req.session && req.session.userId) {
const now = Date.now();
const lastActivity = req.session.lastActivity || now;
const inactiveTime = now - lastActivity;
// 检查会话是否过期(30分钟无活动)
if (inactiveTime > 30 * 60 * 1000) {
req.session.destroy((err) => {
if (err) console.error('会话销毁错误:', err);
});
return res.status(401).json({
error: 'Session Expired',
message: '会话已过期,请重新登录'
});
}
// 更新最后活动时间
req.session.lastActivity = now;
// 检查IP地址变化(可选的安全措施)
if (req.session.ipAddress && req.session.ipAddress !== req.ip) {
logSecurityEvent('IP_ADDRESS_CHANGED', {
oldIP: req.session.ipAddress,
newIP: req.ip,
userId: req.session.userId
}, req);
// 可以选择强制重新登录
// req.session.destroy();
// return res.status(401).json({ error: 'Security violation detected' });
}
req.session.ipAddress = req.ip;
}
next();
}
app.use(sessionSecurityCheck);
// 9. 安全的登录端点
app.post('/secure-login',
validateInput({
username: { required: true, minLength: 3, maxLength: 50 },
password: { required: true, type: 'password' }
}),
csrfProtection,
async (req, res) => {
try {
const { username, password } = req.body;
// 模拟用户验证
const user = { id: 1, username: 'admin', role: 'admin' };
if (user) {
req.session.userId = user.id;
req.session.username = user.username;
req.session.role = user.role;
req.session.loginTime = new Date();
req.session.ipAddress = req.ip;
logSecurityEvent('LOGIN_SUCCESS', {
userId: user.id,
username: user.username
}, req);
res.json({
message: '登录成功',
user: {
id: user.id,
username: user.username,
role: user.role
},
csrfToken: req.csrfToken()
});
} else {
logSecurityEvent('LOGIN_FAILED', {
username,
reason: 'Invalid credentials'
}, req);
res.status(401).json({
error: 'Authentication Failed',
message: '用户名或密码错误'
});
}
} catch (error) {
logSecurityEvent('LOGIN_ERROR', {
error: error.message
}, req);
res.status(500).json({
error: 'Internal Server Error',
message: '登录失败'
});
}
}
);
// 10. CSRF令牌端点
app.get('/csrf-token', csrfProtection, (req, res) => {
res.json({
csrfToken: req.csrfToken()
});
});
// 11. 安全监控端点
app.get('/security-status', (req, res) => {
res.json({
security: {
helmet: '已启用',
csrf: '已启用',
rateLimit: '已启用',
sessionSecurity: '已启用',
inputValidation: '已启用'
},
environment: process.env.NODE_ENV || 'development',
timestamp: new Date().toISOString()
});
});
console.log('安全配置完成');安全最佳实践要点:
通过本节Express会话管理教程的学习,你已经掌握:
A: 选择依据:1)Cookie适合简单的客户端状态存储;2)Session适合传统Web应用和需要服务端控制的场景;3)JWT适合API服务、微服务和移动应用;4)可以组合使用,如JWT存储在httpOnly Cookie中。
A: 防护措施:1)使用HTTPS传输;2)设置httpOnly和secure标志;3)登录后重新生成session ID;4)检查IP地址变化;5)实现会话超时;6)使用强随机数生成session ID。
A: 主要风险:1)无法主动撤销(使用黑名单或短期过期);2)信息泄露(避免在payload中存储敏感信息);3)密钥泄露(使用强密钥并定期轮换);4)重放攻击(使用jti和时间戳)。
A: 安全实现:1)生成安全的重置令牌;2)设置短期过期时间;3)限制重置尝试次数;4)验证用户身份(邮箱、手机);5)重置后强制重新登录;6)记录重置操作日志。
A: 解决方案:1)使用Redis等外部存储;2)实现会话复制;3)使用无状态JWT;4)会话粘性(sticky session);5)考虑使用专门的会话管理服务。
"会话管理是Web应用安全的核心,掌握了Cookie、Session和JWT等技术,你就具备了构建安全可靠的用户认证系统的能力。安全无小事,持续学习和实践安全最佳实践,保护用户数据和应用安全!"