前端登录加密与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 });
});
相关推荐
xuedaobian2 小时前
2025年我是怎么用AI写代码的
前端·程序员·ai编程
风止何安啊2 小时前
Set/Map+Weak三剑客的骚操作:JS 界的 “去重王者” ,“万能钥匙”和“隐形清洁工”
前端·javascript·面试
3秒一个大2 小时前
React 中 Context 的作用与用法:从主题切换案例说起
前端·react.js
2501_944446002 小时前
Flutter&OpenHarmony文本输入组件开发
前端·javascript·flutter
AI前端老薛2 小时前
你了解react合成事件吗
前端·react.js·前端框架
贺今宵2 小时前
2025.electron-vue3-sqlite3使用
前端·javascript·electron
王同学_1163 小时前
爬虫辅助技术(css选择器、xpath、正则基础语法)
前端·css·爬虫
牛先森家的牛奶3 小时前
elementUI的table合并行和列模板
前端·javascript·elementui
En^_^Joy3 小时前
CSS常用属性速查手册
前端·css