You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在客户端安全存储已登录用户?(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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.16 03:55:36