会话管理 #

一、会话管理概述 #

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