如何在客户端安全存储已登录用户?(Node.js/Express/SQLite场景)
登录状态保持的两种实用方案(适配Express+SQLite)
首先必须纠正你当前的一个严重安全问题:绝对不要直接存储或对比明文密码,一定要用bcrypt这类加盐哈希算法处理密码后再存入数据库,避免数据泄露后用户密码直接暴露。
下面针对你的需求,提供两种成熟的登录状态保持方案:
方案一:Session + Cookie(Express生态最常用)
这种方式依赖浏览器自动携带Cookie,后端通过Session验证用户身份,适合传统服务端渲染场景。
步骤1:安装依赖
npm install express-session connect-sqlite3 bcrypt
步骤2:配置Session中间件
用SQLite存储Session数据,避免服务器重启后丢失会话:
const express = require('express'); const session = require('express-session'); const SQLiteStore = require('connect-sqlite3')(session); const bcrypt = require('bcrypt'); const sqlite3 = require('sqlite3').verbose(); const app = express(); // 配置Session app.use(session({ secret: '你的随机密钥(生产环境用环境变量存储,绝对不能硬编码)', resave: false, saveUninitialized: false, store: new SQLiteStore({ db: 'sessions.db', // Session数据存在这个SQLite文件里 table: 'sessions' }), cookie: { httpOnly: true, // 禁止前端JS读取Cookie,防范XSS攻击 secure: process.env.NODE_ENV === 'production', // 生产环境开启HTTPS时必须设为true maxAge: 24 * 60 * 60 * 1000 // Cookie有效期1天 } })); // 解析请求体数据 app.use(express.urlencoded({ extended: true })); app.use(express.json()); // 连接你的业务数据库 const db = new sqlite3.Database('./your-db-name.db');
步骤3:改造登录逻辑
验证密码后将用户标识存入Session:
app.post('/login', async (req, res) => { const { username, password } = req.body; // 查询用户的哈希密码(注意:数据库里存的是password_hash,不是明文密码) db.get('SELECT id, username, password_hash FROM users WHERE username = ?', [username], async (err, user) => { if (err || !user) { return res.status(401).send('用户名或密码错误'); } // 验证密码哈希是否匹配 const isPasswordValid = await bcrypt.compare(password, user.password_hash); if (!isPasswordValid) { return res.status(401).send('用户名或密码错误'); } // 将用户ID存入Session(不要存敏感信息) req.session.userId = user.id; req.session.username = user.username; // 返回初始信息流 db.get('SELECT feed FROM users WHERE id = ?', [user.id], (err, data) => { if (err) return res.status(500).send('服务器错误'); res.render('feed', { feed: data.feed, username: user.username }); }); }); });
步骤4:保护需要登录的接口
写一个中间件验证Session状态,刷新信息流时直接调用这个接口:
// 登录验证中间件 function requireAuth(req, res, next) { if (req.session.userId) { return next(); // 已登录,继续执行后续逻辑 } res.redirect('/login'); // 未登录,跳转到登录页 } // 获取最新信息流的接口 app.get('/api/feed', requireAuth, (req, res) => { db.get('SELECT feed FROM users WHERE id = ?', [req.session.userId], (err, data) => { if (err) return res.status(500).send('服务器错误'); res.json({ feed: data.feed }); }); });
前端刷新时直接请求/api/feed即可,浏览器会自动携带Cookie,后端通过Session识别用户。
方案二:JWT(JSON Web Token)
这种方式不需要后端存储Session,Token本身包含用户信息,适合前后端分离场景。
步骤1:安装依赖
npm install jsonwebtoken bcrypt
步骤2:改造登录逻辑,生成JWT
const jwt = require('jsonwebtoken'); const JWT_SECRET = '你的JWT密钥(生产环境用环境变量)'; app.post('/login', async (req, res) => { const { username, password } = req.body; db.get('SELECT id, username, password_hash FROM users WHERE username = ?', [username], async (err, user) => { if (err || !user) { return res.status(401).send('用户名或密码错误'); } const isPasswordValid = await bcrypt.compare(password, user.password_hash); if (!isPasswordValid) { return res.status(401).send('用户名或密码错误'); } // 生成JWT,有效期1天 const token = jwt.sign({ userId: user.id, username: user.username }, JWT_SECRET, { expiresIn: '1d' }); // 返回Token和初始信息流 db.get('SELECT feed FROM users WHERE id = ?', [user.id], (err, data) => { if (err) return res.status(500).send('服务器错误'); res.json({ token, feed: data.feed }); }); }); });
步骤3:验证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).send('请先登录'); } jwt.verify(token, JWT_SECRET, (err, user) => { if (err) { return res.status(403).send('登录状态已过期'); } req.user = user; // 将用户信息挂载到req对象 next(); }); } // 受保护的信息流接口 app.get('/api/feed', authenticateToken, (req, res) => { db.get('SELECT feed FROM users WHERE id = ?', [req.user.userId], (err, data) => { if (err) return res.status(500).send('服务器错误'); res.json({ feed: data.feed }); }); });
步骤4:前端处理Token
登录成功后把Token存在localStorage或HttpOnly Cookie中,每次请求时携带:
// 登录成功后保存Token localStorage.setItem('authToken', token); // 刷新信息流时请求接口 async function refreshFeed() { const token = localStorage.getItem('authToken'); const response = await fetch('/api/feed', { headers: { 'Authorization': `Bearer ${token}` } }); const data = await response.json(); // 更新页面信息流内容 }
关键安全注意事项
- 永远不要存储明文密码,注册用户时用
bcrypt.hash(password, 10)生成哈希值存入数据库 - 生产环境下,所有密钥(Session secret、JWT secret)都要存在环境变量里,不要硬编码
- 用HTTPS协议传输数据,防止Cookie或Token被窃听
- JWT存在
localStorage时要防范XSS攻击,优先考虑存在HttpOnly Cookie中
内容的提问来源于stack exchange,提问作者Olle
相关产品推荐
相关产品推荐

