前端登录加密与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存在性 | 无权限不渲染 |
常见误区
- ❌ Token返回给前端:应仅存httpOnly Cookie,前端无需知道
- ❌ 长有效期:应短有效期+刷新机制
- ❌ userinfo存敏感信息:仅存非敏感数据
- ❌ 登出不清除:必须同步清除前后端存储
六、核心代码片段
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 });
});