跳到主要内容前端敏感数据安全处理:从存储到传输的全链路防护 | 极客日志JavaScript大前端
前端敏感数据安全处理:从存储到传输的全链路防护
前端敏感数据安全涉及存储、传输、内存及展示四个维度。存储首选 HttpOnly Cookie,避免 localStorage;传输强制 HTTPS 并辅以 RSA/AES 加密;内存需清理敏感变量并禁止打印;展示时进行脱敏处理。通过全链路防护体系,可有效降低数据泄露风险,满足合规要求。
在前端开发中,敏感数据(如 Token、用户密码、手机号、身份证号等)的安全处理是重中之重。一旦泄露,不仅会导致用户信息被盗、账号被冒用,还可能违反《网络安全法》《个人信息保护法》等合规要求,给企业和开发者带来严重损失。
前端处理敏感数据的核心原则是**「最小暴露 + 分层防护」**:不将敏感数据暴露在不安全的存储介质中,不裸传敏感数据,及时清理内存中的敏感信息,从存储、传输、展示、内存四个维度构建全链路安全防护体系。本文将结合实战场景,详细拆解每个环节的安全方案和避坑指南。
一、核心场景 1:Token 安全存储(避坑是关键)
Token(如 JWT、Access Token)是前端最核心的敏感数据,用于身份认证,其存储位置直接决定了安全等级。很多开发者习惯将 Token 存在 localStorage 中,这种方式看似便捷,却存在极大的 XSS 窃取风险。我们先看看主流存储方式的安全性差异,再给出落地性极强的最优方案。
1.1 主流存储方式安全性对比
| 存储方式 | 安全性 | 优点 | 缺点 | 适用场景 |
|---|
| HttpOnly + Secure Cookie | ✅ 最高 | 防 XSS(前端 JS 无法读取)、浏览器自动携带请求 | 需配置 SameSite 防 CSRF、跨域需特殊处理 | 服务端可控、非跨域场景(如后台管理系统、企业内网应用) |
| 内存(变量/闭包) | ✅ 高 | 页面刷新/关闭后自动销毁,无持久化泄露风险 | 无法跨页面共享、刷新后需重新获取 | 临时存储(如登录态临时持有、跨域/跨端无法用 Cookie 场景) |
| localStorage/sessionStorage | ❌ 低 | API 简洁、易操作、支持跨页面共享 | 易被 XSS 脚本窃取(可直接通过 API 读取)、持久化存储风险 | 仅临时过渡(禁止存储核心 Token 等敏感数据) |
| 明文写在代码/本地文件 | ❌ 最低 | 无 | 直接泄露敏感数据,黑客可通过源码/文件获取 | 绝对禁止 |
1.2 最优方案:HttpOnly + Secure Cookie 存储(推荐)
这是 Token 存储的**「黄金方案」**,从根源上规避 XSS 窃取风险——因为 HttpOnly 标记的 Cookie 无法被前端 JavaScript 读取,即使页面被注入 XSS 脚本,也无法获取 Token。配合 Secure、SameSite 等配置,可同时防御 CSRF 和明文传输风险。
1.2.1 服务端核心配置(前端需确认后端已配置)
Token 需通过服务端响应头 Set-Cookie 写入,核心配置项如下:
Set-Cookie: access_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...; HttpOnly; # 核心:前端 JS 无法读取,防 XSS
Secure; # 仅允许 HTTPS 传输,防明文泄露
SameSite=Strict; # 仅同站请求携带,防 CSRF 攻击
Path=/; # Cookie 作用域(全域名生效)
Max-Age=86400; # 过期时间(单位:秒,按需设置,如 1 天)
Domain=.example.com; # 子域名共享(如需跨子域可配置)
1.2.2 前端跨域场景配合
如果前后端分离且存在跨域(如前端部署在 www.example.com,后端 API 部署在 api.example.com),需在请求中开启 withCredentials,让浏览器自动携带跨域 Cookie:
import axios from 'axios';
axios.defaults. = ;
axios.. = ;
= () => {
{
res = axios.();
res.;
} (error) {
.(, error);
}
};
baseURL
'https://api.example.com'
defaults
withCredentials
true
const
getUserInfo
async
try
const
await
get
'/user/info'
return
data
catch
console
error
'获取用户信息失败:'
1.3 妥协方案:内存存储 + 刷新重签(跨域/非 Cookie 场景)
在部分特殊场景(如跨端应用、跨域且无法配置 Cookie、第三方授权回调),无法使用 HttpOnly Cookie 存储 Token 时,可采用**「内存存储 + 刷新重签」**方案——核心是避免 Token 持久化存储,降低泄露风险。
封装 Token 管理工具(仅内存存储,页面刷新即丢失):
const TokenManager = (() => {
let _accessToken = '';
let _refreshToken = '';
const setToken = (accessToken, refreshToken) => {
_accessToken = accessToken || '';
_refreshToken = refreshToken || '';
};
const getAccessToken = () => _accessToken;
const getRefreshToken = () => _refreshToken;
const clearToken = () => {
_accessToken = '';
_refreshToken = '';
};
const isTokenValid = () => !!_accessToken;
return { setToken, getAccessToken, getRefreshToken, clearToken, isTokenValid };
})();
export default TokenManager;
import axios from 'axios';
import TokenManager from '../utils/tokenManager';
export const login = async (username, password) => {
const res = await axios.post('/auth/login', { username, password });
const { accessToken, refreshToken } = res.data;
TokenManager.setToken(accessToken, refreshToken);
return res.data;
};
export const logout = async () => {
await axios.post('/auth/logout');
TokenManager.clearToken();
window.location.href = '/login';
};
axios.interceptors.request.use(config => {
if (TokenManager.isTokenValid()) {
config.headers.Authorization = `Bearer ${TokenManager.getAccessToken()}`;
}
return config;
}, error => Promise.reject(error));
axios.interceptors.response.use(res => res, async error => {
const originalRequest = error.config;
if (error.response.status === 401 && !originalRequest._retry) {
originalRequest._retry = true;
try {
const refreshToken = TokenManager.getRefreshToken();
const res = await axios.post('/auth/refresh-token', { refreshToken });
const { accessToken: newAccessToken } = res.data;
TokenManager.setToken(newAccessToken, refreshToken);
originalRequest.headers.Authorization = `Bearer ${newAccessToken}`;
return axios(originalRequest);
} catch (refreshError) {
TokenManager.clearToken();
window.location.href = '/login';
return Promise.reject(refreshError);
}
}
return Promise.reject(error);
});
1.4 绝对避坑:这些操作禁止做
- ❌ 不要把 Token 存在 localStorage/sessionStorage:XSS 脚本可直接通过
localStorage.getItem('token') 窃取;
- ❌ 不要把 Token 拼在 URL 参数里:会被浏览器历史记录、服务器日志记录,泄露风险极高;
- ❌ 不要在 console.log 中打印 Token/密码:调试时容易遗漏删除,生产环境可能泄露;
- ❌ 不要将 Token 存储在 Vuex/Pinia 且开启持久化:持久化插件本质还是存在 localStorage,同样有 XSS 风险。
二、核心场景 2:敏感数据加密传输(HTTPS 是基础)
敏感数据的传输安全,核心是**「HTTPS 保底 + 前端加密补充」**。HTTPS 是传输层的基础安全保障,可防止中间人攻击和数据裸传;前端加密则是在 HTTPS 基础上进一步加固,满足合规要求或应对特殊场景(如明文密码传输)。
2.1 基础:必须使用 HTTPS(不可替代)
HTTPS 并非简单的**「HTTP + SSL/TLS」,其核心是通过「非对称加密 + 对称加密」**的组合方式,实现数据传输的机密性、完整性和真实性:
- 握手阶段:客户端与服务端协商加密算法,服务端向客户端发送公钥;
- 密钥交换:客户端生成随机的**「对称加密密钥」**,用服务端公钥加密后传输给服务端;
- 数据传输:双方后续所有通信数据,均用**「对称加密密钥」**加密传输(对称加密效率远高于非对称加密)。
✅ 核心作用:即使数据被中间人拦截,拿到的也是密文,无法解密(缺少对称加密密钥)。
⚠️ 前端必须确认:所有接口均使用 HTTPS 协议,禁止 HTTP 混合内容(浏览器会拦截 HTTP 请求,控制台报错)。
2.2 补充:前端对敏感参数二次加密(加固方案)
HTTPS 已能满足大部分场景的安全需求,但在部分高敏感场景(如用户登录密码、支付信息)或合规要求(如等保三级)下,需对核心敏感参数做前端二次加密后再传输——即使 HTTPS 被破解(极端场景),敏感数据仍处于加密状态。
2.2.1 方案 1:非对称加密(RSA)—— 适合密码等单一高敏感参数
RSA 加密的核心优势是**「公钥加密、私钥解密」**,公钥可公开传输,私钥仅服务端持有(即使公钥泄露,黑客也无法解密数据),非常适合登录密码等单一高敏感参数的加密。
import JSEncrypt from 'jsencrypt';
const getPublicKey = async () => {
const res = await axios.get('/api/public-key');
return res.data.publicKey;
};
export const rsaEncrypt = async (data) => {
const publicKey = await getPublicKey();
const encryptor = new JSEncrypt();
encryptor.setPublicKey(publicKey);
return encryptor.encrypt(data);
};
import { rsaEncrypt } from '../utils/rsaEncrypt';
export const login = async (username, password) => {
const encryptedPwd = await rsaEncrypt(password);
const res = await axios.post('/auth/login', {
username,
password: encryptedPwd
});
return res.data;
};
2.2.2 方案 2:对称加密(AES)—— 适合批量敏感参数
AES 加密的优势是**「加密效率高、适合批量数据」,但需要前端与服务端约定「密钥(key)」和「偏移量(iv)」**(密钥需通过 HTTPS 传输,避免裸传),适合用户手机号、身份证号、地址等批量敏感参数的加密。
import CryptoJS from 'crypto-js';
let AES_KEY = '';
let AES_IV = '';
export const initAesConfig = async () => {
const res = await axios.get('/api/aes-config');
AES_KEY = CryptoJS.enc.Utf8.parse(res.data.key);
AES_IV = CryptoJS.enc.Utf8.parse(res.data.iv);
};
export const aesEncrypt = (data) => {
const jsonStr = JSON.stringify(data);
const encrypted = CryptoJS.AES.encrypt(jsonStr, AES_KEY, {
iv: AES_IV,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
});
return encrypted.toString();
};
export const aesDecrypt = (encryptedData) => {
const decrypted = CryptoJS.AES.decrypt(encryptedData, AES_KEY, {
iv: AES_IV,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
});
return JSON.parse(CryptoJS.enc.Utf8.stringify(decrypted).toString());
};
import { initAesConfig, aesEncrypt } from '../utils/aesEncrypt';
initAesConfig();
export const updateUserInfo = async (userInfo) => {
const encryptedData = aesEncrypt(userInfo);
const res = await axios.post('/api/user/update', {
data: encryptedData
});
return res.data;
};
2.3 传输层额外加固:请求头/响应头防护
除了数据加密,还可通过配置请求头/响应头,进一步提升传输安全性:
- 禁止在响应头返回敏感数据:如 Token、用户身份证号等,避免泄露;
- 配置 Content-Security-Policy(CSP)响应头:限制外部脚本加载、禁止 inline-script,减少 XSS 风险;
- 配置 X-Content-Type-Options: nosniff:防止浏览器将非预期的文件类型解析为脚本;
- 配置 X-XSS-Protection: 1; mode=block:开启浏览器内置 XSS 防护。
三、前端内存级防护:敏感数据不泄露
即使存储和传输环节安全,前端内存中的敏感数据仍可能通过控制台打印、调试工具、页面卸载残留等方式泄露。需从**「禁止打印、及时清理、规避全局存储」**三个维度做好内存防护。
3.1 禁止打印敏感数据
开发调试时,很多开发者习惯用 console.log 打印数据,若包含 Token、密码等敏感信息,生产环境未删除会导致泄露。建议封装安全日志工具,过滤敏感字段并区分环境:
export const safeLog = (data, title = '日志信息') => {
if (process.env.NODE_ENV === 'production') return;
const filterData = JSON.parse(JSON.stringify(data));
const sensitiveKeys = ['token', 'accessToken', 'refreshToken', 'password', 'idCard', 'phone', 'bankCard'];
const filterSensitiveData = (obj) => {
if (typeof obj !== 'object' || obj === null) return obj;
if (Array.isArray(obj)) {
return obj.map(item => filterSensitiveData(item));
}
for (const key in obj) {
if (sensitiveKeys.includes(key.toLowerCase())) {
obj[key] = '******';
} else {
obj[key] = filterSensitiveData(obj[key]);
}
}
return obj;
};
const logData = filterSensitiveData(filterData);
console.log(`[${title}]`, logData);
};
import { safeLog } from '../utils/safeLog';
const userInfo = {
name: '张三',
phone: '13800138000',
token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...'
};
safeLog(userInfo, '用户信息');
3.2 及时清理内存中的敏感数据
敏感数据在内存中停留的时间越长,泄露风险越高。需在**「登出、页面卸载、表单提交后」**及时清理:
const logout = async () => {
await axios.post('/auth/logout');
TokenManager.clearToken();
document.querySelector('#password')?.setValue('');
window.__sensitiveData = null;
window.location.href = '/login';
};
window.addEventListener('beforeunload', () => {
TokenManager.clearToken();
const sensitiveInputs = document.querySelectorAll('input[type="password"], [data-sensitive]');
sensitiveInputs.forEach(input => input.value = '');
});
const handleLoginSubmit = async (formData) => {
const { username, password } = formData;
try {
await login(username, password);
formData.password = '';
window.location.href = '/home';
} catch (error) {
console.error('登录失败:', error);
}
};
3.3 规避全局存储敏感数据
避免用全局变量(如 window、global)存储敏感数据,即使是临时存储,也可能被第三方脚本或调试工具获取:
- ❌ 禁止:
window.token = 'xxx';
- ✅ 推荐:用闭包、模块私有变量存储(如前文的 TokenManager)。
四、其他敏感数据处理:展示与输入防护
除了 Token 和传输中的数据,前端展示和输入环节的敏感数据(如手机号、身份证号、密码)也需做防护,避免用户视觉泄露或输入时被窃取。
4.1 敏感数据脱敏展示
前端展示用户手机号、身份证号、银行卡号等数据时,需进行脱敏处理——只显示部分字符,隐藏核心信息:
export const maskPhone = (phone) => {
if (!phone) return '';
return phone.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2');
};
export const maskIdCard = (idCard) => {
if (!idCard) return '';
return idCard.replace(/(\d{6})\d{8}(\d{4})/, '$1********$2');
};
export const maskBankCard = (bankCard) => {
if (!bankCard) return '';
return bankCard.replace(/(\d{4})\d+(\d{4})/, '$1****$2');
};
import { maskPhone, maskIdCard } from '../utils/dataMask';
const userInfo = {
phone: '13800138000',
idCard: '110101199001011234'
};
console.log(maskPhone(userInfo.phone));
console.log(maskIdCard(userInfo.idCard));
4.2 密码输入防护
密码输入环节需避免自动填充、复制粘贴等风险,提升输入安全性:
<input type="password" placeholder="请输入密码" autocomplete="new-password"
oncopy="return false" <!-- 禁止复制 -->
oncut="return false"
onpaste="return false"
spellcheck="false" />
五、实战总结与合规建议
5.1 核心防护链路总结
前端敏感数据安全处理,需覆盖**「存储 → 传输 → 内存 → 展示」**全链路,核心方案总结如下:
- 存储:Token 优先用 HttpOnly + Secure + SameSite Cookie;无法用 Cookie 时,用内存存储 + 刷新重签,禁止持久化;
- 传输:基于 HTTPS,密码用 RSA 加密,批量敏感参数用 AES 加密,避免裸传;
- 内存:禁止打印敏感数据,登出/页面卸载时及时清理,规避全局存储;
- 展示:敏感数据脱敏展示,密码输入框禁止自动填充和复制粘贴。
5.2 合规与上线检查建议
项目上线前,建议做以下敏感数据安全检查,确保符合合规要求:
- 检查所有 Token 存储方式,禁止使用 localStorage/sessionStorage;
- 检查所有接口是否使用 HTTPS,敏感参数是否加密传输;
- 检查生产环境是否有敏感数据打印,是否清理了内存敏感数据;
- 检查敏感数据展示是否脱敏,密码输入框是否有防护配置;
- 进行 XSS 漏洞测试,验证 HttpOnly Cookie 是否有效。
5.3 最后提醒
前端敏感数据处理的核心是**「最小暴露」**——能不存储的绝不存储,能加密的绝不裸传,能脱敏的绝不完整展示。没有绝对安全的方案,只有相对更安全的实践,需结合业务场景和合规要求,选择最合适的防护方案,同时持续关注安全漏洞和最新防护技术,不断优化安全体系。
相关免费在线工具
- Keycode 信息
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
- Escape 与 Native 编解码
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
- JavaScript / HTML 格式化
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
- JavaScript 压缩与混淆
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
- Base64 文件转换器
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online