前端登录加密与Token管理实践

前端登录加密与Token管理实践

前后端分离架构下的登录安全、Token存储方案。涵盖登录加密、JWT、Cookie+httpOnly等核心内容。


一、登录加密流程

方案:前端哈希 + HTTPS + 后端强哈希

javascript 复制代码
// 前端
async function login(username, password) {
  // 1. 前端哈希(防中间人攻击)
  const hashedPwd = CryptoJS.SHA256(password).toString();
  
  // 2. HTTPS传输
  const res = await axios.post('/api/login', { username, password: hashedPwd });
  
  // 3. Token自动存储(浏览器查看后端响应头自动存储token并处理httpOnly Cookie)
}

// 后端(必须)
// 1. 二次加盐哈希存储
const bcryptHash = await bcrypt.hash(hashedPwd, 12);
// 2. 生成JWT放入httpOnly Cookie

核心原则

  • HTTPS 是强制前提:所有登录请求必须通过HTTPS传输
  • 后端必须用 bcrypt/Argon2:即使前端已哈希,后端仍需强哈希存储
  • 前端哈希是可选的:主要防传输层泄露,非必需但推荐

二、JWT详解

结构

css 复制代码
Header.Payload.Signature

1. Header(头部)

  • 作用:声明算法和类型。
  • 示例: json json { "alg": "HS256", // 签名算法(如 HMAC SHA256) "typ": "JWT" // Token 类型 }
  • Base64Url 编码后成为第一部分。

2. Payload(载荷)

  • 作用:存放实际数据(称为"声明" Claims)。
  • 标准声明 (可选):
    • iss(签发者)
    • exp(过期时间)
    • sub(主题,如用户ID)
    • aud(接收者)
  • 自定义声明:业务相关数据(如用户角色、权限)。 json json { "sub": "1234567890", "name": "John Doe", "role": "admin", "exp": 1717986919 // 过期时间戳(UTC) }
  • Base64Url 编码 后成为第二部分。 ⚠️ 注意 :Payload 未加密 ,仅防篡改,不要存储敏感信息(如密码)。

3. Signature(签名)

  • 作用:验证 Token 未被篡改。
  • 生成方式: text text HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret_key )
  • 结果:签名 + 前两部分 = 最终 JWT。

安全实践

  • 短有效期:15分钟访问令牌 + 7天刷新令牌
  • 不存敏感数据:密码、手机号等绝不在Payload中
  • httpOnly Cookie存储:防XSS

三、Cookie + httpOnly 存储方案(核心)

完整流程

后端设置(Node.js示例)

javascript 复制代码
res.cookie('token', jwt, {
  httpOnly: true,    // ✅ JS无法读取,防XSS
  secure: true,      // ✅ 仅HTTPS(生产环境)
  sameSite: 'Strict', // ✅ 防CSRF
  maxAge: 15 * 60 * 1000 // 15分钟过期
});

前端无感

  • 无需手动存储 :浏览器会自动读取响应头中set-cookie
  • 无需手动携带:浏览器自动在请求头中附加Cookie
  • 登出 :后端调用 res.clearCookie('token')

四、前端鉴权问题与解决

问题

httpOnly Cookie导致前端无法直接读取Token,无法判断登录状态。

解决方案:返回Userinfo给前端

javascript 复制代码
// 后端登录接口
app.post('/api/login', (req, res) => {
  const token = generateJWT(user);
  res.cookie('token', token, { httpOnly: true, secure: true });
  
  // 返回非敏感用户信息
  res.json({
    userinfo: { id: user.id, role: user.role, name: user.name }
  });
});

// 前端存储
// 方式1:普通Cookie(前端可读)
document.cookie = `userinfo=${encodeURIComponent(JSON.stringify(userinfo))}; Path=/; SameSite=Strict`;

// 方式2:localStorage(需防XSS)
localStorage.setItem('userinfo', JSON.stringify(userinfo));

// 路由守卫
router.beforeEach((to, from, next) => {
  const userinfo = localStorage.getItem('userinfo') || getCookie('userinfo');
  if (userinfo) next();
  else next('/login');
});

安全增强

  • Userinfo仅含非敏感信息 :ID、角色、昵称,绝不存密码、手机号

  • 过期时间一致:userinfo与token同时失效

  • 登出同步清除 :

    javascript 复制代码
    // 后端
    res.clearCookie('token');
    res.clearCookie('userinfo');
    // 前端
    localStorage.removeItem('userinfo');

高安全场景增强

关键路由(如支付、个人中心)增加后端验证:

javascript 复制代码
router.beforeEach(async (to, from, next) => {
  const userinfo = localStorage.getItem('userinfo');
  if (!userinfo) return next('/login');
  
  if (to.meta.requiresAuth) {
    try {
      await axios.get('/api/auth/verify', { withCredentials: true });
      next();
    } catch {
      localStorage.removeItem('userinfo');
      next('/login');
    }
  } else {
    next();
  }
});

五、总结与最佳实践

核心配置速查

组件 配置 目的
Token Cookie httpOnly: true, secure: true, sameSite: 'Strict' 防XSS、CSRF,仅HTTPS
Userinfo存储 普通Cookie或localStorage 前端路由鉴权
JWT有效期 访问令牌15分钟,刷新令牌7天 平衡安全与体验
路由守卫 检查userinfo存在性 无权限不渲染

常见误区

  1. ❌ Token返回给前端:应仅存httpOnly Cookie,前端无需知道
  2. ❌ 长有效期:应短有效期+刷新机制
  3. ❌ userinfo存敏感信息:仅存非敏感数据
  4. ❌ 登出不清除:必须同步清除前后端存储

六、核心代码片段

1. 后端登录完整示例

javascript 复制代码
app.post('/api/login', async (req, res) => {
  const { username, password } = req.body;
  
  // 1. 前端哈希值 → 后端bcrypt验证
  const user = await db.users.findOne({ username });
  const isValid = await bcrypt.compare(password, user.passwordHash);
  
  if (!isValid) return res.status(401).json({ error: '认证失败' });
  
  // 2. 生成JWT
  const token = jwt.sign(
    { sub: user.id, role: user.role },
    process.env.JWT_SECRET,
    { expiresIn: '15m' }
  );
  
  // 3. 设置httpOnly Cookie
  res.cookie('token', token, {
    httpOnly: true,
    secure: true,
    sameSite: 'Strict',
    maxAge: 15 * 60 * 1000
  });
  
  // 4. 返回用户信息
  res.json({
    userinfo: { id: user.id, role: user.role, name: user.name }
  });
});

2. 前端路由守卫

javascript 复制代码
router.beforeEach(async (to, from, next) => {
  const hasToken = !!document.cookie.includes('token='); // 仅检查存在性
  const userinfo = localStorage.getItem('userinfo');
  
  if (to.path === '/login') {
    next(hasToken ? '/' : '/login');
    return;
  }
  
  if (!hasToken || !userinfo) {
    next('/login');
    return;
  }
  
  // 高安全页面验证Token有效性
  if (to.meta.requiresAuth) {
    try {
      await axios.get('/api/auth/verify', { withCredentials: true });
      next();
    } catch {
      localStorage.removeItem('userinfo');
      next('/login');
    }
  } else {
    next();
  }
});

3. 登出同步清除

javascript 复制代码
// 前端
const logout = async () => {
  await axios.post('/api/logout', {}, { withCredentials: true });
  localStorage.removeItem('userinfo');
  // 或清除普通Cookie: document.cookie = 'userinfo=; Path=/; Expires=Thu, 01 Jan 1970 00:00:00 GMT';
  router.push('/login');
};

// 后端
app.post('/api/logout', (req, res) => {
  res.clearCookie('token');
  res.clearCookie('userinfo');
  res.json({ success: true });
});
相关推荐
卸任9 小时前
Vibe coding工具使用心得
前端·ai编程
brien910 小时前
为什么模型标称 1M,Google Antigravity 聊到 256K 却突然失忆?我们给它做了个原生行内监控圆环
前端
修炼的dance10 小时前
录屏关键帧间隔多少合适?六档实测,2秒到10秒小了75.6%
前端
Lank_M10 小时前
关键帧从1秒拉到10秒,录屏小87.3%,摄像头小10.8%
前端
补码缩写10 小时前
用 ffprobe 拆录屏,八成字节在 29 个关键帧上
前端
百思不得小李10 小时前
一个复杂的动态表单是如何实现的
前端·设计模式
陈随易11 小时前
Cmder 停更 17 个月,2.0 支持 Windows Terminal
前端·后端·程序员
风骏时光牛马11 小时前
前沿视界
前端
kyriewen11 小时前
我扫了 814 个依赖,97 个还敢要 Node 10
前端·javascript·程序员
陈随易11 小时前
3000 万人在用的翻译插件,重写一遍就是一门生意
前端·后端·程序员