Skip to content

Express会话管理2024:Node.js开发者用户认证完整指南

📊 SEO元描述:2024年最新Express会话管理教程,详解Cookie操作、Session管理、JWT认证。包含完整安全实践,适合Node.js开发者掌握用户认证技术。

核心关键词:Express会话管理2024、Node.js用户认证、Cookie Session管理、JWT认证实现、Express安全最佳实践

长尾关键词:Express会话管理怎么实现、Cookie和Session区别、JWT认证原理、Node.js用户登录系统、Express安全认证


📚 会话管理学习目标与核心收获

通过本节Express会话管理教程,你将系统性掌握:

  • 会话管理基础:理解HTTP无状态特性和会话管理的重要性
  • Cookie操作详解:掌握Cookie的设置、读取和安全配置
  • Session管理:学会使用express-session实现服务端会话管理
  • JWT认证实现:掌握JSON Web Token的原理和实际应用
  • 用户认证系统:构建完整的用户注册、登录、权限控制系统
  • 安全最佳实践:了解会话安全、CSRF防护等安全措施

🎯 适合人群

  • Express进阶学习者的用户认证技能提升
  • 全栈开发者的后端安全技术掌握
  • Web安全工程师的认证机制深入理解
  • Node.js开发者的会话管理实战需求

🌟 会话管理是什么?为什么Web应用需要状态管理?

会话管理是什么?这是Web开发中的核心概念。由于HTTP协议是无状态的,服务器无法识别不同请求是否来自同一用户,会话管理技术解决了这个问题,也是用户认证和状态保持的基础。

会话管理的核心价值

  • 🎯 用户识别:在多个请求间识别和跟踪用户身份
  • 🔧 状态保持:维护用户的登录状态和个性化设置
  • 💡 安全控制:实现访问权限控制和安全验证
  • 📚 用户体验:提供个性化内容和无缝的用户体验
  • 🚀 业务逻辑:支持购物车、用户偏好等业务功能

💡 设计理念:会话管理应该安全、高效、用户友好,同时保护用户隐私

Cookie操作详解

让我们从Cookie的基础操作开始:

javascript
// 🍪 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操作演示配置完成');

Cookie操作要点

  • httpOnly:防止XSS攻击,禁止JavaScript访问
  • secure:仅在HTTPS连接中传输
  • sameSite:防止CSRF攻击,控制跨站请求
  • signed:使用密钥签名,防止篡改
  • maxAge/expires:控制Cookie生命周期

Session管理实现

使用express-session实现会话管理

javascript
// 🔐 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管理要点

  • 🎯 存储选择:生产环境使用Redis/MongoDB,开发环境可用内存
  • 🎯 安全配置:设置合适的cookie选项和session密钥
  • 🎯 生命周期:合理设置session过期时间和更新策略
  • 🎯 清理机制:定期清理过期session,避免内存泄漏

JWT认证实现

JSON Web Token认证系统

javascript
// 🔑 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认证要点

  • 🎯 无状态:JWT是自包含的,服务器无需存储会话信息
  • 🎯 可扩展:适合分布式系统和微服务架构
  • 🎯 安全性:使用强密钥签名,设置合理的过期时间
  • 🎯 刷新机制:实现访问令牌和刷新令牌的双令牌机制
  • 🎯 权限控制:在令牌中包含用户权限信息

安全最佳实践

会话安全和CSRF防护

javascript
// 🛡️ 会话安全最佳实践
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('安全配置完成');

安全最佳实践要点

  • 🎯 多层防护:使用多种安全措施构建深度防御
  • 🎯 输入验证:严格验证和清理所有用户输入
  • 🎯 会话安全:实现会话超时、IP检查等安全机制
  • 🎯 CSRF防护:使用CSRF令牌防止跨站请求伪造
  • 🎯 安全监控:记录和监控安全事件

📚 会话管理学习总结与下一步规划

✅ 本节核心收获回顾

通过本节Express会话管理教程的学习,你已经掌握:

  1. 会话管理基础概念:理解了HTTP无状态特性和会话管理的重要性
  2. Cookie操作详解:掌握了Cookie的设置、读取和各种安全配置选项
  3. Session管理实现:学会了使用express-session实现完整的服务端会话管理
  4. JWT认证系统:掌握了JSON Web Token的原理和双令牌认证机制
  5. 用户认证系统:构建了包含注册、登录、权限控制的完整认证系统
  6. 安全最佳实践:了解了CSRF防护、输入验证、安全监控等安全措施

🎯 会话管理下一步

  1. OAuth集成:学习第三方登录(Google、GitHub等)集成
  2. 单点登录(SSO):实现跨应用的统一认证系统
  3. 多因素认证(MFA):添加短信验证、TOTP等额外安全层
  4. 会话集群:学习分布式环境下的会话管理

🔗 相关学习资源

💪 实践练习建议

  1. 构建用户管理系统:实现完整的用户注册、登录、权限管理
  2. 开发API认证服务:创建可复用的认证微服务
  3. 实现社交登录:集成第三方OAuth认证
  4. 安全审计系统:构建安全事件监控和报警系统

🔍 常见问题FAQ

Q1: Cookie、Session和JWT应该如何选择?

A: 选择依据:1)Cookie适合简单的客户端状态存储;2)Session适合传统Web应用和需要服务端控制的场景;3)JWT适合API服务、微服务和移动应用;4)可以组合使用,如JWT存储在httpOnly Cookie中。

Q2: 如何防止会话劫持和固定攻击?

A: 防护措施:1)使用HTTPS传输;2)设置httpOnly和secure标志;3)登录后重新生成session ID;4)检查IP地址变化;5)实现会话超时;6)使用强随机数生成session ID。

Q3: JWT的安全风险有哪些?

A: 主要风险:1)无法主动撤销(使用黑名单或短期过期);2)信息泄露(避免在payload中存储敏感信息);3)密钥泄露(使用强密钥并定期轮换);4)重放攻击(使用jti和时间戳)。

Q4: 如何实现安全的密码重置功能?

A: 安全实现:1)生成安全的重置令牌;2)设置短期过期时间;3)限制重置尝试次数;4)验证用户身份(邮箱、手机);5)重置后强制重新登录;6)记录重置操作日志。

Q5: 分布式环境下如何管理会话?

A: 解决方案:1)使用Redis等外部存储;2)实现会话复制;3)使用无状态JWT;4)会话粘性(sticky session);5)考虑使用专门的会话管理服务。


"会话管理是Web应用安全的核心,掌握了Cookie、Session和JWT等技术,你就具备了构建安全可靠的用户认证系统的能力。安全无小事,持续学习和实践安全最佳实践,保护用户数据和应用安全!"